Multirange datatypes
authorAlexander Korotkov <akorotkov@postgresql.org>
Sun, 20 Dec 2020 04:20:33 +0000 (07:20 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Sun, 20 Dec 2020 04:20:33 +0000 (07:20 +0300)
Multiranges are basically sorted arrays of non-overlapping ranges with
set-theoretic operations defined over them.

Since v14, each range type automatically gets a corresponding multirange
datatype.  There are both manual and automatic mechanisms for naming multirange
types.  Once can specify multirange type name using multirange_type_name
attribute in CREATE TYPE.  Otherwise, a multirange type name is generated
automatically.  If the range type name contains "range" then we change that to
"multirange".  Otherwise, we add "_multirange" to the end.

Implementation of multiranges comes with a space-efficient internal
representation format, which evades extra paddings and duplicated storage of
oids.  Altogether this format allows fetching a particular range by its index
in O(n).

Statistic gathering and selectivity estimation are implemented for multiranges.
For this purpose, stored multirange is approximated as union range without gaps.
This field will likely need improvements in the future.

Catversion is bumped.

Discussion: https://postgr.es/m/CALNJ-vSUpQ_Y%3DjXvTxt1VYFztaBSsWVXeF1y6gTYQ4bOiWDLgQ%40mail.gmail.com
Discussion: https://postgr.es/m/a0b8026459d1e6167933be2104a6174e7d40d0ab.camel%40j-davis.com#fe7218c83b08068bfffb0c5293eceda0
Author: Paul Jungwirth, revised by me
Reviewed-by: David Fetter, Corey Huinker, Jeff Davis, Pavel Stehule
Reviewed-by: Alvaro Herrera, Tom Lane, Isaac Morland, David G. Johnston
Reviewed-by: Zhihong Yu, Alexander Korotkov
67 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/datatype.sgml
doc/src/sgml/extend.sgml
doc/src/sgml/func.sgml
doc/src/sgml/rangetypes.sgml
doc/src/sgml/ref/create_type.sgml
src/backend/catalog/pg_range.c
src/backend/catalog/pg_type.c
src/backend/commands/typecmds.c
src/backend/executor/functions.c
src/backend/parser/parse_coerce.c
src/backend/utils/adt/Makefile
src/backend/utils/adt/multirangetypes.c [new file with mode: 0644]
src/backend/utils/adt/multirangetypes_selfuncs.c [new file with mode: 0644]
src/backend/utils/adt/pg_upgrade_support.c
src/backend/utils/adt/pseudotypes.c
src/backend/utils/adt/rangetypes.c
src/backend/utils/adt/rangetypes_typanalyze.c
src/backend/utils/cache/lsyscache.c
src/backend/utils/cache/syscache.c
src/backend/utils/cache/typcache.c
src/backend/utils/fmgr/funcapi.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/pg_dump/t/002_pg_dump.pl
src/include/access/tupmacs.h
src/include/catalog/binary_upgrade.h
src/include/catalog/catversion.h
src/include/catalog/pg_aggregate.dat
src/include/catalog/pg_amop.dat
src/include/catalog/pg_amproc.dat
src/include/catalog/pg_cast.dat
src/include/catalog/pg_opclass.dat
src/include/catalog/pg_operator.dat
src/include/catalog/pg_opfamily.dat
src/include/catalog/pg_proc.dat
src/include/catalog/pg_range.dat
src/include/catalog/pg_range.h
src/include/catalog/pg_type.dat
src/include/catalog/pg_type.h
src/include/commands/typecmds.h
src/include/utils/lsyscache.h
src/include/utils/multirangetypes.h [new file with mode: 0644]
src/include/utils/rangetypes.h
src/include/utils/selfuncs.h
src/include/utils/syscache.h
src/include/utils/typcache.h
src/pl/plpgsql/src/pl_comp.c
src/test/regress/expected/dependency.out
src/test/regress/expected/hash_func.out
src/test/regress/expected/multirangetypes.out [new file with mode: 0644]
src/test/regress/expected/opr_sanity.out
src/test/regress/expected/plpgsql.out
src/test/regress/expected/polymorphism.out
src/test/regress/expected/rangefuncs.out
src/test/regress/expected/rangetypes.out
src/test/regress/expected/sanity_check.out
src/test/regress/expected/type_sanity.out
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/hash_func.sql
src/test/regress/sql/multirangetypes.sql [new file with mode: 0644]
src/test/regress/sql/opr_sanity.sql
src/test/regress/sql/polymorphism.sql
src/test/regress/sql/rangetypes.sql
src/test/regress/sql/type_sanity.sql
src/tools/pgindent/typedefs.list

index 89ca59b92b5837456e508afbdc145a60ac2588da..d98863604673a7629c4b787120d8d748664080fc 100644 (file)
@@ -6237,6 +6237,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmultitypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the multirange type for this range type
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rngcollation</structfield> <type>oid</type>
@@ -8671,8 +8681,9 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>c</literal> for a composite type (e.g., a table's row type),
        <literal>d</literal> for a domain,
        <literal>e</literal> for an enum type,
-       <literal>p</literal> for a pseudo-type, or
-       <literal>r</literal> for a range type.
+       <literal>p</literal> for a pseudo-type,
+       <literal>r</literal> for a range type, or
+       <literal>m</literal> for a multirange type.
        See also <structfield>typrelid</structfield> and
        <structfield>typbasetype</structfield>.
       </para></entry>
index 9eb19a1c616303536508eadc7fc3081f3fa1ab80..58d168c763e96577ae93f6bcb8d1923a134de085 100644 (file)
@@ -4907,6 +4907,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4923,6 +4927,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5034,6 +5042,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5063,6 +5078,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
index 1c37026bb0508dab3d95675fc5282fe77a4c2901..6e3d82b85b804d5388b7dce6cfb3bc5c1764221c 100644 (file)
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
index df29af6371a296c02e506fd4851d218b6efc8a03..d5cd705eebb7f0562ab1a4f67739e8223aca45c2 100644 (file)
@@ -17884,12 +17884,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18099,15 +18102,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18269,10 +18706,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18604,6 +19216,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
index b75fb3a3929b3bd9eb74432aeccb22ba925f44c8..83aa9bc4e9e40d5a0aa7c162134ea49de77e8955 100644 (file)
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
-  <title>Built-in Range Types</title>
+  <title>Built-in Range and Multirange Types</title>
 
  <para>
   PostgreSQL comes with the following built-in range types:
   <itemizedlist>
     <listitem>
       <para>
-       <type>int4range</type> &mdash; Range of <type>integer</type>
+       <type>int4range</type> &mdash; Range of <type>integer</type>,
+       <type>int4multirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>int8range</type> &mdash; Range of <type>bigint</type>
+       <type>int8range</type> &mdash; Range of <type>bigint</type>,
+       <type>int8multirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>numrange</type> &mdash; Range of <type>numeric</type>
+       <type>numrange</type> &mdash; Range of <type>numeric</type>,
+       <type>nummultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>tsrange</type> &mdash; Range of <type>timestamp without time zone</type>
+       <type>tsrange</type> &mdash; Range of <type>timestamp without time zone</type>,
+       <type>tsmultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>tstzrange</type> &mdash; Range of <type>timestamp with time zone</type>
+       <type>tstzrange</type> &mdash; Range of <type>timestamp with time zone</type>,
+       <type>tstzmultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>daterange</type> &mdash; Range of <type>date</type>
+       <type>daterange</type> &mdash; Range of <type>date</type>,
+       <type>datemultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
   </itemizedlist>
@@ -232,10 +245,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +300,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +387,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
index d575f166142451273d50dba85e7cf29915fc3e02..7d2d6aa0af846bf6ce8beac3674aa689fbb17be9 100644 (file)
@@ -33,6 +33,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> AS RANGE (
     [ , COLLATION = <replaceable class="parameter">collation</replaceable> ]
     [ , CANONICAL = <replaceable class="parameter">canonical_function</replaceable> ]
     [ , SUBTYPE_DIFF = <replaceable class="parameter">subtype_diff_function</replaceable> ]
+    [ , MULTIRANGE_TYPE_NAME = <replaceable class="parameter">multirange_type_name</replaceable> ]
 )
 
 CREATE TYPE <replaceable class="parameter">name</replaceable> (
@@ -174,6 +175,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
     the range type.  See <xref linkend="rangetypes-defining"/> for more
     information.
    </para>
+
+   <para>
+    The optional <replaceable class="parameter">multirange_type_name</replaceable>
+    parameter specifies the name of the corresponding multirange type.  If not
+    specified, this name is chosen automatically as follows.
+    If range type name contains <literal>range</literal> substring, then
+    multirange type name is formed by replacement of the <literal>range</literal>
+    substring with <literal>multirange</literal> substring in the range
+    type name.  Otherwise, multirange type name is formed by appending
+    <literal>_multirange</literal> suffix to the range type name.
+   </para>
   </refsect2>
 
   <refsect2>
@@ -630,6 +642,15 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">multirange_type_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the corresponding multirange type.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">input_function</replaceable></term>
     <listitem>
index a606d8c3adb43ea2b3aa9626500053797c95ed8d..91b0fb0611a78369201e60990b9a7b0e55dce217 100644 (file)
@@ -35,7 +35,7 @@
 void
 RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
                        Oid rangeSubOpclass, RegProcedure rangeCanonical,
-                       RegProcedure rangeSubDiff)
+                       RegProcedure rangeSubDiff, Oid multirangeTypeOid)
 {
        Relation        pg_range;
        Datum           values[Natts_pg_range];
@@ -43,6 +43,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
        HeapTuple       tup;
        ObjectAddress myself;
        ObjectAddress referenced;
+       ObjectAddress referencing;
        ObjectAddresses *addrs;
 
        pg_range = table_open(RangeRelationId, RowExclusiveLock);
@@ -55,6 +56,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
        values[Anum_pg_range_rngsubopc - 1] = ObjectIdGetDatum(rangeSubOpclass);
        values[Anum_pg_range_rngcanonical - 1] = ObjectIdGetDatum(rangeCanonical);
        values[Anum_pg_range_rngsubdiff - 1] = ObjectIdGetDatum(rangeSubDiff);
+       values[Anum_pg_range_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
        tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
        record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
        free_object_addresses(addrs);
 
+       /* record multirange type's dependency on the range type */
+       referencing.classId = TypeRelationId;
+       referencing.objectId = multirangeTypeOid;
+       referencing.objectSubId = 0;
+       recordDependencyOn(&referencing, &myself, DEPENDENCY_INTERNAL);
+
        table_close(pg_range, RowExclusiveLock);
 }
 
index 4252875ef5013ab70b6e0d9ea40eef9b7d2402c1..2aa686a640fbd4aa07b8a2039de62d8980f2710a 100644 (file)
@@ -28,6 +28,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -37,6 +38,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+                                                               bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid                    binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -863,31 +867,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-       char       *arr = (char *) palloc(NAMEDATALEN);
-       int                     namelen = strlen(typeName);
-       int                     i;
-
-       /*
-        * The idea is to prepend underscores as needed until we make a name that
-        * doesn't collide with anything...
-        */
-       for (i = 1; i < NAMEDATALEN - 1; i++)
-       {
-               arr[i - 1] = '_';
-               if (i + namelen < NAMEDATALEN)
-                       strcpy(arr + i, typeName);
-               else
-               {
-                       memcpy(arr + i, typeName, NAMEDATALEN - i);
-                       truncate_identifier(arr, NAMEDATALEN, false);
-               }
-               if (!SearchSysCacheExists2(TYPENAMENSP,
-                                                                  CStringGetDatum(arr),
-                                                                  ObjectIdGetDatum(typeNamespace)))
-                       break;
-       }
+       char       *arr;
 
-       if (i >= NAMEDATALEN - 1)
+       arr = makeUniqueTypeName(typeName, typeNamespace, false);
+       if (arr == NULL)
                ereport(ERROR,
                                (errcode(ERRCODE_DUPLICATE_OBJECT),
                                 errmsg("could not form array type name for type \"%s\"",
@@ -958,3 +941,90 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
        return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *       - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+       char       *buf;
+       char       *rangestr;
+
+       /*
+        * If the range type name contains "range" then change that to
+        * "multirange". Otherwise add "_multirange" to the end.
+        */
+       rangestr = strstr(rangeTypeName, "range");
+       if (rangestr)
+       {
+               char       *prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+               buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+       }
+       else
+               buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+       /* clip it at NAMEDATALEN-1 bytes */
+       buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+       if (SearchSysCacheExists2(TYPENAMENSP,
+                                                         CStringGetDatum(buf),
+                                                         ObjectIdGetDatum(typeNamespace)))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                errmsg("type \"%s\" already exists", buf),
+                                errdetail("Failed while creating a multirange type for type \"%s\".", rangeTypeName),
+                                errhint("You can manually specify a multirange type name using the \"multirange_type_name\" attribute")));
+
+       return pstrdup(buf);
+}
+
+/*
+ * makeUniqueTypeName
+ *             Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+       int                     i;
+       int                     namelen;
+       char            dest[NAMEDATALEN];
+
+       Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+       if (tryOriginal &&
+               !SearchSysCacheExists2(TYPENAMENSP,
+                                                          CStringGetDatum(typeName),
+                                                          ObjectIdGetDatum(typeNamespace)))
+               return pstrdup(typeName);
+
+       /*
+        * The idea is to prepend underscores as needed until we make a name that
+        * doesn't collide with anything ...
+        */
+       namelen = strlen(typeName);
+       for (i = 1; i < NAMEDATALEN - 1; i++)
+       {
+               dest[i - 1] = '_';
+               strlcpy(dest + i, typeName, NAMEDATALEN - i);
+               if (namelen + i >= NAMEDATALEN)
+                       truncate_identifier(dest, NAMEDATALEN, false);
+
+               if (!SearchSysCacheExists2(TYPENAMENSP,
+                                                                  CStringGetDatum(dest),
+                                                                  ObjectIdGetDatum(typeNamespace)))
+                       return pstrdup(dest);
+       }
+
+       return NULL;
+}
index 7c0b2c3bf0223c790274dba417e85632d7202a81..0fcd8c8f16e2bffec502aa982079e01d9e38491c 100644 (file)
@@ -107,9 +107,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid                    binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid                    binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid                    binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
 
 static void makeRangeConstructors(const char *name, Oid namespace,
                                                                  Oid rangeOid, Oid subtype);
+static void makeMultirangeConstructors(const char *name, Oid namespace,
+                                                                          Oid multirangeOid, Oid rangeOid,
+                                                                          Oid rangeArrayOid, Oid *castFuncOid);
 static Oid     findTypeInputFunction(List *procname, Oid typeOid);
 static Oid     findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid     findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -772,7 +777,8 @@ DefineDomain(CreateDomainStmt *stmt)
                typtype != TYPTYPE_COMPOSITE &&
                typtype != TYPTYPE_DOMAIN &&
                typtype != TYPTYPE_ENUM &&
-               typtype != TYPTYPE_RANGE)
+               typtype != TYPTYPE_RANGE &&
+               typtype != TYPTYPE_MULTIRANGE)
                ereport(ERROR,
                                (errcode(ERRCODE_DATATYPE_MISMATCH),
                                 errmsg("\"%s\" is not a valid base type for a domain",
@@ -1323,6 +1329,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *             Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1331,7 +1342,12 @@ DefineRange(CreateRangeStmt *stmt)
        Oid                     typeNamespace;
        Oid                     typoid;
        char       *rangeArrayName;
+       char       *multirangeTypeName = NULL;
+       char       *multirangeArrayName;
+       Oid                     multirangeNamespace = InvalidOid;
        Oid                     rangeArrayOid;
+       Oid                     multirangeOid;
+       Oid                     multirangeArrayOid;
        Oid                     rangeSubtype = InvalidOid;
        List       *rangeSubOpclassName = NIL;
        List       *rangeCollationName = NIL;
@@ -1348,6 +1364,8 @@ DefineRange(CreateRangeStmt *stmt)
        AclResult       aclresult;
        ListCell   *lc;
        ObjectAddress address;
+       ObjectAddress mltrngaddress;
+       Oid                     castFuncOid;
 
        /* Convert list of names to a name and namespace */
        typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1431,6 +1449,16 @@ DefineRange(CreateRangeStmt *stmt)
                                                 errmsg("conflicting or redundant options")));
                        rangeSubtypeDiffName = defGetQualifiedName(defel);
                }
