commit 495cbffc1d9e6b97b8f66bd1a510996e4bcc8973
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Thu Jun 10 20:20:31 2021 +0300

    Support for unnest(multirange) and cast multirange as array of ranges
    
    Reported-by:
    Bug:
    Discussion:
    Author:
    Reviewed-by:
    Tested-by:
    Backpatch-through:

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fbc80c1403d..c7c2a2d11f7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -19181,6 +19181,29 @@ SELECT NULLIF(value, '(none)') ...
         <returnvalue>{[1,2)}</returnvalue>
        </para></entry>
       </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>unnest</primary>
+         <secondary>for multirange</secondary>
+        </indexterm>
+        <function>unnest</function> ( <type>anymultirange</type> )
+        <returnvalue>setof anyrange</returnvalue>
+       </para>
+       <para>
+        Expands a multirange into a set of ranges.
+        The ranges are read out in storage order (ascending).
+       </para>
+       <para>
+        <literal>unnest('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue></returnvalue>
+<programlisting>
+ [1,2)
+ [3,4)
+</programlisting>
+       </para></entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index 92ea0e83dab..2462f82172f 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -266,6 +266,19 @@ SELECT '[4,4)'::int4range;
 SELECT '{}'::int4multirange;
 SELECT '{[3,7)}'::int4multirange;
 SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
+  <para>
+   The multirange could be explicitly cast to the array of corresponding
+   ranges.
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{[3,7), [8,9)}'::int4multirange::int4range[];
+SELECT '{[1.0,14.0), [20.0,25.0)}'::nummultirange::numrange[];
 </programlisting>
   </para>
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 58ec65c6afc..9ea0ce17e12 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -114,7 +114,11 @@ 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);
+									   Oid rangeArrayOid,
+									   Oid *oneArgContructorOid);
+static void makeMultirangeCasts(const char *name, Oid namespace,
+								Oid multirangeOid, Oid rangeOid,
+								Oid rangeArrayOid, Oid singleArgContructorOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -1365,7 +1369,7 @@ DefineRange(CreateRangeStmt *stmt)
 	ListCell   *lc;
 	ObjectAddress address;
 	ObjectAddress mltrngaddress PG_USED_FOR_ASSERTS_ONLY;
-	Oid			castFuncOid;
+	Oid			singleArgContructorOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1717,10 +1721,12 @@ DefineRange(CreateRangeStmt *stmt)
 	makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype);
 	makeMultirangeConstructors(multirangeTypeName, typeNamespace,
 							   multirangeOid, typoid, rangeArrayOid,
-							   &castFuncOid);
+							   &singleArgContructorOid);
 
-	/* Create cast from the range type to its multirange type */
-	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+	/* Create casts for this multirange type */
+	makeMultirangeCasts(multirangeTypeName, typeNamespace,
+						multirangeOid, typoid, rangeArrayOid,
+						singleArgContructorOid);
 
 	pfree(multirangeTypeName);
 	pfree(multirangeArrayName);
@@ -1808,13 +1814,13 @@ makeRangeConstructors(const char *name, Oid namespace,
  * 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
+ * Sets oneArgContructorOid 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)
+						   Oid *oneArgContructorOid)
 {
 	ObjectAddress myself,
 				referenced;
@@ -1904,7 +1910,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	/* ditto */
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
 	pfree(argtypes);
-	*castFuncOid = myself.objectId;
+	*oneArgContructorOid = myself.objectId;
 
 	/* n-arg constructor - vararg */
 	argtypes = buildoidvector(&rangeArrayOid, 1);
@@ -1949,6 +1955,76 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	pfree(parameterModes);
 }
 
