Add range_agg with multirange inputs
authorPeter Eisentraut <peter@eisentraut.org>
Wed, 30 Mar 2022 18:12:53 +0000 (20:12 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Wed, 30 Mar 2022 18:16:23 +0000 (20:16 +0200)
range_agg for normal ranges already existed.  A lot of code can be
shared.

Author: Paul Jungwirth <pj@illuminatedcomputing.com>
Reviewed-by: Chapman Flack <chap@anastigmatix.net>
Discussion: https://www.postgresql.org/message-id/flat/007ef255-35ef-fd26-679c-f97e7a7f30c2@illuminatedcomputing.com

doc/src/sgml/func.sgml
src/backend/utils/adt/multirangetypes.c
src/include/catalog/catversion.h
src/include/catalog/pg_aggregate.dat
src/include/catalog/pg_proc.dat
src/test/regress/expected/multirangetypes.out
src/test/regress/expected/opr_sanity.out
src/test/regress/sql/multirangetypes.sql

index 8a5cc253f3b9f83197bf8f3572aab764495f7348..4001cb2bda5afaffb9b09e01b57d62dd3b2e05d6 100644 (file)
@@ -20007,6 +20007,11 @@ SELECT NULLIF(value, '(none)') ...
          <type>anyrange</type> )
         <returnvalue>anymultirange</returnvalue>
        </para>
+       <para role="func_signature">
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anymultirange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
        <para>
         Computes the union of the non-null input values.
        </para></entry>
index 2fa779998ec370a703c8ef1502697c29ecd66ca0..efd8584a3d87381321ff0bf6b694cfa42102795a 100644 (file)
@@ -1361,6 +1361,9 @@ range_agg_transfn(PG_FUNCTION_ARGS)
 
 /*
  * range_agg_finalfn: use our internal array to merge touching ranges.
+ *
+ * Shared by range_agg_finalfn(anyrange) and
+ * multirange_agg_finalfn(anymultirange).
  */
 Datum
 range_agg_finalfn(PG_FUNCTION_ARGS)
@@ -1396,6 +1399,64 @@ range_agg_finalfn(PG_FUNCTION_ARGS)
    PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
 }
 
