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

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

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