+               else if (strcmp(defel->defname, "multirange_type_name") == 0)
+               {
+                       if (multirangeTypeName != NULL)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                                errmsg("conflicting or redundant options")));
+                       /* we can look up the subtype name immediately */
+                       multirangeNamespace = QualifiedNameGetCreationNamespace(defGetQualifiedName(defel),
+                                                                                                                                       &multirangeTypeName);
+               }
                else
                        ereport(ERROR,
                                        (errcode(ERRCODE_SYNTAX_ERROR),
@@ -1496,8 +1524,10 @@ DefineRange(CreateRangeStmt *stmt)
        /* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
        alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-       /* Allocate OID for array type */
+       /* Allocate OID for array type, its multirange, and its multirange array */
        rangeArrayOid = AssignTypeArrayOid();
+       multirangeOid = AssignTypeMultirangeOid();
+       multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
        /* Create the pg_type entry */
        address =
@@ -1536,9 +1566,75 @@ DefineRange(CreateRangeStmt *stmt)
        Assert(typoid == InvalidOid || typoid == address.objectId);
        typoid = address.objectId;
 
+       /* Create the multirange that goes with it */
+       if (multirangeTypeName)
+       {
+               Oid             old_typoid;
+
+               /*
+                * Look to see if multirange type already exists.
+                */
+               old_typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
+                                                                        CStringGetDatum(multirangeTypeName),
+                                                                        ObjectIdGetDatum(multirangeNamespace));
+
+               /*
+                * If it's not a shell, see if it's an autogenerated array type, and if so
+                * rename it out of the way.
+                */
+               if (OidIsValid(old_typoid) && get_typisdefined(old_typoid))
+               {
+                       if (!moveArrayTypeName(old_typoid, multirangeTypeName, multirangeNamespace))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                                errmsg("type \"%s\" already exists", multirangeTypeName)));
+               }
+       }
+       else
+       {
+               /* Generate multirange name automatically */
+               multirangeNamespace = typeNamespace;
+               multirangeTypeName = makeMultirangeTypeName(typeName, multirangeNamespace);
+       }
+
+       mltrngaddress =
+               TypeCreate(multirangeOid,       /* force assignment of this type OID */
+                                  multirangeTypeName,  /* type name */
+                                  multirangeNamespace, /* namespace */
+                                  InvalidOid,  /* relation oid (n/a here) */
+                                  0,                   /* relation kind (ditto) */
+                                  GetUserId(), /* owner's ID */
+                                  -1,                  /* internal size (always varlena) */
+                                  TYPTYPE_MULTIRANGE,  /* type-type (multirange type) */
+                                  TYPCATEGORY_RANGE,   /* type-category (range type) */
+                                  false,               /* multirange types are never preferred */
+                                  DEFAULT_TYPDELIM,    /* array element delimiter */
+                                  F_MULTIRANGE_IN, /* input procedure */
+                                  F_MULTIRANGE_OUT,    /* output procedure */
+                                  F_MULTIRANGE_RECV,   /* receive procedure */
+                                  F_MULTIRANGE_SEND,   /* send procedure */
+                                  InvalidOid,  /* typmodin procedure - none */
+                                  InvalidOid,  /* typmodout procedure - none */
+                                  F_MULTIRANGE_TYPANALYZE, /* analyze procedure */
+                                  InvalidOid,  /* subscript procedure - none */
+                                  InvalidOid,  /* element type ID - none */
+                                  false,               /* this is not an array type */
+                                  multirangeArrayOid,  /* array type we are about to create */
+                                  InvalidOid,  /* base type ID (only for domains) */
+                                  NULL,                /* never a default type value */
+                                  NULL,                /* no binary form available either */
+                                  false,               /* never passed by value */
+                                  alignment,   /* alignment */
+                                  'x',                 /* TOAST strategy (always extended) */
+                                  -1,                  /* typMod (Domains only) */
+                                  0,                   /* Array dimensions of typbasetype */
+                                  false,               /* Type NOT NULL */
+                                  InvalidOid); /* type's collation (ranges never have one) */
+       Assert(multirangeOid == mltrngaddress.objectId);
+
        /* Create the entry in pg_range */
        RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-                               rangeCanonical, rangeSubtypeDiff);
+                               rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
        /*
         * Create the array type that goes with it.
@@ -1580,8 +1676,54 @@ DefineRange(CreateRangeStmt *stmt)
 
        pfree(rangeArrayName);
 
+       /* Create the multirange's array type */
+
+       multirangeArrayName = makeArrayTypeName(multirangeTypeName, typeNamespace);
+
+       TypeCreate(multirangeArrayOid,  /* force assignment of this type OID */
+                          multirangeArrayName, /* type name */
+                          multirangeNamespace, /* namespace */
+                          InvalidOid,          /* relation oid (n/a here) */
+                          0,                           /* relation kind (ditto) */
+                          GetUserId(),         /* owner's ID */
+                          -1,                          /* internal size (always varlena) */
+                          TYPTYPE_BASE,        /* type-type (base type) */
+                          TYPCATEGORY_ARRAY,   /* type-category (array) */
+                          false,                       /* array types are never preferred */
+                          DEFAULT_TYPDELIM,    /* array element delimiter */
+                          F_ARRAY_IN,          /* input procedure */
+                          F_ARRAY_OUT,         /* output procedure */
+                          F_ARRAY_RECV,        /* receive procedure */
+                          F_ARRAY_SEND,        /* send procedure */
+                          InvalidOid,          /* typmodin procedure - none */
+                          InvalidOid,          /* typmodout procedure - none */
+                          F_ARRAY_TYPANALYZE,  /* analyze procedure */
+                          F_ARRAY_SUBSCRIPT_HANDLER,   /* array subscript procedure */
+                          multirangeOid,       /* element type ID */
+                          true,                        /* yes this is an array type */
+                          InvalidOid,          /* no further array type */
+                          InvalidOid,          /* base type ID */
+                          NULL,                        /* never a default type value */
+                          NULL,                        /* binary default isn't sent either */
+                          false,                       /* never passed by value */
+                          alignment,           /* alignment - same as range's */
+                          'x',                         /* ARRAY is always toastable */
+                          -1,                          /* typMod (Domains only) */
+                          0,                           /* Array dimensions of typbasetype */
+                          false,                       /* Type NOT NULL */
+                          InvalidOid);         /* typcollation */
+
        /* And create the constructor functions for this range type */
        makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype);
+       makeMultirangeConstructors(multirangeTypeName, typeNamespace,
+                                                          multirangeOid, typoid, rangeArrayOid,
+                                                          &castFuncOid);
+
+       /* Create cast from the range type to its multirange type */
+       CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+       pfree(multirangeTypeName);
+       pfree(multirangeArrayName);
 
        return address;
 }
@@ -1659,6 +1801,149 @@ makeRangeConstructors(const char *name, Oid namespace,
        }
 }
 