+/*
+ * Create casts for the multirange type.  The first cast makes multirange from
+ * range, and it's based on the single-argument constructor.  The second cast
+ * makes an array of ranges from multirange.
+ */
+static void
+makeMultirangeCasts(const char *name, Oid namespace,
+					Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+					Oid singleArgContructorOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+
+	/*
+	 * Create cast from range to multirange using the existing single-argument
+	 * constructor procedure.
+	 */
+	CastCreate(rangeOid, multirangeOid, singleArgContructorOid, 'e', 'f',
+			   DEPENDENCY_INTERNAL);
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* multirange_to_array() function */
+	argtypes = buildoidvector(&multirangeOid, 1);
+	myself = ProcedureCreate("multirange_to_array", /* name */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 rangeArrayOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_to_array", /* prosrc */
+							 NULL,	/* probin */
+							 NULL,	/* prosqlbody */
+							 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 */
+
+	/*
+	 * Make the multirange_to_array() function internally-dependent on the
+	 * multirange type so that they go away silently when the type is dropped.
+	 */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+
+	/*
+	 * Create cast from multirange to the array of ranges using
+	 * multirange_to_array() function.
+	 */
+	CastCreate(multirangeOid, rangeArrayOid, myself.objectId, 'e', 'f',
+			   DEPENDENCY_INTERNAL);
+}
+
 /*
  * Find suitable I/O and other support functions for a type.
  *
diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index fbcc27d0726..23c0ed4712a 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -34,6 +34,7 @@
 
 #include "access/tupmacs.h"
 #include "common/hashfn.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
@@ -1068,6 +1069,35 @@ multirange_constructor0(PG_FUNCTION_ARGS)
 	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 0, NULL));
 }
 
+/*
+ * Cast multirange to the array of ranges.
+ */
+Datum
+multirange_to_array(PG_FUNCTION_ARGS)
+{
+	ArrayBuildState *astate = NULL;
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int			i;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	for (i = 0; i < mr->rangeCount; i++)
+	{
+		RangeType  *r;
+
+		r = multirange_get_range(typcache->rngtype, mr, i);
+		astate = accumArrayResult(astate,
+								  RangeTypePGetDatum(r),
+								  false,
+								  typcache->rngtype->type_id,
+								  CurrentMemoryContext);
+	}
+
+	PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
+}
+
+
 
 /* multirange, multirange -> multirange type functions */
 
@@ -2645,6 +2675,78 @@ range_merge_from_multirange(PG_FUNCTION_ARGS)
 	PG_RETURN_RANGE_P(result);
 }
 
+/* Turn multirange into a set of ranges */
+Datum
+multirange_unnest(PG_FUNCTION_ARGS)
+{
+	typedef struct
+	{
+		MultirangeType *mr;
+		TypeCacheEntry *typcache;
+		int			index;
+	} multirange_unnest_fctx;
+
+	FuncCallContext *funcctx;
+	multirange_unnest_fctx *fctx;
+	MemoryContext oldcontext;
+
+	/* stuff done only on the first call of the function */
+	if (SRF_IS_FIRSTCALL())
+	{
+		MultirangeType *mr;
+
+		/* create a function context for cross-call persistence */
+		funcctx = SRF_FIRSTCALL_INIT();
+
+		/*
+		 * switch to memory context appropriate for multiple function calls
+		 */
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		/*
+		 * Get the multirange value and detoast if needed.  We can't do this
+		 * earlier because if we have to detoast, we want the detoasted copy
+		 * to be in multi_call_memory_ctx, so it will go away when we're done
+		 * and not before.  (If no detoast happens, we assume the originally
+		 * passed multirange will stick around till then.)
+		 */
+		mr = PG_GETARG_MULTIRANGE_P(0);
+
+		/* allocate memory for user context */
+		fctx = (multirange_unnest_fctx *) palloc(sizeof(multirange_unnest_fctx));
+
+		/* initialize state */
+		fctx->mr = mr;
+		fctx->index = 0;
+		fctx->typcache = lookup_type_cache(MultirangeTypeGetOid(mr),
+										   TYPECACHE_MULTIRANGE_INFO);
+
+		funcctx->user_fctx = fctx;
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	/* stuff done on every call of the function */
+	funcctx = SRF_PERCALL_SETUP();
+	fctx = funcctx->user_fctx;
+
+	if (fctx->index < fctx->mr->rangeCount)
+	{
+		RangeType  *range;
+
+		range = multirange_get_range(fctx->typcache->rngtype,
+									 fctx->mr,
+									 fctx->index);
+		fctx->index++;
+
+		SRF_RETURN_NEXT(funcctx, RangeTypePGetDatum(range));
+	}
+	else
+	{
+		/* do when there is no more left */
+		SRF_RETURN_DONE(funcctx);
+	}
+}
+
 /* Hash support */
 
 /* hash a multirange value */
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 67f73cb6fb2..d0b37edb605 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -548,4 +548,24 @@
 { castsource => 'tstzrange', casttarget => 'tstzmultirange',
   castfunc => 'tstzmultirange(tstzrange)', castcontext => 'e',
   castmethod => 'f' },
+
+# multirange to array
+{ castsource => 'int4multirange', casttarget => '_int4range',
+  castfunc => 'multirange_to_array(int4multirange)', castcontext => 'e',
+  castmethod => 'f' }
+{ castsource => 'int8multirange', casttarget => '_int8range',
+  castfunc => 'multirange_to_array(int8multirange)', castcontext => 'e',
+  castmethod => 'f' }
+{ castsource => 'nummultirange', casttarget => '_numrange',
+  castfunc => 'multirange_to_array(nummultirange)', castcontext => 'e',
+  castmethod => 'f' }
+{ castsource => 'datemultirange', casttarget => '_daterange',
+  castfunc => 'multirange_to_array(datemultirange)', castcontext => 'e',
+  castmethod => 'f' }
+{ castsource => 'tsmultirange', casttarget => '_tsrange',
+  castfunc => 'multirange_to_array(tsmultirange)', castcontext => 'e',
+  castmethod => 'f' }
+{ castsource => 'tstzmultirange', casttarget => '_tstzrange',
+  castfunc => 'multirange_to_array(tstzmultirange)', castcontext => 'e',
+  castmethod => 'f' }
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae46070..44a89e52adb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10542,6 +10542,29 @@
   proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
   prorettype => 'anymultirange', proargtypes => 'anymultirange',
   prosrc => 'aggregate_dummy' },
