diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 448fb771a9a49559603d491238834765ba8614ea..010ef1cd5586498f94d29228255a2745516afed0 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -1238,6 +1238,17 @@ advance_combine_function(AggState *aggstate, pergroupstate->noTransValue = false; return; } + + if (pergroupstate->transValueIsNull) + { + /* + * Don't call a strict function with NULL inputs. Note it is + * possible to get here despite the above tests, if the combinefn + * is strict *and* returned a NULL on a prior cycle. If that + * happens we will propagate the NULL all the way to the end. + */ + return; + } } /* We run the combine functions in per-input-tuple memory context */ diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index c4ea86ff05008c092c57b1d2daa9436c2623e0ff..56d7b20a0faacdf2fc417a43f8b639e36cdf3449 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -1983,3 +1983,74 @@ NOTICE: sum_transfn called with 4 (1 row) rollback; +-- test that the aggregate transition logic correctly handles +-- transition / combine functions returning NULL +-- First test the case of a normal transition function returning NULL +BEGIN; +CREATE FUNCTION balkifnull(int8, int4) +RETURNS int8 +STRICT +LANGUAGE plpgsql AS $$ +BEGIN + IF $1 IS NULL THEN + RAISE 'erroneously called with NULL argument'; + END IF; + RETURN NULL; +END$$; +CREATE AGGREGATE balk( + BASETYPE = int4, + SFUNC = balkifnull(int8, int4), + STYPE = int8, + "PARALLEL" = SAFE, + INITCOND = '0'); +SELECT balk(1) FROM tenk1; + balk +------ + +(1 row) + +ROLLBACK; +-- Secondly test the case of a parallel aggregate combiner function +-- returning NULL. For that use normal transition function, but a +-- combiner function returning NULL. +BEGIN ISOLATION LEVEL REPEATABLE READ; +CREATE FUNCTION balkifnull(int8, int8) +RETURNS int8 +PARALLEL SAFE +STRICT +LANGUAGE plpgsql AS $$ +BEGIN + IF $1 IS NULL THEN + RAISE 'erroneously called with NULL argument'; + END IF; + RETURN NULL; +END$$; +CREATE AGGREGATE balk( + BASETYPE = int4, + SFUNC = int4_sum(int8, int4), + STYPE = int8, + COMBINEFUNC = balkifnull(int8, int8), + "PARALLEL" = SAFE, + INITCOND = '0' +); +-- force use of parallelism +ALTER TABLE tenk1 set (parallel_workers = 4); +SET LOCAL parallel_setup_cost=0; +SET LOCAL max_parallel_workers_per_gather=4; +EXPLAIN (COSTS OFF) SELECT balk(1) FROM tenk1; + QUERY PLAN +-------------------------------------------------------------------------------- + Finalize Aggregate + -> Gather + Workers Planned: 4 + -> Partial Aggregate + -> Parallel Index Only Scan using tenk1_thous_tenthous on tenk1 +(5 rows) + +SELECT balk(1) FROM tenk1; + balk +------ + +(1 row) + +ROLLBACK; diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index fefbef89e08907ded2cefd2798a129b975ec302d..b216808b3e2eca7ca0d4414de6e1d41ee4367e92 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -837,3 +837,66 @@ create aggregate my_half_sum(int4) select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one); rollback; + + +-- test that the aggregate transition logic correctly handles +-- transition / combine functions returning NULL + +-- First test the case of a normal transition function returning NULL +BEGIN; +CREATE FUNCTION balkifnull(int8, int4) +RETURNS int8 +STRICT +LANGUAGE plpgsql AS $$ +BEGIN + IF $1 IS NULL THEN + RAISE 'erroneously called with NULL argument'; + END IF; + RETURN NULL; +END$$; + +CREATE AGGREGATE balk( + BASETYPE = int4, + SFUNC = balkifnull(int8, int4), + STYPE = int8, + "PARALLEL" = SAFE, + INITCOND = '0'); + +SELECT balk(1) FROM tenk1; + +ROLLBACK; + +-- Secondly test the case of a parallel aggregate combiner function +-- returning NULL. For that use normal transition function, but a +-- combiner function returning NULL. +BEGIN ISOLATION LEVEL REPEATABLE READ; +CREATE FUNCTION balkifnull(int8, int8) +RETURNS int8 +PARALLEL SAFE +STRICT +LANGUAGE plpgsql AS $$ +BEGIN + IF $1 IS NULL THEN + RAISE 'erroneously called with NULL argument'; + END IF; + RETURN NULL; +END$$; + +CREATE AGGREGATE balk( + BASETYPE = int4, + SFUNC = int4_sum(int8, int4), + STYPE = int8, + COMBINEFUNC = balkifnull(int8, int8), + "PARALLEL" = SAFE, + INITCOND = '0' +); + +-- force use of parallelism +ALTER TABLE tenk1 set (parallel_workers = 4); +SET LOCAL parallel_setup_cost=0; +SET LOCAL max_parallel_workers_per_gather=4; + +EXPLAIN (COSTS OFF) SELECT balk(1) FROM tenk1; +SELECT balk(1) FROM tenk1; + +ROLLBACK;