+/*
+ * We make a separate multirange constructor for each range type
+ * so its name can include the base type, like range constructors do.
+ * If we had an anyrangearray polymorphic type we could use it here,
+ * but since each type has its own constructor name there's no need.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+                                                  Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+                                                  Oid *castFuncOid)
+{
+       ObjectAddress myself,
+                               referenced;
+       oidvector  *argtypes;
+       Datum           allParamTypes;
+       ArrayType  *allParameterTypes;
+       Datum           paramModes;
+       ArrayType  *parameterModes;
+
+       referenced.classId = TypeRelationId;
+       referenced.objectId = multirangeOid;
+       referenced.objectSubId = 0;
+
+       /* 0-arg constructor - for empty multiranges */
+       argtypes = buildoidvector(NULL, 0);
+       myself = ProcedureCreate(name,  /* name: same as multirange type */
+                                                        namespace,
+                                                        false, /* replace */
+                                                        false, /* returns set */
+                                                        multirangeOid, /* return type */
+                                                        BOOTSTRAP_SUPERUSERID, /* proowner */
+                                                        INTERNALlanguageId,    /* language */
+                                                        F_FMGR_INTERNAL_VALIDATOR,
+                                                        "multirange_constructor0", /* prosrc */
+                                                        NULL,  /* probin */
+                                                        PROKIND_FUNCTION,
+                                                        false, /* security_definer */
+                                                        false, /* leakproof */
+                                                        false, /* isStrict */
+                                                        PROVOLATILE_IMMUTABLE, /* volatility */
+                                                        PROPARALLEL_SAFE,      /* parallel safety */
+                                                        argtypes,      /* parameterTypes */
+                                                        PointerGetDatum(NULL), /* allParameterTypes */
+                                                        PointerGetDatum(NULL), /* parameterModes */
+                                                        PointerGetDatum(NULL), /* parameterNames */
+                                                        NIL,   /* parameterDefaults */
+                                                        PointerGetDatum(NULL), /* trftypes */
+                                                        PointerGetDatum(NULL), /* proconfig */
+                                                        InvalidOid,    /* prosupport */
+                                                        1.0,   /* procost */
+                                                        0.0);  /* prorows */
+
+       /*
+        * Make the constructor internally-dependent on the multirange type so
+        * that they go away silently when the type is dropped.  Note that pg_dump
+        * depends on this choice to avoid dumping the constructors.
+        */
+       recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+       pfree(argtypes);
+
+       /*
+        * 1-arg constructor - for casts
+        *
+        * In theory we shouldn't need both this and the vararg (n-arg)
+        * constructor, but having a separate 1-arg function lets us define casts
+        * against it.
+        */
+       argtypes = buildoidvector(&rangeOid, 1);
+       myself = ProcedureCreate(name,  /* name: same as multirange type */
+                                                        namespace,
+                                                        false, /* replace */
+                                                        false, /* returns set */
+                                                        multirangeOid, /* return type */
+                                                        BOOTSTRAP_SUPERUSERID, /* proowner */
+                                                        INTERNALlanguageId,    /* language */
+                                                        F_FMGR_INTERNAL_VALIDATOR,
+                                                        "multirange_constructor1", /* prosrc */
+                                                        NULL,  /* probin */
+                                                        PROKIND_FUNCTION,
+                                                        false, /* security_definer */
+                                                        false, /* leakproof */
+                                                        true,  /* isStrict */
+                                                        PROVOLATILE_IMMUTABLE, /* volatility */
+                                                        PROPARALLEL_SAFE,      /* parallel safety */
+                                                        argtypes,      /* parameterTypes */
+                                                        PointerGetDatum(NULL), /* allParameterTypes */
+                                                        PointerGetDatum(NULL), /* parameterModes */
+                                                        PointerGetDatum(NULL), /* parameterNames */
+                                                        NIL,   /* parameterDefaults */
+                                                        PointerGetDatum(NULL), /* trftypes */
+                                                        PointerGetDatum(NULL), /* proconfig */
+                                                        InvalidOid,    /* prosupport */
+                                                        1.0,   /* procost */
+                                                        0.0);  /* prorows */
+       /* ditto */
+       recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+       pfree(argtypes);
+       *castFuncOid = myself.objectId;
+
+       /* n-arg constructor - vararg */
+       argtypes = buildoidvector(&rangeArrayOid, 1);
+       allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+       allParameterTypes = construct_array(&allParamTypes,
+                                                                               1, OIDOID,
+                                                                               sizeof(Oid), true, 'i');
+       paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+       parameterModes = construct_array(&paramModes, 1, CHAROID,
+                                                                        1, true, 'c');
+       myself = ProcedureCreate(name,  /* name: same as multirange type */
+                                                        namespace,
+                                                        false, /* replace */
+                                                        false, /* returns set */
+                                                        multirangeOid, /* return type */
+                                                        BOOTSTRAP_SUPERUSERID, /* proowner */
+                                                        INTERNALlanguageId,    /* language */
+                                                        F_FMGR_INTERNAL_VALIDATOR,
+                                                        "multirange_constructor2", /* prosrc */
+                                                        NULL,  /* probin */
+                                                        PROKIND_FUNCTION,
+                                                        false, /* security_definer */
+                                                        false, /* leakproof */
+                                                        false, /* isStrict */
+                                                        PROVOLATILE_IMMUTABLE, /* volatility */
+                                                        PROPARALLEL_SAFE,      /* parallel safety */
+                                                        argtypes,      /* parameterTypes */
+                                                        PointerGetDatum(allParameterTypes),    /* allParameterTypes */
+                                                        PointerGetDatum(parameterModes),       /* parameterModes */
+                                                        PointerGetDatum(NULL), /* parameterNames */
+                                                        NIL,   /* parameterDefaults */
+                                                        PointerGetDatum(NULL), /* trftypes */
+                                                        PointerGetDatum(NULL), /* proconfig */
+                                                        InvalidOid,    /* prosupport */
+                                                        1.0,   /* procost */
+                                                        0.0);  /* prorows */
+       /* ditto */
+       recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+       pfree(argtypes);
+       pfree(allParameterTypes);
+       pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O and other support functions for a type.
@@ -2152,6 +2437,72 @@ AssignTypeArrayOid(void)
        return type_array_oid;
 }
 
+/*
+ *     AssignTypeMultirangeOid
+ *
+ *     Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+       Oid                     type_multirange_oid;
+
+       /* Use binary-upgrade override for pg_type.oid? */
+       if (IsBinaryUpgrade)
+       {
+               if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+               type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+               binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+       }
+       else
+       {
+               Relation        pg_type = table_open(TypeRelationId, AccessShareLock);
+
+               type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+                                                                                                Anum_pg_type_oid);
+               table_close(pg_type, AccessShareLock);
+       }
+
+       return type_multirange_oid;
+}
+
+/*
+ *     AssignTypeMultirangeArrayOid
+ *
+ *     Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+       Oid                     type_multirange_array_oid;
+
+       /* Use binary-upgrade override for pg_type.oid? */
+       if (IsBinaryUpgrade)
+       {
+               if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+               type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+               binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+       }
+       else
+       {
+               Relation        pg_type = table_open(TypeRelationId, AccessShareLock);
+
+               type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+                                                                                                          Anum_pg_type_oid);
+               table_close(pg_type, AccessShareLock);
+       }
+
+       return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
index 459a33375b14dcea4b829ce23af88096fd3e5834..ca8d637e73ab6f5668cd6fbdafebb635fd4bcc63 100644 (file)
@@ -1711,7 +1711,8 @@ check_sql_fn_retval(List *queryTreeLists,
        if (fn_typtype == TYPTYPE_BASE ||
                fn_typtype == TYPTYPE_DOMAIN ||
                fn_typtype == TYPTYPE_ENUM ||
-               fn_typtype == TYPTYPE_RANGE)
+               fn_typtype == TYPTYPE_RANGE ||
+               fn_typtype == TYPTYPE_MULTIRANGE)
        {
                /*
                 * For scalar-type returns, the target list must have exactly one
index da6c3ae4b5f6246ef8887fb9bd8469648fc612d2..e33618f9744d36e0b711cd4a2a6faf57fd818e8d 100644 (file)
@@ -190,19 +190,21 @@ coerce_type(ParseState *pstate, Node *node,
        if (targetTypeId == ANYARRAYOID ||
                targetTypeId == ANYENUMOID ||
                targetTypeId == ANYRANGEOID ||
+               targetTypeId == ANYMULTIRANGEOID ||
                targetTypeId == ANYCOMPATIBLEARRAYOID ||
-               targetTypeId == ANYCOMPATIBLERANGEOID)
+               targetTypeId == ANYCOMPATIBLERANGEOID ||
+               targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
        {
                /*
                 * Assume can_coerce_type verified that implicit coercion is okay.
                 *
                 * These cases are unlike the ones above because the exposed type of
-                * the argument must be an actual array, enum, or range type.  In
-                * particular the argument must *not* be an UNKNOWN constant.  If it
-                * is, we just fall through; below, we'll call the pseudotype's input
-                * function, which will produce an error.  Also, if what we have is a
-                * domain over array, enum, or range, we have to relabel it to its
-                * base type.
+                * the argument must be an actual array, enum, range, or multirange
+                * type.  In particular the argument must *not* be an UNKNOWN
+                * constant.  If it is, we just fall through; below, we'll call the
+                * pseudotype's input function, which will produce an error.  Also, if
+                * what we have is a domain over array, enum, range, or multirange, we
+                * have to relabel it to its base type.
                 *
                 * Note: currently, we can't actually see a domain-over-enum here,
                 * since the other functions in this file will not match such a
@@ -1570,8 +1572,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *       which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *       which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *       multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *       the array element type and/or range subtype must be the same as each
  *       other and the same as the ANYELEMENT type.
@@ -1586,8 +1588,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *       to a common supertype (chosen as per select_common_type's rules).
  *       ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *       common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *       or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *       included while making the choice of common supertype.
+ *       or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *       types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *       type over the common supertype (which might not be the same array type
  *       as any of the original arrays).
@@ -1595,6 +1597,10 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *       (after domain flattening), since we have no preference rule that would
  *       let us choose one over another.  Furthermore, that range's subtype
  *       must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *       type (after domain flattening), since we have no preference rule that would
+ *       let us choose one over another.  Furthermore, that multirange's range's
+ *       subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1603,7 +1609,9 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1621,8 +1629,12 @@ check_generic_type_consistency(const Oid *actual_arg_types,
        Oid                     elem_typeid = InvalidOid;
        Oid                     array_typeid = InvalidOid;
        Oid                     range_typeid = InvalidOid;
+       Oid                     multirange_typeid = InvalidOid;
        Oid                     anycompatible_range_typeid = InvalidOid;
        Oid                     anycompatible_range_typelem = InvalidOid;
+       Oid                     anycompatible_multirange_typeid = InvalidOid;
+       Oid                     anycompatible_multirange_typelem = InvalidOid;
+       Oid                     range_typelem = InvalidOid;
        bool            have_anynonarray = false;
        bool            have_anyenum = false;
        bool            have_anycompatible_nonarray = false;
@@ -1671,6 +1683,15 @@ check_generic_type_consistency(const Oid *actual_arg_types,
                                return false;
                        range_typeid = actual_type;
                }
+               else if (decl_type == ANYMULTIRANGEOID)
+               {
+                       if (actual_type == UNKNOWNOID)
+                               continue;
+                       actual_type = getBaseType(actual_type); /* flatten domains */
+                       if (OidIsValid(multirange_typeid) && actual_type != multirange_typeid)
+                               return false;
+                       multirange_typeid = actual_type;
+               }
                else if (decl_type == ANYCOMPATIBLEOID ||
                                 decl_type == ANYCOMPATIBLENONARRAYOID)
                {
@@ -1715,6 +1736,45 @@ check_generic_type_consistency(const Oid *actual_arg_types,
                                anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
                        }
                }
+               else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+               {
+                       if (actual_type == UNKNOWNOID)
+                               continue;
+                       actual_type = getBaseType(actual_type); /* flatten domains */
+                       if (OidIsValid(anycompatible_multirange_typeid))
+                       {
+                               /* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+                               if (anycompatible_multirange_typeid != actual_type)
+                                       return false;
+                       }
+                       else
+                       {
+                               anycompatible_multirange_typeid = actual_type;
+                               anycompatible_multirange_typelem = get_multirange_range(actual_type);
+                               if (!OidIsValid(anycompatible_multirange_typelem))
+                                       return false;   /* not a multirange type */
+
+                               if (OidIsValid(anycompatible_range_typeid))
+                               {
+                                       /*
+                                        * ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE
+                                        * arguments must match
+                                        */
+                                       if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+                                               return false;
+                               }
+                               else
+                               {
+                                       anycompatible_range_typeid = anycompatible_multirange_typelem;
+                                       anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+                                       if (!OidIsValid(anycompatible_range_typelem))
+                                               return false;   /* not a range type */
+                               }
+                               /* collect the subtype for common-supertype choice */
+                               anycompatible_actual_types[n_anycompatible_args++] =
+                                       anycompatible_range_typelem;
+                       }
+               }
        }
 
        /* Get the element type based on the array type, if we have one */