+{ oid => '1293', descr => 'expand multirange to set of ranges',
+  proname => 'unnest', prorows => '100',
+  proretset => 't', prorettype => 'anyrange', proargtypes => 'anymultirange',
+  prosrc => 'multirange_unnest' },
+
+{ oid => '4544', descr => 'convert multirange to array of ranges',
+  proname => 'multirange_to_array', prorettype => '_int4range',
+  proargtypes => 'int4multirange', prosrc => 'multirange_to_array' },
+{ oid => '4545', descr => 'convert multirange to array of ranges',
+  proname => 'multirange_to_array', prorettype => '_int8range',
+  proargtypes => 'int8multirange', prosrc => 'multirange_to_array' },
+{ oid => '4546', descr => 'convert multirange to array of ranges',
+  proname => 'multirange_to_array', prorettype => '_numrange',
+  proargtypes => 'nummultirange', prosrc => 'multirange_to_array' },
+{ oid => '4547', descr => 'convert multirange to array of ranges',
+  proname => 'multirange_to_array', prorettype => '_daterange',
+  proargtypes => 'datemultirange', prosrc => 'multirange_to_array' },
+{ oid => '4548', descr => 'convert multirange to array of ranges',
+  proname => 'multirange_to_array', prorettype => '_tsrange',
+  proargtypes => 'tsmultirange', prosrc => 'multirange_to_array' },
+{ oid => '4549', descr => 'convert multirange to array of ranges',
+  proname => 'multirange_to_array', prorettype => '_tstzrange',
+  proargtypes => 'tstzmultirange', prosrc => 'multirange_to_array' },
 
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 98ac592127b..be68ab7317b 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -322,6 +322,12 @@ select int4range(null, null)::int4multirange;
  {(,)}
 (1 row)
 
+select int4multirange(int4range('5', '6'), int4range('1', '2'))::int4range[];
+  int4multirange   
+-------------------
+ {"[1,2)","[5,6)"}
+(1 row)
+
 select 'empty'::textrange::textmultirange;
  textmultirange 
 ----------------
@@ -346,6 +352,29 @@ select textrange(null, null)::textmultirange;
  {(,)}
 (1 row)
 
+select textmultirange(textrange('a', 'b'), textrange('d', 'e'))::textrange[];
+  textmultirange   
+-------------------
+ {"[a,b)","[d,e)"}
+(1 row)
+
+--
+-- test unnest(multirange) function
+--
+select unnest(int4multirange(int4range('5', '6'), int4range('1', '2')));
+ unnest 
+--------
+ [1,2)
+ [5,6)
+(2 rows)
+
+select unnest(textmultirange(textrange('a', 'b'), textrange('d', 'e')));
+ unnest 
+--------
+ [a,b)
+ [d,e)
+(2 rows)
+
 --
 -- create some test data and test the operators
 --
