Commit 244ad541 authored by Alexander Korotkov's avatar Alexander Korotkov

Support for unnest(multirange)

It has been spotted that multiranges lack of ability to decompose them into
individual ranges.  Subscription and proper expanded object representation
require substantial work, and it's too late for v14.  This commit
provides the implementation of unnest(multirange), which is quite trivial.
unnest(multirange) is defined as a polymorphic procedure.

Catversion is bumped.

Reported-by: Jonathan S. Katz
Discussion: https://postgr.es/m/flat/60258efe-bd7e-4886-82e1-196e0cac5433%40postgresql.org
Author: Alexander Korotkov
Reviewed-by: Justin Pryzby, Jonathan S. Katz, Zhihong Yu, Tom Lane
Reviewed-by: Alvaro Herrera
parent d8f3b021
...@@ -19181,6 +19181,29 @@ SELECT NULLIF(value, '(none)') ... ...@@ -19181,6 +19181,29 @@ SELECT NULLIF(value, '(none)') ...
<returnvalue>{[1,2)}</returnvalue> <returnvalue>{[1,2)}</returnvalue>
</para></entry> </para></entry>
</row> </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> </tbody>
</tgroup> </tgroup>
</table> </table>
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
#include "access/tupmacs.h" #include "access/tupmacs.h"
#include "common/hashfn.h" #include "common/hashfn.h"
#include "funcapi.h"
#include "lib/stringinfo.h" #include "lib/stringinfo.h"
#include "libpq/pqformat.h" #include "libpq/pqformat.h"
#include "miscadmin.h" #include "miscadmin.h"
...@@ -2645,6 +2646,78 @@ range_merge_from_multirange(PG_FUNCTION_ARGS) ...@@ -2645,6 +2646,78 @@ range_merge_from_multirange(PG_FUNCTION_ARGS)
PG_RETURN_RANGE_P(result); 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 support */
/* hash a multirange value */ /* hash a multirange value */
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 202106151 #define CATALOG_VERSION_NO 202107181
#endif #endif
...@@ -10537,6 +10537,10 @@ ...@@ -10537,6 +10537,10 @@
proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f', proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
prorettype => 'anymultirange', proargtypes => 'anymultirange', prorettype => 'anymultirange', proargtypes => 'anymultirange',
prosrc => 'aggregate_dummy' }, 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' },
# date, time, timestamp constructors # date, time, timestamp constructors
{ oid => '3846', descr => 'construct date', { oid => '3846', descr => 'construct date',
......
...@@ -346,6 +346,23 @@ select textrange(null, null)::textmultirange; ...@@ -346,6 +346,23 @@ select textrange(null, null)::textmultirange;
{(,)} {(,)}
(1 row) (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 -- create some test data and test the operators
-- --
...@@ -2938,6 +2955,13 @@ LINE 1: select multirange_of_text(textrange2('a','Z')); ...@@ -2938,6 +2955,13 @@ 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. 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; select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
ERROR: range lower bound must be less than or equal to range upper bound ERROR: range lower bound must be less than or equal to range upper bound
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; select _textrange1(textrange2('a','z')) @> 'b'::text;
?column? ?column?
---------- ----------
......
...@@ -77,6 +77,12 @@ select textrange('a', 'c')::textmultirange; ...@@ -77,6 +77,12 @@ select textrange('a', 'c')::textmultirange;
select textrange('a', null)::textmultirange; select textrange('a', null)::textmultirange;
select textrange(null, null)::textmultirange; select textrange(null, null)::textmultirange;
--
-- 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 -- create some test data and test the operators
-- --
...@@ -658,6 +664,7 @@ create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, ...@@ -658,6 +664,7 @@ create type textrange2 as range(subtype=text, multirange_type_name=_textrange1,
select multirange_of_text(textrange2('a','Z')); -- should fail select multirange_of_text(textrange2('a','Z')); -- should fail
select multirange_of_text(textrange1('a','Z')) @> 'b'::text; select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
select unnest(multirange_of_text(textrange1('a','b'), textrange1('d','e')));
select _textrange1(textrange2('a','z')) @> 'b'::text; select _textrange1(textrange2('a','z')) @> 'b'::text;
drop type textrange1; drop type textrange1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment