Support for unnest(multirange)
authorAlexander Korotkov <akorotkov@postgresql.org>
Sun, 18 Jul 2021 18:07:24 +0000 (21:07 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Sun, 18 Jul 2021 18:07:24 +0000 (21:07 +0300)
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
doc/src/sgml/func.sgml
src/backend/utils/adt/multirangetypes.c
src/include/catalog/pg_proc.dat
src/test/regress/expected/multirangetypes.out
src/test/regress/sql/multirangetypes.sql

index ac6347a97115d44d682049caac4206aeff2e164f..4c1a2d794cc332e52de0cab1d227698feda1c62a 100644 (file)
@@ -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>
index 7aeec7617fc5ec9ab5a4ae217f969f5de3615abf..7076a60c32d7d120b2b339d12122ab59f72fff3d 100644 (file)
@@ -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 */
index 8bf9d704b70ef8ea0fa791ac3955c4dcad10f342..8cd025208245d0681f99db572b4ff449bc2df843 100644 (file)
   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' },
 
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
index af3ef4a258cd731b32ae6630379da0cc557b4bb7..7f689ad41b48037f71861f757bfa689ec2cff0e8 100644 (file)
@@ -346,6 +346,23 @@ select textrange(null, null)::textmultirange;
  {(,)}
 (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
 --
@@ -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.
 select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
 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;
  ?column? 
 ----------
index b91a23e0d5aa7e903e501411fb1d8cb2c8ef9a7f..ad50afc0f5537c5d6e63d70043167d4ff8025e1c 100644 (file)
@@ -77,6 +77,12 @@ select textrange('a', 'c')::textmultirange;
 select textrange('a', 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
 --
@@ -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(textrange1('a','Z')) @> 'b'::text;
+select unnest(multirange_of_text(textrange1('a','b'), textrange1('d','e')));
 select _textrange1(textrange2('a','z')) @> 'b'::text;
 
 drop type textrange1;