@@ -2728,6 +2757,19 @@ LINE 1: select multirange_of_text(textrange2('a','Z'));
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
 ERROR:  range lower bound must be less than or equal to range upper bound
+select multirange_of_text(textrange1('a','b'), textrange1('d','e'))::textrange1[];
+ multirange_of_text 
+--------------------
+ {"[a,b)","[d,e)"}
+(1 row)
+
+select unnest(multirange_of_text(textrange1('a','b'), textrange1('d','e')));
+ unnest 
+--------
+ [a,b)
+ [d,e)
+(2 rows)
+
 select _textrange1(textrange2('a','z')) @> 'b'::text;
  ?column? 
 ----------
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 562b586d8e0..fbcd1934093 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -151,6 +151,8 @@ WHERE p1.oid != p2.oid AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
     p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
     p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p1.prosrc != 'multirange_to_array' AND
+    p2.prosrc != 'multirange_to_array' AND
     (p1.prorettype < p2.prorettype)
 ORDER BY 1, 2;
          prorettype          |        prorettype        
@@ -171,6 +173,8 @@ WHERE p1.oid != p2.oid AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
     p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
     p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p1.prosrc != 'multirange_to_array' AND
+    p2.prosrc != 'multirange_to_array' AND
     (p1.proargtypes[0] < p2.proargtypes[0])
 ORDER BY 1, 2;
          proargtypes         |       proargtypes        
@@ -193,6 +197,8 @@ WHERE p1.oid != p2.oid AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
     p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
     p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p1.prosrc != 'multirange_to_array' AND
+    p2.prosrc != 'multirange_to_array' AND
     (p1.proargtypes[1] < p2.proargtypes[1])
 ORDER BY 1, 2;
          proargtypes         |       proargtypes        
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 3cbebedcd4a..a382ca58a93 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -72,10 +72,18 @@ select 'empty'::int4range::int4multirange;
 select int4range(1, 3)::int4multirange;
 select int4range(1, null)::int4multirange;
 select int4range(null, null)::int4multirange;
+select int4multirange(int4range('5', '6'), int4range('1', '2'))::int4range[];
 select 'empty'::textrange::textmultirange;
 select textrange('a', 'c')::textmultirange;
 select textrange('a', null)::textmultirange;
 select textrange(null, null)::textmultirange;
+select textmultirange(textrange('a', 'b'), textrange('d', 'e'))::textrange[];
+
+--
+-- test unnest(multirange) function
+--
+select unnest(int4multirange(int4range('5', '6'), int4range('1', '2')));
+select unnest(textmultirange(textrange('a', 'b'), textrange('d', 'e')));
 
 --
 -- create some test data and test the operators
@@ -621,6 +629,8 @@ create type textrange2 as range(subtype=text, multirange_type_name=_textrange1,
 
 select multirange_of_text(textrange2('a','Z'));  -- should fail
 select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
+select multirange_of_text(textrange1('a','b'), textrange1('d','e'))::textrange1[];
+select unnest(multirange_of_text(textrange1('a','b'), textrange1('d','e')));
 select _textrange1(textrange2('a','z')) @> 'b'::text;
 
 drop type textrange1;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 5a9c4796923..19497625f9a 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -127,6 +127,8 @@ WHERE p1.oid != p2.oid AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
     p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
     p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p1.prosrc != 'multirange_to_array' AND
+    p2.prosrc != 'multirange_to_array' AND
     (p1.prorettype < p2.prorettype)
 ORDER BY 1, 2;
 
@@ -140,6 +142,8 @@ WHERE p1.oid != p2.oid AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
     p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
     p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p1.prosrc != 'multirange_to_array' AND
+    p2.prosrc != 'multirange_to_array' AND
     (p1.proargtypes[0] < p2.proargtypes[0])
 ORDER BY 1, 2;
 
@@ -153,6 +157,8 @@ WHERE p1.oid != p2.oid AND
     p2.prosrc NOT LIKE E'range\\_constructor_' AND
     p1.prosrc NOT LIKE E'multirange\\_constructor_' AND
     p2.prosrc NOT LIKE E'multirange\\_constructor_' AND
+    p1.prosrc != 'multirange_to_array' AND
+    p2.prosrc != 'multirange_to_array' AND
     (p1.proargtypes[1] < p2.proargtypes[1])
 ORDER BY 1, 2;
 
