From 7ae1619bc5b1794938c7387a766b8cae34e38d8a Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Wed, 30 Mar 2022 20:12:53 +0200 Subject: [PATCH] Add range_agg with multirange inputs range_agg for normal ranges already existed. A lot of code can be shared. Author: Paul Jungwirth Reviewed-by: Chapman Flack Discussion: https://www.postgresql.org/message-id/flat/007ef255-35ef-fd26-679c-f97e7a7f30c2@illuminatedcomputing.com --- doc/src/sgml/func.sgml | 5 ++ src/backend/utils/adt/multirangetypes.c | 61 +++++++++++++++++++ src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_aggregate.dat | 3 + src/include/catalog/pg_proc.dat | 11 ++++ src/test/regress/expected/multirangetypes.out | 61 +++++++++++++++++++ src/test/regress/expected/opr_sanity.out | 3 +- src/test/regress/sql/multirangetypes.sql | 12 ++++ 8 files changed, 156 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 8a5cc253f3b..4001cb2bda5 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -20007,6 +20007,11 @@ SELECT NULLIF(value, '(none)') ... anyrange ) anymultirange + + range_agg ( value + anymultirange ) + anymultirange + Computes the union of the non-null input values. diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c index 2fa779998ec..efd8584a3d8 100644 --- a/src/backend/utils/adt/multirangetypes.c +++ b/src/backend/utils/adt/multirangetypes.c @@ -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) { diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 96649193d9c..cb26c967adc 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202203291 +#define CATALOG_VERSION_NO 202203301 #endif diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat index 1934f19335b..62156346cf4 100644 --- a/src/include/catalog/pg_aggregate.dat +++ b/src/include/catalog/pg_aggregate.dat @@ -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', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 01e1dd4d6d1..25304430f44 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -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', diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out index 84c3e7c9f3b..ac2eb84c3af 100644 --- a/src/test/regress/expected/multirangetypes.out +++ b/src/test/regress/expected/multirangetypes.out @@ -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 -- diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 15e40168364..86d755aa443 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -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 diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql index fbc1b9282b0..1abcaeddb5c 100644 --- a/src/test/regress/sql/multirangetypes.sql +++ b/src/test/regress/sql/multirangetypes.sql @@ -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 --