@@ -1761,8 +1821,6 @@ check_generic_type_consistency(const Oid *actual_arg_types,
        /* Get the element type based on the range type, if we have one */
        if (OidIsValid(range_typeid))
        {
-               Oid                     range_typelem;
-
                range_typelem = get_range_subtype(range_typeid);
                if (!OidIsValid(range_typelem))
                        return false;           /* should be a range, but isn't */
@@ -1781,6 +1839,45 @@ check_generic_type_consistency(const Oid *actual_arg_types,
                }
        }
 
+       /* Get the element type based on the multirange type, if we have one */
+       if (OidIsValid(multirange_typeid))
+       {
+               Oid                     multirange_typelem;
+
+               multirange_typelem = get_multirange_range(multirange_typeid);
+               if (!OidIsValid(multirange_typelem))
+                       return false;           /* should be a multirange, but isn't */
+
+               if (!OidIsValid(range_typeid))
+               {
+                       /*
+                        * If we don't have a range type yet, use the one we just got
+                        */
+                       range_typeid = multirange_typelem;
+                       range_typelem = get_range_subtype(multirange_typelem);
+                       if (!OidIsValid(range_typelem))
+                               return false;   /* should be a range, but isn't */
+               }
+               else if (multirange_typelem != range_typeid)
+               {
+                       /* otherwise, they better match */
+                       return false;
+               }
+
+               if (!OidIsValid(elem_typeid))
+               {
+                       /*
+                        * If we don't have an element type yet, use the one we just got
+                        */
+                       elem_typeid = range_typelem;
+               }
+               else if (range_typelem != elem_typeid)
+               {
+                       /* otherwise, they better match */
+                       return false;
+               }
+       }
+
        if (have_anynonarray)
        {
                /* require the element type to not be an array or domain over array */
@@ -1819,8 +1916,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
                }
 
                /*
-                * the anycompatible type must exactly match the range element type,
-                * if we were able to identify one
+                * The anycompatible type must exactly match the range element type,
+                * if we were able to identify one. This checks compatibility for
+                * anycompatiblemultirange too since that also sets
+                * anycompatible_range_typelem above.
                 */
                if (OidIsValid(anycompatible_range_typelem) &&
                        anycompatible_range_typelem != anycompatible_typeid)
@@ -1859,21 +1958,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *       argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *       argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *       use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *       argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *       actual type, range type or multirange type as the function's return
+ *       type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *       ANYMULTIRANGE, use the argument's actual type as the function's return
+ *       type. Or if any argument is ANYRANGE, use its multirange type as the
+ *       function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *       at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *       return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *       select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *       (We have no way to select a specific range type if the arguments don't
+ *       include ANYRANGE.)
  *       (alone or in combination with plain ANYELEMENT), we add the extra
  *       condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *       we add the extra condition that the ANYELEMENT type must not be an array.
  *       (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *       is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *       ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *       of those arguments (or their element types/subtypes, for array and range
  *       inputs), and then coercing all those arguments to the common supertype,
@@ -1927,10 +2032,15 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
        Oid                     elem_typeid = InvalidOid;
        Oid                     array_typeid = InvalidOid;
        Oid                     range_typeid = InvalidOid;
+       Oid                     multirange_typeid = InvalidOid;
        Oid                     anycompatible_typeid = InvalidOid;
        Oid                     anycompatible_array_typeid = InvalidOid;
        Oid                     anycompatible_range_typeid = InvalidOid;
        Oid                     anycompatible_range_typelem = InvalidOid;
+       Oid                     anycompatible_multirange_typeid = InvalidOid;
+       Oid                     anycompatible_multirange_typelem = InvalidOid;
+       Oid                     range_typelem;
+       Oid                     multirange_typelem;
        bool            have_anynonarray = (rettype == ANYNONARRAYOID);
        bool            have_anyenum = (rettype == ANYENUMOID);
        bool            have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -2015,6 +2125,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                                                                   format_type_be(actual_type))));
                        range_typeid = actual_type;
                }
+               else if (decl_type == ANYMULTIRANGEOID)
+               {
+                       n_poly_args++;
+                       if (actual_type == UNKNOWNOID)
+                       {
+                               have_poly_unknowns = true;
+                               continue;
+                       }
+                       if (allow_poly && decl_type == actual_type)
+                               continue;               /* no new information here */
+                       actual_type = getBaseType(actual_type); /* flatten domains */
+                       if (OidIsValid(multirange_typeid) && actual_type != multirange_typeid)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("arguments declared \"anymultirange\" are not all alike"),
+                                                errdetail("%s versus %s",
+                                                                  format_type_be(multirange_typeid),
+                                                                  format_type_be(actual_type))));
+                       multirange_typeid = actual_type;
+               }
                else if (decl_type == ANYCOMPATIBLEOID ||
                                 decl_type == ANYCOMPATIBLENONARRAYOID)
                {
@@ -2083,6 +2213,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                                anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
                        }
                }
+               else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+               {
+                       have_poly_anycompatible = true;
+                       if (actual_type == UNKNOWNOID)
+                               continue;
+                       if (allow_poly && decl_type == actual_type)
+                               continue;               /* no new information here */
+                       actual_type = getBaseType(actual_type); /* flatten domains */
+                       if (OidIsValid(anycompatible_multirange_typeid))
+                       {
+                               /* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+                               if (anycompatible_multirange_typeid != actual_type)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+                                                        errdetail("%s versus %s",
+                                                                          format_type_be(anycompatible_multirange_typeid),
+                                                                          format_type_be(actual_type))));
+                       }
+                       else
+                       {
+                               anycompatible_multirange_typeid = actual_type;
+                               anycompatible_multirange_typelem = get_multirange_range(actual_type);
+                               anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+                               if (!OidIsValid(anycompatible_multirange_typelem))
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                        errmsg("argument declared %s is not a multirange type but type %s",
+                                                                       "anycompatiblemultirange",
+                                                                       format_type_be(actual_type))));
+                               /* collect the subtype for common-supertype choice */
+                               anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+                       }
+               }
        }
 
        /*
@@ -2151,8 +2315,6 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                /* Get the element type based on the range type, if we have one */
                if (OidIsValid(range_typeid))
                {
-                       Oid                     range_typelem;
-
                        range_typelem = get_range_subtype(range_typeid);
                        if (!OidIsValid(range_typelem))
                                ereport(ERROR,
@@ -2181,6 +2343,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                                                                   format_type_be(elem_typeid))));
                        }
                }
+               else
+                       range_typelem = InvalidOid;
+
+               /* Get the element type based on the multirange type, if we have one */
+               if (OidIsValid(multirange_typeid))
+               {
+                       multirange_typelem = get_multirange_range(multirange_typeid);
+                       if (!OidIsValid(multirange_typelem))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("argument declared %s is not a multirange type but type %s",
+                                                               "anymultirange",
+                                                               format_type_be(multirange_typeid))));
+
+                       if (!OidIsValid(range_typeid))
+                       {
+                               /*
+                                * If we don't have a range type yet, use the one we just got
+                                */
+                               range_typeid = multirange_typelem;
+                               range_typelem = get_range_subtype(range_typeid);
+                       }
+                       else if (multirange_typelem != range_typeid)
+                       {
+                               /* otherwise, they better match */
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("argument declared %s is not consistent with argument declared %s",
+                                                               "anymultirange", "anyrange"),
+                                                errdetail("%s versus %s",
+                                                                  format_type_be(multirange_typeid),
+                                                                  format_type_be(range_typeid))));
+                       }
+
+                       if (!OidIsValid(elem_typeid))
+                       {
+                               /*
+                                * if we don't have an element type yet, use the one we just got
+                                */
+                               elem_typeid = range_typelem;
+                       }
+                       else if (range_typelem != elem_typeid)
+                       {
+                               /* otherwise, they better match */
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                errmsg("argument declared %s is not consistent with argument declared %s",
+                                                               "anymultirange", "anyelement"),
+                                                errdetail("%s versus %s",
+                                                                  format_type_be(multirange_typeid),
+                                                                  format_type_be(elem_typeid))));
+                       }
+               }
+               else
+                       multirange_typelem = InvalidOid;
 
                if (!OidIsValid(elem_typeid))
                {
@@ -2189,6 +2406,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                                elem_typeid = ANYELEMENTOID;
                                array_typeid = ANYARRAYOID;
                                range_typeid = ANYRANGEOID;
+                               multirange_typeid = ANYMULTIRANGEOID;
                        }
                        else
                        {
@@ -2288,6 +2506,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                                anycompatible_typeid = ANYCOMPATIBLEOID;
                                anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
                                anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+                               anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
                        }
                        else
                        {
@@ -2319,6 +2538,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                                declared_arg_types[j] = anycompatible_array_typeid;
                        else if (decl_type == ANYCOMPATIBLERANGEOID)
                                declared_arg_types[j] = anycompatible_range_typeid;
+                       else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+                               declared_arg_types[j] = anycompatible_multirange_typeid;
                }
        }
 
@@ -2369,6 +2590,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                                }
                                declared_arg_types[j] = range_typeid;
                        }
+                       else if (decl_type == ANYMULTIRANGEOID)
+                       {
+                               if (!OidIsValid(multirange_typeid))
+                               {
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                                        errmsg("could not find multirange type for data type %s",
+                                                                       format_type_be(elem_typeid))));
+                               }
+                               declared_arg_types[j] = multirange_typeid;
+                       }
                }
        }
 
@@ -2405,6 +2637,22 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                return range_typeid;
        }
 
+       /* if we return ANYMULTIRANGE use the appropriate argument type */
+       if (rettype == ANYMULTIRANGEOID)
+       {
+               if (!OidIsValid(multirange_typeid))
+               {
+                       if (OidIsValid(range_typeid))
+                               multirange_typeid = get_range_multirange(range_typeid);
+                       else
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                                errmsg("could not find multirange type for data type %s",
+                                                               format_type_be(elem_typeid))));
+               }
+               return multirange_typeid;
+       }
+
        /* if we return ANYCOMPATIBLE use the appropriate type */
        if (rettype == ANYCOMPATIBLEOID ||
                rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2439,6 +2687,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
                return anycompatible_range_typeid;
        }
 
+       /* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+       if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+       {
+               /* this error is unreachable if the function signature is valid: */
+               if (!OidIsValid(anycompatible_multirange_typeid))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg_internal("could not identify anycompatiblemultirange type")));
+               return anycompatible_multirange_typeid;
+       }
+
        /* we don't return a generic type; send back the original return type */
        return rettype;
 }
@@ -2456,20 +2715,38 @@ check_valid_polymorphic_signature(Oid ret_type,
                                                                  const Oid *declared_arg_types,
                                                                  int nargs)
 {
-       if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+       if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
        {
                /*
-                * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-                * several range types with the same element type to use.  Likewise
-                * for ANYCOMPATIBLERANGE.
+                * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE
+                * input, else we can't tell which of several range types with the
+                * same element type to use.
                 */
                for (int i = 0; i < nargs; i++)
                {
-                       if (declared_arg_types[i] == ret_type)
+                       if (declared_arg_types[i] == ANYRANGEOID ||
+                               declared_arg_types[i] == ANYMULTIRANGEOID)
                                return NULL;    /* OK */
                }
-               return psprintf(_("A result of type %s requires at least one input of type %s."),
-                                               format_type_be(ret_type), format_type_be(ret_type));
+               return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+                                               format_type_be(ret_type));
+       }
+       else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+       {
+               /*
+                * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an
+                * ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE input, else we can't
+                * tell which of several range types with the same element type to
+                * use.
+                */
+               for (int i = 0; i < nargs; i++)
+               {
+                       if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+                               declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+                               return NULL;    /* OK */
+               }
+               return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+                                               format_type_be(ret_type));
        }
        else if (IsPolymorphicTypeFamily1(ret_type))
        {
@@ -2480,7 +2757,7 @@ check_valid_polymorphic_signature(Oid ret_type,
                                return NULL;    /* OK */
                }
                /* Keep this list in sync with IsPolymorphicTypeFamily1! */
-               return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+               return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
                                                format_type_be(ret_type));
        }
        else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2632,6 +2909,11 @@ IsBinaryCoercible(Oid srctype, Oid targettype)
                if (type_is_range(srctype))
                        return true;
 
