Subject: [PATCH v12 1/5] Paul's main patch

From: Alvaro Herrera <[email-masked]>
Date: Mon, 9 Mar 2020 17:20:04 -0300

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

--fdj2RfSjLxBAspz7
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v12-0002-match-a77315fdf2a197a925e670be2d8b376c4ac02efc.patch"