+/*
+ * multirange_agg_transfn: combine adjacent/overlapping multiranges.
+ *
+ * All we do here is gather the input multiranges' ranges into an array so
+ * that the finalfn can sort and combine them.
+ */
+Datum
+multirange_agg_transfn(PG_FUNCTION_ARGS)
+{
+   MemoryContext aggContext;
+   Oid         mltrngtypoid;
+   TypeCacheEntry *typcache;
+   TypeCacheEntry *rngtypcache;
+   ArrayBuildState *state;
+
+   if (!AggCheckCallContext(fcinfo, &aggContext))
+       elog(ERROR, "multirange_agg_transfn called in non-aggregate context");
+
+   mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+   if (!type_is_multirange(mltrngtypoid))
+       elog(ERROR, "range_agg must be called with a multirange");
+
+   typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+   rngtypcache = typcache->rngtype;
+
+   if (PG_ARGISNULL(0))
+       state = initArrayResult(rngtypcache->type_id, aggContext, false);
+   else
+       state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+   /* skip NULLs */
+   if (!PG_ARGISNULL(1))
+   {
+       MultirangeType *current;
+       int32       range_count;
+       RangeType **ranges;
+
+       current = PG_GETARG_MULTIRANGE_P(1);
+       multirange_deserialize(rngtypcache, current, &range_count, &ranges);
+       if (range_count == 0)
+       {
+           /*
+            * Add an empty range so we get an empty result (not a null result).
+            */
+           accumArrayResult(state,
+                            RangeTypePGetDatum(make_empty_range(rngtypcache)),
+                            false, rngtypcache->type_id, aggContext);
+       }
+       else
+       {
+           for (int32 i = 0; i < range_count; i++)
+               accumArrayResult(state, RangeTypePGetDatum(ranges[i]), false, rngtypcache->type_id, aggContext);
+       }
+   }
+
+   PG_RETURN_POINTER(state);
+}
+
 Datum
 multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
 {
index 96649193d9c8039394dfc92ef6ac5ae9eb734f59..cb26c967adcb211fa4fa99ab4db7902e9199cbb3 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202203291
+#define CATALOG_VERSION_NO 202203301
 
 #endif
index 1934f19335b28e2c8633225bbaa2b91d6cfb4263..62156346cf4f6f084928b05230f53e18bd487c1b 100644 (file)
 { aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
   aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
   aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(anymultirange)', aggtransfn => 'multirange_agg_transfn',
+  aggfinalfn => 'multirange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
 
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
index 01e1dd4d6d17d9613623d33c48c09fd737c807a2..25304430f44e193d99bbc8bd04da3c5efc008ca5 100644 (file)
   proname => 'range_agg', prokind => 'a', proisstrict => 'f',
   prorettype => 'anymultirange', proargtypes => 'anyrange',
   prosrc => 'aggregate_dummy' },
+{ oid => '8205', descr => 'aggregate transition function',
+  proname => 'multirange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anymultirange', prosrc => 'multirange_agg_transfn' },
+{ oid => '8206', descr => 'aggregate final function',
+  proname => 'multirange_agg_finalfn', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'internal anymultirange',
+  prosrc => 'range_agg_finalfn' },
+{ oid => '8207', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy' },
 { oid => '4388', descr => 'range aggregate by intersecting',
   proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
   proargtypes => 'anymultirange anymultirange',
index 84c3e7c9f3bfe4717a76881bba5344156d6d5b51..ac2eb84c3af90db38a14609749a5607653d0b26e 100644 (file)
@@ -2784,6 +2784,67 @@ FROM    (VALUES
  {[a,f],[g,j)}
 (1 row)
 
+-- range_agg with multirange inputs
+select range_agg(nmr) from nummultirange_test;
+ range_agg 
+-----------
+ {(,)}
+(1 row)
+
+select range_agg(nmr) from nummultirange_test where false;
+ range_agg 
+-----------
+(1 row)
+
+select range_agg(null::nummultirange) from nummultirange_test;
+ range_agg 
+-----------
+(1 row)
+
+select range_agg(nmr) from (values ('{}'::nummultirange)) t(nmr);
+ range_agg 
+-----------
+ {}
+(1 row)
+
+select range_agg(nmr) from (values ('{}'::nummultirange), ('{}'::nummultirange)) t(nmr);
+ range_agg 
+-----------
+ {}
+(1 row)
+
+select range_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_agg 
+-----------
+ {[1,2]}
+(1 row)
+
+select range_agg(nmr) from (values ('{[1,2], [5,6]}'::nummultirange)) t(nmr);
+   range_agg   
+---------------
+ {[1,2],[5,6]}
+(1 row)
+
+select range_agg(nmr) from (values ('{[1,2], [2,3]}'::nummultirange)) t(nmr);
+ range_agg 
+-----------
+ {[1,3]}
+(1 row)
+
+select range_agg(nmr) from (values ('{[1,2]}'::nummultirange), ('{[5,6]}'::nummultirange)) t(nmr);
+   range_agg   
+---------------
+ {[1,2],[5,6]}
+(1 row)
+
+select range_agg(nmr) from (values ('{[1,2]}'::nummultirange), ('{[2,3]}'::nummultirange)) t(nmr);
+ range_agg 
+-----------
+ {[1,3]}
+(1 row)
+
 --
 -- range_intersect_agg function
 --
index 15e40168364c71174074f04fe5fde2c4f42dc0df..86d755aa44368907d193f0eb3ce059f6e90715e1 100644 (file)
@@ -201,7 +201,8 @@ ORDER BY 1, 2;
  timestamp without time zone | timestamp with time zone
  bit                         | bit varying
  txid_snapshot               | pg_snapshot
-(4 rows)
+ anyrange                    | anymultirange
+(5 rows)
 
 SELECT DISTINCT p1.proargtypes[2]::regtype, p2.proargtypes[2]::regtype
 FROM pg_proc AS p1, pg_proc AS p2
index fbc1b9282b071b708b2bcd9573c6aa033e5a6f71..1abcaeddb5c490798f58e29fe8211e694d8108cb 100644 (file)
@@ -572,6 +572,18 @@ FROM    (VALUES
           ('[h,j)'::textrange)
         ) t(r);
 
+-- range_agg with multirange inputs
+select range_agg(nmr) from nummultirange_test;
+select range_agg(nmr) from nummultirange_test where false;
+select range_agg(null::nummultirange) from nummultirange_test;
+select range_agg(nmr) from (values ('{}'::nummultirange)) t(nmr);
+select range_agg(nmr) from (values ('{}'::nummultirange), ('{}'::nummultirange)) t(nmr);
+select range_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_agg(nmr) from (values ('{[1,2], [5,6]}'::nummultirange)) t(nmr);
+select range_agg(nmr) from (values ('{[1,2], [2,3]}'::nummultirange)) t(nmr);
+select range_agg(nmr) from (values ('{[1,2]}'::nummultirange), ('{[5,6]}'::nummultirange)) t(nmr);
+select range_agg(nmr) from (values ('{[1,2]}'::nummultirange), ('{[2,3]}'::nummultirange)) t(nmr);
+
 --
 -- range_intersect_agg function
 --