Add range_agg with multirange inputs

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
This commit is contained in:
Peter Eisentraut 2022-03-30 20:12:53 +02:00
parent ff50baec65
commit 7ae1619bc5
8 changed files with 156 additions and 2 deletions

View 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>

View 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)
{

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202203291
#define CATALOG_VERSION_NO 202203301
#endif

View File

@ -563,6 +563,9 @@
{ 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',

View File

@ -10688,6 +10688,17 @@
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',

View 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
--

View 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

View 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
--