+       /* Also accept any multirange type as coercible to ANMULTIYRANGE */
+       if (targettype == ANYMULTIRANGEOID || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+               if (type_is_multirange(srctype))
+                       return true;
+
        /* Also accept any composite type as coercible to RECORD */
        if (targettype == RECORDOID)
                if (ISCOMPLEX(srctype))
index ce09ad73754e05bd595a97eac502a85d246a2e32..82732146d3deb56f9012cad00ca260fe5016919b 100644 (file)
@@ -60,6 +60,8 @@ OBJS = \
        mac8.o \
        mcxtfuncs.o \
        misc.o \
+       multirangetypes.o \
+       multirangetypes_selfuncs.o \
        name.o \
        network.o \
        network_gist.o \
diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
new file mode 100644 (file)
index 0000000..a4dc439
--- /dev/null
@@ -0,0 +1,2679 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes.c
+ *       I/O functions, operators, and support functions for multirange types.
+ *
+ * The stored (serialized) format of a multirange value is:
+ *
+ *     12 bytes: MultirangeType struct including varlena header, multirange
+ *                       type's OID and the number of ranges in the multirange.
+ *     4 * (rangesCount - 1) bytes: 32-bit items pointing to the each range
+ *                                                              in the multirange starting from
+ *                                                              the second one.
+ *     1 * rangesCount bytes : 8-bit flags for each range in the multirange
+ *     The rest of the multirange are range bound values pointed by multirange
+ *     items.
+ *
+ *     Majority of items contain lengths of corresponding range bound values.
+ *     Thanks to that items are typically low numbers.  This makes multiranges
+ *     compression-friendly.  Every MULTIRANGE_ITEM_OFFSET_STRIDE item contains
+ *     an offset of the corresponding range bound values.  That allows fast lookups
+ *     for a particular range index.  Offsets are counted starting from the end of
+ *     flags aligned to the bound type.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       src/backend/utils/adt/multirangetypes.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/tupmacs.h"
+#include "common/hashfn.h"
+#include "lib/stringinfo.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+       TypeCacheEntry *typcache;       /* multirange type's typcache entry */
+       FmgrInfo        typioproc;              /* range type's I/O proc */
+       Oid                     typioparam;             /* range type's I/O parameter */
+} MultirangeIOData;
+
+typedef enum
+{
+       MULTIRANGE_BEFORE_RANGE,
+       MULTIRANGE_IN_RANGE,
+       MULTIRANGE_IN_RANGE_ESCAPED,
+       MULTIRANGE_IN_RANGE_QUOTED,
+       MULTIRANGE_IN_RANGE_QUOTED_ESCAPED,
+       MULTIRANGE_AFTER_RANGE,
+       MULTIRANGE_FINISHED,
+} MultirangeParseState;
+
+/*
+ * Macros for accessing past MultirangeType parts of multirange: items, flags
+ * and boundaries.
+ */
+#define MultirangeGetItemsPtr(mr) ((uint32 *) ((Pointer) (mr) + \
+       sizeof(MultirangeType)))
+#define MultirangeGetFlagsPtr(mr) ((uint8 *) ((Pointer) (mr) + \
+       sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32)))
+#define MultirangeGetBoundariesPtr(mr, align) ((Pointer) (mr) + \
+       att_align_nominal(sizeof(MultirangeType) + \
+               ((mr)->rangeCount - 1) * sizeof(uint32) + \
+               (mr)->rangeCount * sizeof(uint8), (align)))
+
+#define MULTIRANGE_ITEM_OFF_BIT 0x80000000
+#define MULTIRANGE_ITEM_GET_OFFLEN(item) ((item) & 0x7FFFFFFF)
+#define MULTIRANGE_ITEM_HAS_OFF(item) ((item) & MULTIRANGE_ITEM_OFF_BIT)
+#define MULTIRANGE_ITEM_OFFSET_STRIDE 4
+
+typedef int (*multirange_bsearch_comparison) (TypeCacheEntry *typcache,
+                                                                                         RangeBound *lower,
+                                                                                         RangeBound *upper,
+                                                                                         void *key,
+                                                                                         bool *match);
+
+static MultirangeIOData *get_multirange_io_data(FunctionCallInfo fcinfo,
+                                                                                               Oid mltrngtypid,
+                                                                                               IOFuncSelector func);
+static int32 multirange_canonicalize(TypeCacheEntry *rangetyp,
+                                                                        int32 input_range_count,
+                                                                        RangeType **ranges);
+
+/*
+ *----------------------------------------------------------
+ * I/O FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/*
+ * Converts string to multirange.
+ *
+ * We expect curly brackets to bound the list, with zero or more ranges
+ * separated by commas.  We accept whitespace anywhere: before/after our
+ * brackets and around the commas.  Ranges can be the empty literal or some
+ * stuff inside parens/brackets.  Mostly we delegate parsing the individual
+ * range contents to range_in, but we have to detect quoting and
+ * backslash-escaping which can happen for range bounds.  Backslashes can
+ * escape something inside or outside a quoted string, and a quoted string
+ * can escape quote marks with either backslashes or double double-quotes.
+ */
+Datum
+multirange_in(PG_FUNCTION_ARGS)
+{
+       char       *input_str = PG_GETARG_CSTRING(0);
+       Oid                     mltrngtypoid = PG_GETARG_OID(1);
+       Oid                     typmod = PG_GETARG_INT32(2);
+       TypeCacheEntry *rangetyp;
+       int32           ranges_seen = 0;
+       int32           range_count = 0;
+       int32           range_capacity = 8;
+       RangeType  *range;
+       RangeType **ranges = palloc(range_capacity * sizeof(RangeType *));
+       MultirangeIOData *cache;
+       MultirangeType *ret;
+       MultirangeParseState parse_state;
+       const char *ptr = input_str;
+       const char *range_str = NULL;
+       int32           range_str_len;
+       char       *range_str_copy;
+
+       cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+       rangetyp = cache->typcache->rngtype;
+
+       /* consume whitespace */
+       while (*ptr != '\0' && isspace((unsigned char) *ptr))
+               ptr++;
+
+       if (*ptr == '{')
+               ptr++;
+       else
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                errmsg("malformed multirange literal: \"%s\"",
+                                               input_str),
+                                errdetail("Missing left bracket.")));
+
+       /* consume ranges */
+       parse_state = MULTIRANGE_BEFORE_RANGE;
+       for (; parse_state != MULTIRANGE_FINISHED; ptr++)
+       {
+               char            ch = *ptr;
+
+               if (ch == '\0')
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                        errmsg("malformed multirange literal: \"%s\"",
+                                                       input_str),
+                                        errdetail("Unexpected end of input.")));
+
+               /* skip whitespace */
+               if (isspace((unsigned char) ch))
+                       continue;
+
+               switch (parse_state)
+               {
+                       case MULTIRANGE_BEFORE_RANGE:
+                               if (ch == '[' || ch == '(')
+                               {
+                                       range_str = ptr;
+                                       parse_state = MULTIRANGE_IN_RANGE;
+                               }
+                               else if (ch == '}' && ranges_seen == 0)
+                                       parse_state = MULTIRANGE_FINISHED;
+                               else if (pg_strncasecmp(ptr, RANGE_EMPTY_LITERAL,
+                                                                               strlen(RANGE_EMPTY_LITERAL)) == 0)
+                               {
+                                       ranges_seen++;
+                                       /* nothing to do with an empty range */
+                                       ptr += strlen(RANGE_EMPTY_LITERAL) - 1;
+                                       parse_state = MULTIRANGE_AFTER_RANGE;
+                               }
+                               else
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                                        errmsg("malformed multirange literal: \"%s\"",
+                                                                       input_str),
+                                                        errdetail("Expected range start.")));
+                               break;
+                       case MULTIRANGE_IN_RANGE:
+                               if (ch == '"')
+                                       parse_state = MULTIRANGE_IN_RANGE_QUOTED;
+                               else if (ch == '\\')
+                                       parse_state = MULTIRANGE_IN_RANGE_ESCAPED;
+                               else if (ch == ']' || ch == ')')
+                               {
+                                       range_str_len = ptr - range_str + 1;
+                                       range_str_copy = pnstrdup(range_str, range_str_len);
+                                       if (range_capacity == range_count)
+                                       {
+                                               range_capacity *= 2;
+                                               ranges = (RangeType **)
+                                                       repalloc(ranges, range_capacity * sizeof(RangeType *));
+                                       }
+                                       ranges_seen++;
+                                       range = DatumGetRangeTypeP(InputFunctionCall(&cache->typioproc,
+                                                                                                                                range_str_copy,
+                                                                                                                                cache->typioparam,
+                                                                                                                                typmod));
+                                       if (!RangeIsEmpty(range))
+                                               ranges[range_count++] = range;
+                                       parse_state = MULTIRANGE_AFTER_RANGE;
+                               }
+                               else
+                                        /* include it in range_str */ ;
+                               break;
+                       case MULTIRANGE_IN_RANGE_ESCAPED:
+                               /* include it in range_str */
+                               parse_state = MULTIRANGE_IN_RANGE;
+                               break;
+                       case MULTIRANGE_IN_RANGE_QUOTED:
+                               if (ch == '"')
+                                       if (*(ptr + 1) == '"')
+                                       {
+                                               /* two quote marks means an escaped quote mark */
+                                               ptr++;
+                                       }
+                                       else
+                                               parse_state = MULTIRANGE_IN_RANGE;
+                               else if (ch == '\\')
+                                       parse_state = MULTIRANGE_IN_RANGE_QUOTED_ESCAPED;
+                               else
+                                        /* include it in range_str */ ;
+                               break;
+                       case MULTIRANGE_AFTER_RANGE:
+                               if (ch == ',')
+                                       parse_state = MULTIRANGE_BEFORE_RANGE;
+                               else if (ch == '}')
+                                       parse_state = MULTIRANGE_FINISHED;
+                               else
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                                        errmsg("malformed multirange literal: \"%s\"",
+                                                                       input_str),
+                                                        errdetail("Expected comma or end of multirange.")));
+                               break;
+                       case MULTIRANGE_IN_RANGE_QUOTED_ESCAPED:
+                               /* include it in range_str */
+                               parse_state = MULTIRANGE_IN_RANGE_QUOTED;
+                               break;
+                       default:
+                               elog(ERROR, "unknown parse state: %d", parse_state);
+               }
+       }
+
+       /* consume whitespace */
+       while (*ptr != '\0' && isspace((unsigned char) *ptr))
+               ptr++;
+
+       if (*ptr != '\0')
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                errmsg("malformed multirange literal: \"%s\"",
+                                               input_str),
+                                errdetail("Junk after right bracket.")));
+
+       ret = make_multirange(mltrngtypoid, rangetyp, range_count, ranges);
+       PG_RETURN_MULTIRANGE_P(ret);
+}
+
+Datum
+multirange_out(PG_FUNCTION_ARGS)
+{
+       MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0);
+       Oid                     mltrngtypoid = MultirangeTypeGetOid(multirange);
+       MultirangeIOData *cache;
+       StringInfoData buf;
+       RangeType  *range;
+       char       *rangeStr;
+       int32           range_count;
+       int32           i;
+       RangeType **ranges;
+
+       cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+       initStringInfo(&buf);
+
+       appendStringInfoChar(&buf, '{');
+
+       multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+       for (i = 0; i < range_count; i++)
+       {
+               if (i > 0)
+                       appendStringInfoChar(&buf, ',');
+               range = ranges[i];
+               rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+               appendStringInfoString(&buf, rangeStr);
+       }
+
+       appendStringInfoChar(&buf, '}');
+
+       PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+Datum
+multirange_recv(PG_FUNCTION_ARGS)
+{
+       StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
+       Oid                     mltrngtypoid = PG_GETARG_OID(1);
+       int32           typmod = PG_GETARG_INT32(2);
+       MultirangeIOData *cache;
+       uint32          range_count;
+       RangeType **ranges;
+       MultirangeType *ret;
+       StringInfoData tmpbuf;
+
+       cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+       range_count = pq_getmsgint(buf, 4);
+       ranges = palloc(range_count * sizeof(RangeType *));
+
+       initStringInfo(&tmpbuf);
+       for (int i = 0; i < range_count; i++)
+       {
+               uint32          range_len = pq_getmsgint(buf, 4);
+               const char *range_data = pq_getmsgbytes(buf, range_len);
+
+               resetStringInfo(&tmpbuf);
+               appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+               ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+                                                                                                                  &tmpbuf,
+                                                                                                                  cache->typioparam,
+                                                                                                                  typmod));
+       }
+       pfree(tmpbuf.data);
+
+       pq_getmsgend(buf);
+
+       ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+                                                 range_count, ranges);
+       PG_RETURN_MULTIRANGE_P(ret);
+}
+
+Datum
+multirange_send(PG_FUNCTION_ARGS)
+{
+       MultirangeType *multirange = PG_GETARG_MULTIRANGE_P(0);
+       Oid                     mltrngtypoid = MultirangeTypeGetOid(multirange);
+       StringInfo      buf = makeStringInfo();
+       RangeType **ranges;
+       int32           range_count;
+       MultirangeIOData *cache;
+
+       cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+       /* construct output */
+       pq_begintypsend(buf);
+
+       pq_sendint32(buf, multirange->rangeCount);
+
+       multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+       for (int i = 0; i < range_count; i++)
+       {
+               Datum           range;
+
+               range = RangeTypePGetDatum(ranges[i]);
+               range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+               pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+               pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+       }
+
+       PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * get_multirange_io_data: get cached information needed for multirange type I/O
+ *
+ * The multirange I/O functions need a bit more cached info than other multirange
+ * functions, so they store a MultirangeIOData struct in fn_extra, not just a
+ * pointer to a type cache entry.
+ */
+static MultirangeIOData *
+get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid, IOFuncSelector func)
+{
+       MultirangeIOData *cache = (MultirangeIOData *) fcinfo->flinfo->fn_extra;
+
+       if (cache == NULL || cache->typcache->type_id != mltrngtypid)
+       {
+               Oid                     typiofunc;
+               int16           typlen;
+               bool            typbyval;
+               char            typalign;
+               char            typdelim;
+
+               cache = (MultirangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+                                                                                                               sizeof(MultirangeIOData));
+               cache->typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO);
+               if (cache->typcache->rngtype == NULL)
+                       elog(ERROR, "type %u is not a multirange type", mltrngtypid);
+
+               /* get_type_io_data does more than we need, but is convenient */
+               get_type_io_data(cache->typcache->rngtype->type_id,
+                                                func,
+                                                &typlen,
+                                                &typbyval,
+                                                &typalign,
+                                                &typdelim,
+                                                &cache->typioparam,
+                                                &typiofunc);
+
+               if (!OidIsValid(typiofunc))
+               {
+                       /* this could only happen for receive or send */
+                       if (func == IOFunc_receive)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                                                errmsg("no binary input function available for type %s",
+                                                               format_type_be(cache->typcache->rngtype->type_id))));
+                       else
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                                                errmsg("no binary output function available for type %s",
+                                                               format_type_be(cache->typcache->rngtype->type_id))));
+               }
+               fmgr_info_cxt(typiofunc, &cache->typioproc,
+                                         fcinfo->flinfo->fn_mcxt);
+
+               fcinfo->flinfo->fn_extra = (void *) cache;
+       }
+
+       return cache;
+}
+
+/*
+ * Converts a list of arbitrary ranges into a list that is sorted and merged.
+ * Changes the contents of `ranges`.
+ *
+ * Returns the number of slots actually used, which may be less than
+ * input_range_count but never more.
+ *
+ * We assume that no input ranges are null, but empties are okay.
+ */
+static int32
+multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
+                                               RangeType **ranges)
+{
+       RangeType  *lastRange = NULL;
+       RangeType  *currentRange;
+       int32           i;
+       int32           output_range_count = 0;
+
+       /* Sort the ranges so we can find the ones that overlap/meet. */
+       qsort_arg(ranges, input_range_count, sizeof(RangeType *), range_compare,
+                         rangetyp);
+
+       /* Now merge where possible: */
+       for (i = 0; i < input_range_count; i++)
+       {
+               currentRange = ranges[i];
+               if (RangeIsEmpty(currentRange))
+                       continue;
+
+               if (lastRange == NULL)
+               {
+                       ranges[output_range_count++] = lastRange = currentRange;
+                       continue;
+               }
+
+               /*
+                * range_adjacent_internal gives true if *either* A meets B or B meets
+                * A, which is not quite want we want, but we rely on the sorting
+                * above to rule out B meets A ever happening.
+                */
+               if (range_adjacent_internal(rangetyp, lastRange, currentRange))
+               {
+                       /* The two ranges touch (without overlap), so merge them: */
+                       ranges[output_range_count - 1] = lastRange =
+                               range_union_internal(rangetyp, lastRange, currentRange, false);
+               }
+               else if (range_before_internal(rangetyp, lastRange, currentRange))
+               {
+                       /* There's a gap, so make a new entry: */
+                       lastRange = ranges[output_range_count] = currentRange;
+                       output_range_count++;
+               }
+               else
+               {
+                       /* They must overlap, so merge them: */
+                       ranges[output_range_count - 1] = lastRange =
+                               range_union_internal(rangetyp, lastRange, currentRange, true);
+               }
+       }
+
+       return output_range_count;
+}
+
+/*
+ *----------------------------------------------------------
+ * SUPPORT FUNCTIONS
+ *
+ *      These functions aren't in pg_proc, but are useful for
+ *      defining new generic multirange functions in C.
+ *----------------------------------------------------------
+ */
+
+/*
+ * multirange_get_typcache: get cached information about a multirange type
+ *
+ * This is for use by multirange-related functions that follow the convention
+ * of using the fn_extra field as a pointer to the type cache entry for
+ * the multirange type.  Functions that need to cache more information than
+ * that must fend for themselves.
+ */
+TypeCacheEntry *
+multirange_get_typcache(FunctionCallInfo fcinfo, Oid mltrngtypid)
+{
+       TypeCacheEntry *typcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+
+       if (typcache == NULL ||
+               typcache->type_id != mltrngtypid)
+       {
+               typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO);
+               if (typcache->rngtype == NULL)
+                       elog(ERROR, "type %u is not a multirange type", mltrngtypid);
+               fcinfo->flinfo->fn_extra = (void *) typcache;
+       }
+
+       return typcache;
+}
+
+
+/*
+ * Estimate size occupied by serialized multirage.
+ */
+static Size
+multirange_size_estimate(TypeCacheEntry *rangetyp, int32 range_count,
+                                                RangeType **ranges)
+{
+       char            elemalign = rangetyp->rngelemtype->typalign;
+       Size            size;
+       int32           i;
+
+       /*
+        * Count space for MultirangeType struct, items and flags.
+        */
+       size = att_align_nominal(sizeof(MultirangeType) +
+                                                        Max(range_count - 1, 0) * sizeof(uint32) +
+                                                        range_count * sizeof(uint8), elemalign);
+
+       /* Count space for range bounds */
+       for (i = 0; i < range_count; i++)
+               size += att_align_nominal(VARSIZE(ranges[i]) -
+                                                                 sizeof(RangeType) -
+                                                                 sizeof(char), elemalign);
+
+       return size;
+}
+
+/*
+ * Write multirange data into pre-allocated space.
+ */
+static void
+write_multirange_data(MultirangeType *multirange, TypeCacheEntry *rangetyp,
+                                         int32 range_count, RangeType **ranges)
+{
+       uint32     *items;
+       uint32          prev_offset = 0;
+       uint8      *flags;
+       int32           i;
+       Pointer         begin,
+                               ptr;
+       char            elemalign = rangetyp->rngelemtype->typalign;
+
+       items = MultirangeGetItemsPtr(multirange);
+       flags = MultirangeGetFlagsPtr(multirange);
+       ptr = begin = MultirangeGetBoundariesPtr(multirange, elemalign);
+       for (i = 0; i < range_count; i++)
+       {
+               uint32          len;
+
+               if (i > 0)
+               {
+                       /*
+                        * Every range, except the first one, has an item.  Every
+                        * MULTIRANGE_ITEM_OFFSET_STRIDE item contains an offset, others
+                        * contain lengths.
+                        */
+                       items[i - 1] = ptr - begin;
+                       if ((i % MULTIRANGE_ITEM_OFFSET_STRIDE) != 0)
+                               items[i - 1] -= prev_offset;
+                       else
+                               items[i - 1] |= MULTIRANGE_ITEM_OFF_BIT;
+                       prev_offset = ptr - begin;
+               }
+               flags[i] = *((Pointer) ranges[i] + VARSIZE(ranges[i]) - sizeof(char));
+               len = VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char);
+               memcpy(ptr, (Pointer) (ranges[i] + 1), len);
+               ptr += att_align_nominal(len, elemalign);
+       }
+}
+
+
+/*
+ * This serializes the multirange from a list of non-null ranges.  It also
+ * sorts the ranges and merges any that touch.  The ranges should already be
+ * detoasted, and there should be no NULLs.  This should be used by most
+ * callers.
+ *
+ * Note that we may change the `ranges` parameter (the pointers, but not
+ * any already-existing RangeType contents).
+ */
+MultirangeType *
+make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count,
+                               RangeType **ranges)
+{
+       MultirangeType *multirange;
+       Size            size;
+
+       /* Sort and merge input ranges. */
+       range_count = multirange_canonicalize(rangetyp, range_count, ranges);
+
+       /* Note: zero-fill is required here, just as in heap tuples */
+       size = multirange_size_estimate(rangetyp, range_count, ranges);
+       multirange = palloc0(size);
+       SET_VARSIZE(multirange, size);
+
+       /* Now fill in the datum */
+       multirange->multirangetypid = mltrngtypoid;
+       multirange->rangeCount = range_count;
+
+       write_multirange_data(multirange, rangetyp, range_count, ranges);
+
+       return multirange;
+}
+
+/*
+ * Get offset of bounds values of the i'th range in the multirange.
+ */
+static uint32
+multirange_get_bounds_offset(const MultirangeType *multirange, int32 i)
+{
+       uint32     *items = MultirangeGetItemsPtr(multirange);
+       uint32          offset = 0;
+
+       /*
+        * Summarize lengths till we meet an offset.
+        */
+       while (i > 0)
+       {
+               offset += MULTIRANGE_ITEM_GET_OFFLEN(items[i - 1]);
+               if (MULTIRANGE_ITEM_HAS_OFF(items[i - 1]))
+                       break;
+               i--;
+       }
+       return offset;
+}
+
+/*
+ * Fetch the i'th range from the multirange.
+ */
+RangeType *
+multirange_get_range(TypeCacheEntry *rangetyp,
+                                        const MultirangeType *multirange, int i)
+{
+       uint32          offset;
+       uint8           flags;
+       Pointer         begin,
+                               ptr;
+       int16           typlen = rangetyp->rngelemtype->typlen;
+       char            typalign = rangetyp->rngelemtype->typalign;
+       uint32          len;
+       RangeType  *range;
+
+       Assert(i < multirange->rangeCount);
+
+       offset = multirange_get_bounds_offset(multirange, i);
+       flags = MultirangeGetFlagsPtr(multirange)[i];
+       ptr = begin = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
+
+       /*
+        * Calculate the size of bound values.  In principle, we could get offset
+        * of the next range bound values and calculate accordingly.  But range
+        * bound values are aligned, so we have to walk the values to get the
+        * exact size.
+        */
+       if (RANGE_HAS_LBOUND(flags))
+               ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+       if (RANGE_HAS_UBOUND(flags))
+               ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+       len = (ptr - begin) + sizeof(RangeType) + sizeof(uint8);
+
+       range = palloc0(len);
+       SET_VARSIZE(range, len);
+       range->rangetypid = rangetyp->type_id;
+
+       memcpy(range + 1, begin, ptr - begin);
+       *((uint8 *) (range + 1) + (ptr - begin)) = flags;
+
+       return range;
+}
+
+/*
+ * Fetch bounds from the i'th range of the multirange.  This is the shortcut for
+ * doing the same thing as multirange_get_range() + range_deserialize(), but
+ * performing fewer operations.
+ */
+void
+multirange_get_bounds(TypeCacheEntry *rangetyp,
+                                         const MultirangeType *multirange,
+                                         uint32 i, RangeBound *lower, RangeBound *upper)
+{
+       uint32          offset;
+       uint8           flags;
+       Pointer         ptr;
+       int16           typlen = rangetyp->rngelemtype->typlen;
+       char            typalign = rangetyp->rngelemtype->typalign;
+       bool            typbyval = rangetyp->rngelemtype->typbyval;
+       Datum           lbound;
+       Datum           ubound;
+
+       Assert(i < multirange->rangeCount);
+
+       offset = multirange_get_bounds_offset(multirange, i);
+       flags = MultirangeGetFlagsPtr(multirange)[i];
+       ptr = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
+
+       /* multirange can't contain empty ranges */
+       Assert((flags & RANGE_EMPTY) == 0);
+
+       /* fetch lower bound, if any */
+       if (RANGE_HAS_LBOUND(flags))
+       {
+               /* att_align_pointer cannot be necessary here */
+               lbound = fetch_att(ptr, typbyval, typlen);
+               ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+       }
+       else
+               lbound = (Datum) 0;
+
+       /* fetch upper bound, if any */
+       if (RANGE_HAS_UBOUND(flags))
+       {
+               ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+               ubound = fetch_att(ptr, typbyval, typlen);
+               /* no need for att_addlength_pointer */
+       }
+       else
+               ubound = (Datum) 0;
+
+       /* emit results */
+       lower->val = lbound;
+       lower->infinite = (flags & RANGE_LB_INF) != 0;
+       lower->inclusive = (flags & RANGE_LB_INC) != 0;
+       lower->lower = true;
+
+       upper->val = ubound;
+       upper->infinite = (flags & RANGE_UB_INF) != 0;
+       upper->inclusive = (flags & RANGE_UB_INC) != 0;
+       upper->lower = false;
+}
+
+/*
+ * multirange_deserialize: deconstruct a multirange value
+ *
+ * NB: the given multirange object must be fully detoasted; it cannot have a
+ * short varlena header.
+ */
+void
+multirange_deserialize(TypeCacheEntry *rangetyp,
+                                          const MultirangeType *multirange, int32 *range_count,
+                                          RangeType ***ranges)
+{
+       *range_count = multirange->rangeCount;
+
+       /* Convert each ShortRangeType into a RangeType */
+       if (*range_count > 0)
+       {
+               int                     i;
+
+               *ranges = palloc(*range_count * sizeof(RangeType *));
+               for (i = 0; i < *range_count; i++)
+                       (*ranges)[i] = multirange_get_range(rangetyp, multirange, i);
+       }
+       else
+       {
+               *ranges = NULL;
+       }
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+       return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ * Similar to range_overlaps_internal(), but takes range bounds instead of
+ * ranges as arguments.
+ */
+static bool
+range_bounds_overlaps(TypeCacheEntry *typcache,
+                                         RangeBound *lower1, RangeBound *upper1,
+                                         RangeBound *lower2, RangeBound *upper2)
+{
+       if (range_cmp_bounds(typcache, lower1, lower2) >= 0 &&
+               range_cmp_bounds(typcache, lower1, upper2) <= 0)
+               return true;
+
+       if (range_cmp_bounds(typcache, lower2, lower1) >= 0 &&
+               range_cmp_bounds(typcache, lower2, upper1) <= 0)
+               return true;
+
+       return false;
+}
+
+/*
+ * Similar to range_contains_internal(), but takes range bounds instead of
+ * ranges as arguments.
+ */
+static bool
+range_bounds_contains(TypeCacheEntry *typcache,
+                                         RangeBound *lower1, RangeBound *upper1,
+                                         RangeBound *lower2, RangeBound *upper2)
+{
+       if (range_cmp_bounds(typcache, lower1, lower2) <= 0 &&
+               range_cmp_bounds(typcache, upper1, upper2) >= 0)
+               return true;
+
+       return false;
+}
+
+/*
+ * Check if the given key matches any range in multirange using binary search.
+ * If the required range isn't found, that counts as a mismatch.  When the
+ * required range is found, the comparison function can still report this as
+ * either match or mismatch.  For instance, if we search for containment, we can
+ * found a range, which is overlapping but not containing the key range, and
+ * that would count as a mismatch.
+ */
+static bool
+multirange_bsearch_match(TypeCacheEntry *typcache, MultirangeType *mr,
+                                                void *key, multirange_bsearch_comparison cmp_func)
+{
+       uint32          l,
+                               u,
+                               idx;
+       int                     comparison;
+       bool            match = false;
+
+       l = 0;
+       u = mr->rangeCount;
+       while (l < u)
+       {
+               RangeBound      lower,
+                                       upper;
+
+               idx = (l + u) / 2;
+               multirange_get_bounds(typcache, mr, idx, &lower, &upper);
+               comparison = (*cmp_func) (typcache, &lower, &upper, key, &match);
+
+               if (comparison < 0)
+                       u = idx;
+               else if (comparison > 0)
+                       l = idx + 1;
+               else
+                       return match;
+       }
+
+       return false;
+}
+
+/*
+ *----------------------------------------------------------
+ * GENERIC FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/*
+ * Construct multirange value from zero or more ranges.  Since this is a
+ * variadic function we get passed an array.  The array must contain ranges
+ * that match our return value, and there must be no NULLs.
+ */
+Datum
+multirange_constructor2(PG_FUNCTION_ARGS)
+{
+       Oid                     mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+       Oid                     rngtypid;
+       TypeCacheEntry *typcache;
+       TypeCacheEntry *rangetyp;
+       ArrayType  *rangeArray;
+       int                     range_count;
+       Datum      *elements;
+       bool       *nulls;
+       RangeType **ranges;
+       int                     dims;
+       int                     i;
+
+       typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+       rangetyp = typcache->rngtype;
+
+       /*
+        * A no-arg invocation should call multirange_constructor0 instead, but
+        * returning an empty range is what that does.
+        */
+
+       if (PG_NARGS() == 0)
+               PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL));
+
+       /*
+        * These checks should be guaranteed by our signature, but let's do them
+        * just in case.
+        */
+
+       if (PG_ARGISNULL(0))
+               ereport(ERROR,
+                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                errmsg("multirange values cannot contain NULL members")));
+
+       rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+       dims = ARR_NDIM(rangeArray);
+       if (dims > 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_CARDINALITY_VIOLATION),
+                                errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+       rngtypid = ARR_ELEMTYPE(rangeArray);
+       if (rngtypid != rangetyp->type_id)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("type %u does not match constructor type", rngtypid)));
+
+       /*
+        * Be careful: we can still be called with zero ranges, like this:
+        * `int4multirange(variadic '{}'::int4range[])
+        */
+       if (dims == 0)
+       {
+               range_count = 0;
+               ranges = NULL;
+       }
+       else
+       {
+               deconstruct_array(rangeArray, rngtypid, rangetyp->typlen, rangetyp->typbyval,
+                                                 rangetyp->typalign, &elements, &nulls, &range_count);
+
+               ranges = palloc0(range_count * sizeof(RangeType *));
+               for (i = 0; i < range_count; i++)
+               {
+                       if (nulls[i])
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                                errmsg("multirange values cannot contain NULL members")));
+
+                       /* make_multirange will do its own copy */
+                       ranges[i] = DatumGetRangeTypeP(elements[i]);
+               }
+       }
+
+       PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.  It'd be nice if we could
+ * just use multirange_constructor2 for this case, but we need a non-variadic
+ * single-arg function to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+       Oid                     mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+       Oid                     rngtypid;
+       TypeCacheEntry *typcache;
+       TypeCacheEntry *rangetyp;
+       RangeType  *range;
+
+       typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+       rangetyp = typcache->rngtype;
+
+       /*
+        * These checks should be guaranteed by our signature, but let's do them
+        * just in case.
+        */
+
+       if (PG_ARGISNULL(0))
+               ereport(ERROR,
+                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                errmsg("multirange values cannot contain NULL members")));
+
+       range = PG_GETARG_RANGE_P(0);
+
+       /* Make sure the range type matches. */
+       rngtypid = RangeTypeGetOid(range);
+       if (rngtypid != rangetyp->type_id)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("type %u does not match constructor type", rngtypid)));
+
+       PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * Constructor just like multirange_constructor1, but opr_sanity gets angry
+ * if the same internal function handles multiple functions with different arg
+ * counts.
+ */
+Datum
+multirange_constructor0(PG_FUNCTION_ARGS)
+{
+       Oid                     mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+       TypeCacheEntry *typcache;
+       TypeCacheEntry *rangetyp;
+
+       typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+       rangetyp = typcache->rngtype;
+
+       /* We should always be called with no arguments */
+
+       if (PG_NARGS() == 0)
+               PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL));
+       else
+               elog(ERROR,                             /* can't happen */
+                        "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+       MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+       TypeCacheEntry *typcache;
+       int32           range_count1;
+       int32           range_count2;
+       int32           range_count3;
+       RangeType **ranges1;
+       RangeType **ranges2;
+       RangeType **ranges3;
+
+       if (MultirangeIsEmpty(mr1))
+               PG_RETURN_MULTIRANGE_P(mr2);
+       if (MultirangeIsEmpty(mr2))
+               PG_RETURN_MULTIRANGE_P(mr1);
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+       multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+       multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+       range_count3 = range_count1 + range_count2;
+       ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+       memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+       memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+       PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+                                                                                  range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+       MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+       Oid                     mltrngtypoid = MultirangeTypeGetOid(mr1);
+       TypeCacheEntry *typcache;
+       TypeCacheEntry *rangetyp;
+       int32           range_count1;
+       int32           range_count2;
+       RangeType **ranges1;
+       RangeType **ranges2;
+
+       typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+       rangetyp = typcache->rngtype;
+
+       if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+               PG_RETURN_MULTIRANGE_P(mr1);
+
+       multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+       multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+       PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+                                                                                                        rangetyp,
+                                                                                                        range_count1,
+                                                                                                        ranges1,
+                                                                                                        range_count2,
+                                                                                                        ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+                                                 int32 range_count1, RangeType **ranges1,
+                                                 int32 range_count2, RangeType **ranges2)
+{
+       RangeType  *r1;
+       RangeType  *r2;
+       RangeType **ranges3;
+       int32           range_count3;
+       int32           i1;
+       int32           i2;
+
+       /*
+        * Worst case: every range in ranges1 makes a different cut to some range
+        * in ranges2.
+        */
+       ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+       range_count3 = 0;
+
+       /*
+        * For each range in mr1, keep subtracting until it's gone or the ranges
+        * in mr2 have passed it. After a subtraction we assign what's left back
+        * to r1. The parallel progress through mr1 and mr2 is similar to
+        * multirange_overlaps_multirange_internal.
+        */
+       r2 = ranges2[0];
+       for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+       {
+               r1 = ranges1[i1];
+
+               /* Discard r2s while r2 << r1 */
+               while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+               {
+                       r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+               }
+
+               while (r2 != NULL)
+               {
+                       if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+                       {
+                               /*
+                                * If r2 takes a bite out of the middle of r1, we need two
+                                * outputs
+                                */
+                               range_count3++;
+                               r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+                       }
+                       else if (range_overlaps_internal(rangetyp, r1, r2))
+                       {
+                               /*
+                                * If r2 overlaps r1, replace r1 with r1 - r2.
+                                */
+                               r1 = range_minus_internal(rangetyp, r1, r2);
+
+                               /*
+                                * If r2 goes past r1, then we need to stay with it, in case
+                                * it hits future r1s. Otherwise we need to keep r1, in case
+                                * future r2s hit it. Since we already subtracted, there's no
+                                * point in using the overright/overleft calls.
+                                */
+                               if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+                                       break;
+                               else
+                                       r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+                       }
+                       else
+                       {
+                               /*
+                                * This and all future r2s are past r1, so keep them. Also
+                                * assign whatever is left of r1 to the result.
+                                */
+                               break;
+                       }
+               }
+
+               /*
+                * Nothing else can remove anything from r1, so keep it. Even if r1 is
+                * empty here, make_multirange will remove it.
+                */
+               ranges3[range_count3++] = r1;
+       }
+
+       return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+       MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+       Oid                     mltrngtypoid = MultirangeTypeGetOid(mr1);
+       TypeCacheEntry *typcache;
+       TypeCacheEntry *rangetyp;
+       int32           range_count1;
+       int32           range_count2;
+       RangeType **ranges1;
+       RangeType **ranges2;
+
+       typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+       rangetyp = typcache->rngtype;
+
+       if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+               PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+       multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+       multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+       PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+                                                                                                                rangetyp,
+                                                                                                                range_count1,
+                                                                                                                ranges1,
+                                                                                                                range_count2,
+                                                                                                                ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+                                                         int32 range_count1, RangeType **ranges1,
+                                                         int32 range_count2, RangeType **ranges2)
+{
+       RangeType  *r1;
+       RangeType  *r2;
+       RangeType **ranges3;
+       int32           range_count3;
+       int32           i1;
+       int32           i2;
+
+       if (range_count1 == 0 || range_count2 == 0)
+               return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+       /*-----------------------------------------------
+        * Worst case is a stitching pattern like this:
+        *
+        * mr1: --- --- --- ---
+        * mr2:   --- --- ---
+        * mr3:   - - - - - -
+        *
+        * That seems to be range_count1 + range_count2 - 1,
+        * but one extra won't hurt.
+        *-----------------------------------------------
+        */
+       ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+       range_count3 = 0;
+
+       /*
+        * For each range in mr1, keep intersecting until the ranges in mr2 have
+        * passed it. The parallel progress through mr1 and mr2 is similar to
+        * multirange_minus_multirange_internal, but we don't have to assign back
+        * to r1.
+        */
+       r2 = ranges2[0];
+       for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+       {
+               r1 = ranges1[i1];
+
+               /* Discard r2s while r2 << r1 */
+               while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+               {
+                       r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+               }
+
+               while (r2 != NULL)
+               {
+                       if (range_overlaps_internal(rangetyp, r1, r2))
+                       {
+                               /* Keep the overlapping part */
+                               ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+                               /* If we "used up" all of r2, go to the next one... */
+                               if (range_overleft_internal(rangetyp, r2, r1))
+                                       r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+                               /* ...otherwise go to the next r1 */
+                               else
+                                       break;
+                       }
+                       else
+                               /* We're past r1, so move to the next one */
+                               break;
+               }
+
+               /* If we're out of r2s, there can be no more intersections */
+               if (r2 == NULL)
+                       break;
+       }
+
+       return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+       MemoryContext aggContext;
+       Oid                     rngtypoid;
+       ArrayBuildState *state;
+
+       if (!AggCheckCallContext(fcinfo, &aggContext))
+               elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+       rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+       if (!type_is_range(rngtypoid))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("range_agg must be called with a range")));
+
+       if (PG_ARGISNULL(0))
+               state = initArrayResult(rngtypoid, aggContext, false);
+       else
+               state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+       /* skip NULLs */
+       if (!PG_ARGISNULL(1))
+               accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+       PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+       MemoryContext aggContext;
+       Oid                     mltrngtypoid;
+       TypeCacheEntry *typcache;
+       ArrayBuildState *state;
+       int32           range_count;
+       RangeType **ranges;
+       int                     i;
+
+       if (!AggCheckCallContext(fcinfo, &aggContext))
+               elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+       state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+       if (state == NULL)
+               /* This shouldn't be possible, but just in case.... */
+               PG_RETURN_NULL();
+
+       /* Also return NULL if we had zero inputs, like other aggregates */
+       range_count = state->nelems;
+       if (range_count == 0)
+               PG_RETURN_NULL();
+
+       mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+       typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+       ranges = palloc0(range_count * sizeof(RangeType *));
+       for (i = 0; i < range_count; i++)
+               ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+       PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+       MemoryContext aggContext;
+       Oid                     mltrngtypoid;
+       TypeCacheEntry *typcache;
+       MultirangeType *result;
+       MultirangeType *current;
+       int32           range_count1;
+       int32           range_count2;
+       RangeType **ranges1;
+       RangeType **ranges2;
+
+       if (!AggCheckCallContext(fcinfo, &aggContext))
+               elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+       mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+       if (!type_is_multirange(mltrngtypoid))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("range_intersect_agg must be called with a multirange")));
+
+       typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+       /* strictness ensures these are non-null */
+       result = PG_GETARG_MULTIRANGE_P(0);
+       current = PG_GETARG_MULTIRANGE_P(1);
+
+       multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+       multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+       result = multirange_intersect_internal(mltrngtypoid,
+                                                                                  typcache->rngtype,
+                                                                                  range_count1,
+                                                                                  ranges1,
+                                                                                  range_count2,
+                                                                                  ranges2);
+       PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       TypeCacheEntry *typcache;
+       RangeBound      lower;
+       RangeBound      upper;
+
+       if (MultirangeIsEmpty(mr))
+               PG_RETURN_NULL();
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+       multirange_get_bounds(typcache->rngtype, mr, 0,
+                                                 &lower, &upper);
+
+       if (!lower.infinite)
+               PG_RETURN_DATUM(lower.val);
+       else
+               PG_RETURN_NULL();
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       TypeCacheEntry *typcache;
+       RangeBound      lower;
+       RangeBound      upper;
+
+       if (MultirangeIsEmpty(mr))
+               PG_RETURN_NULL();
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+       multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+                                                 &lower, &upper);
+
+       if (!upper.infinite)
+               PG_RETURN_DATUM(upper.val);
+       else
+               PG_RETURN_NULL();
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+       PG_RETURN_BOOL(MultirangeIsEmpty(mr));
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       TypeCacheEntry *typcache;
+       RangeBound      lower;
+       RangeBound      upper;
+
+       if (MultirangeIsEmpty(mr))
+               PG_RETURN_BOOL(false);
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+       multirange_get_bounds(typcache->rngtype, mr, 0,
+                                                 &lower, &upper);
+
+       PG_RETURN_BOOL(lower.inclusive);
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       TypeCacheEntry *typcache;
+       RangeBound      lower;
+       RangeBound      upper;
+
+       if (MultirangeIsEmpty(mr))
+               PG_RETURN_BOOL(false);
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+       multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+                                                 &lower, &upper);
+
+       PG_RETURN_BOOL(upper.inclusive);
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       TypeCacheEntry *typcache;
+       RangeBound      lower;
+       RangeBound      upper;
+
+       if (MultirangeIsEmpty(mr))
+               PG_RETURN_BOOL(false);
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+       multirange_get_bounds(typcache->rngtype, mr, 0,
+                                                 &lower, &upper);
+
+       PG_RETURN_BOOL(lower.infinite);
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       TypeCacheEntry *typcache;
+       RangeBound      lower;
+       RangeBound      upper;
+
+       if (MultirangeIsEmpty(mr))
+               PG_RETURN_BOOL(false);
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+       multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+                                                 &lower, &upper);
+
+       PG_RETURN_BOOL(upper.infinite);
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       Datum           val = PG_GETARG_DATUM(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+       PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+       Datum           val = PG_GETARG_DATUM(0);
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+       PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Comparison function for checking if any range of multirange contains given
+ * key element using binary search.
+ */
+static int
+multirange_elem_bsearch_comparison(TypeCacheEntry *typcache,
+                                                                  RangeBound *lower, RangeBound *upper,
+                                                                  void *key, bool *match)
+{
+       Datum           val = *((Datum *) key);
+       int                     cmp;
+
+       if (!lower->infinite)
+       {
+               cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+                                                                                         typcache->rng_collation,
+                                                                                         lower->val, val));
+               if (cmp > 0 || (cmp == 0 && !lower->inclusive))
+                       return -1;
+       }
+
+       if (!upper->infinite)
+       {
+               cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+                                                                                         typcache->rng_collation,
+                                                                                         upper->val, val));
+               if (cmp < 0 || (cmp == 0 && !upper->inclusive))
+                       return 1;
+       }
+
+       *match = true;
+       return 0;
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache,
+                                                                 MultirangeType *mr, Datum val)
+{
+       if (MultirangeIsEmpty(mr))
+               return false;
+
+       return multirange_bsearch_match(typcache->rngtype, mr, &val,
+                                                                       multirange_elem_bsearch_comparison);
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       RangeType  *r = PG_GETARG_RANGE_P(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+       PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+       RangeType  *r = PG_GETARG_RANGE_P(0);
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+       PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Comparison function for checking if any range of multirange contains given
+ * key range using binary search.
+ */
+static int
+multirange_range_contains_bsearch_comparison(TypeCacheEntry *typcache,
+                                                                                        RangeBound *lower, RangeBound *upper,
+                                                                                        void *key, bool *match)
+{
+       RangeBound *keyLower = (RangeBound *) key;
+       RangeBound *keyUpper = (RangeBound *) key + 1;
+
+       /* Check if key range is strictly in the left or in the right */
+       if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
+               return -1;
+       if (range_cmp_bounds(typcache, keyLower, upper) > 0)
+               return -1;
+
+       /*
+        * At this point we found overlapping range.  But we have to check if it
+        * really contains the key range.  Anyway, we have to stop our search
+        * here, because multirange contains only non-overlapping ranges.
+        */
+       *match = range_bounds_contains(typcache, lower, upper, keyLower, keyUpper);
+
+       return 0;
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+       TypeCacheEntry *rangetyp;
+       RangeBound      bounds[2];
+       bool            empty;
+
+       rangetyp = typcache->rngtype;
+
+       /*
+        * Every multirange contains an infinite number of empty ranges, even an
+        * empty one.
+        */
+       if (RangeIsEmpty(r))
+               return true;
+
+       if (MultirangeIsEmpty(mr))
+               return false;
+
+       range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
+       Assert(!empty);
+
+       return multirange_bsearch_match(rangetyp, mr, bounds,
+                                                                       multirange_range_contains_bsearch_comparison);
+}
+
+
+/* multirange, multirange -> bool functions */
+
+/* equality (internal version) */
+bool
+multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
+{
+       TypeCacheEntry *rangetyp = typcache->rngtype;
+       int32           range_count_1;
+       int32           range_count_2;
+       int32           i;
+       RangeBound      lower1,
+                               upper1,
+                               lower2,
+                               upper2;
+
+       /* Different types should be prevented by ANYMULTIRANGE matching rules */
+       if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
+               elog(ERROR, "multirange types do not match");
+
+       range_count_1 = mr1->rangeCount;
+       range_count_2 = mr2->rangeCount;
+
+       if (range_count_1 != range_count_2)
+               return false;
+
+       for (i = 0; i < range_count_1; i++)
+       {
+               multirange_get_bounds(rangetyp, mr1, i, &lower1, &upper1);
+               multirange_get_bounds(rangetyp, mr2, i, &lower2, &upper2);
+
+               if (range_cmp_bounds(rangetyp, &lower1, &lower2) != 0 ||
+                       range_cmp_bounds(rangetyp, &upper1, &upper2) != 0)
+                       return false;
+       }
+
+       return true;
+}
+
+/* equality */
+Datum
+multirange_eq(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+       MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+       PG_RETURN_BOOL(multirange_eq_internal(typcache, mr1, mr2));
+}
+
+/* inequality (internal version) */
+bool
+multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
+{
+       return (!multirange_eq_internal(typcache, mr1, mr2));
+}
+
+/* inequality */
+Datum
+multirange_ne(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+       MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+       PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+       RangeType  *r = PG_GETARG_RANGE_P(0);
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+       PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+       RangeType  *r = PG_GETARG_RANGE_P(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+       PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+       MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+       MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+       TypeCacheEntry *typcache;
+
+       typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+       PG_RETURN_BOOL(multirange_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+/*
+ * Comparison function for checking if any range of multirange overlaps given
+ * key range using binary search.
+ */
+static int
+multirange_range_overlaps_bsearch_comparison(TypeCacheEntry *typcache,
+                                                                                        RangeBound *lower, RangeBound *upper,
+                                                                                        void *key, bool *match)
+{
+       RangeBound *keyLower = (RangeBound *) key;
+       RangeBound *keyUpper = (RangeBound *) key + 1;
+
+       if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
+               return -1;
+       if (range_cmp_bounds(typcache, keyLower, upper) > 0)
+               return -1;
+
+       *match = true;
+       return 0;
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+       TypeCacheEntry *rangetyp;
+       RangeBound      bounds[2];
+       bool            empty;
+
+       rangetyp = typcache->rngtype;
+
+       /*
+        * Empties never overlap, even with empties. (This seems strange since
+        * they *do* contain each other, but we want to follow how ranges work.)
+        */
+       if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+               return false;
+
+       range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
+       Assert(!empty);
+
+       return multirange_bsearch_match(rangetyp, mr, bounds,
+                                                                       multirange_range_overlaps_bsearch_comparison);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+                                                                               MultirangeType *mr2)
+{
+       TypeCacheEntry *rangetyp;
+       int32           range_count1;
+       int32           range_count2;
+       int32           i1;
+       int32           i2;
+       RangeBound      lower1,
+                               upper1,
+                               lower2,
+                               upper2;
+
+       /*
+        * Empties never overlap, even with empties. (This seems strange since
+        * they *do* contain each other, but we want to follow how ranges work.)
+        */
+       if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+               return false;
+
+       rangetyp = typcache->rngtype;
+
+       range_count1 = mr1->rangeCount;
+       range_count2 = mr2->rangeCount;
+
+       /*
+        * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+        * we can use their ordering to avoid O(n^2). This is similar to
+        * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+        * don't find an overlap with r we're done, and here if we don't find an
+        * overlap with r2 we try the next r2.
+        */
+       i1 = 0;
+       multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+       for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+       {
+               multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2);
+
+               /* Discard r1s while r1 << r2 */
+               while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0)
+               {
+                       if (++i1 >= range_count1)
+                               return false;
+                       multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+               }
+
+               /*
+                * If r1 && r2, we're done, otherwise we failed to find an overlap for
+                * r2, so go to the next one.
+                */
+               if (range_bounds_overlaps(rangetyp, &lower1, &upper1, &lower2, &upper2))
+                 &