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

    Support for unnest(multirange)
    
    Reported-by:
    Bug:
    Discussion:
    Author:
    Reviewed-by:
    Tested-by:
    Backpatch-through:

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 0b81649779a..946b3316806 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"
@@ -2645,6 +2646,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_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae46070..c9aed0f9d2d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1629,6 +1629,10 @@
 { oid => '3996', descr => 'planner support for array_unnest',
   proname => 'array_unnest_support', prorettype => 'internal',
   proargtypes => 'internal', prosrc => 'array_unnest_support' },
+{ oid => '1293', descr => 'expand mutlirange to set of ranges',
+  proname => 'unnest', prorows => '100',
+  proretset => 't', prorettype => 'anyrange', proargtypes => 'anymultirange',
+  prosrc => 'multirange_unnest' },
 { oid => '3167',
   descr => 'remove any occurrences of an element from an array',
   proname => 'array_remove', proisstrict => 'f',
