Skip to content

Commit c253b72

Browse files
committed
Fix handling of NULLs returned by aggregate combine functions.
When strict aggregate combine functions, used in multi-stage/parallel aggregation, returned NULL, we didn't check for that, invoking the combine function with NULL the next round, despite it being strict. The equivalent code invoking normal transition functions has a check for that situation, which did not get copied in a7de3dc. Fix the bug by adding the equivalent check. Based on a quick look I could not find any strict combine functions in core actually returning NULL, and it doesn't seem very likely external users have done so. So this isn't likely to have caused issues in practice. Add tests verifying transition / combine functions returning NULL is tested. Reported-By: Andres Freund Author: Andres Freund Discussion: https://postgr.es/m/20171121033642.7xvmjqrl4jdaaat3@alap3.anarazel.de Backpatch: 9.6, where parallel aggregation was introduced
1 parent 7c84bc0 commit c253b72

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed

src/backend/executor/nodeAgg.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,17 @@ advance_combine_function(AggState *aggstate,
10641064
pergroupstate->noTransValue = false;
10651065
return;
10661066
}
1067+
1068+
if (pergroupstate->transValueIsNull)
1069+
{
1070+
/*
1071+
* Don't call a strict function with NULL inputs. Note it is
1072+
* possible to get here despite the above tests, if the combinefn
1073+
* is strict *and* returned a NULL on a prior cycle. If that
1074+
* happens we will propagate the NULL all the way to the end.
1075+
*/
1076+
return;
1077+
}
10671078
}
10681079

10691080
/* We run the combine functions in per-input-tuple memory context */

src/test/regress/expected/aggregates.out

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,3 +1974,76 @@ NOTICE: sum_transfn called with 4
19741974
(1 row)
19751975

19761976
rollback;
1977+
-- test that the aggregate transition logic correctly handles
1978+
-- transition / combine functions returning NULL
1979+
-- First test the case of a normal transition function returning NULL
1980+
BEGIN;
1981+
CREATE FUNCTION balkifnull(int8, int4)
1982+
RETURNS int8
1983+
STRICT
1984+
LANGUAGE plpgsql AS $$
1985+
BEGIN
1986+
IF $1 IS NULL THEN
1987+
RAISE 'erroneously called with NULL argument';
1988+
END IF;
1989+
RETURN NULL;
1990+
END$$;
1991+
CREATE AGGREGATE balk(
1992+
BASETYPE = int4,
1993+
SFUNC = balkifnull(int8, int4),
1994+
STYPE = int8,
1995+
"PARALLEL" = SAFE,
1996+
INITCOND = '0');
1997+
SELECT balk(1) FROM tenk1;
1998+
balk
1999+
------
2000+
2001+
(1 row)
2002+
2003+
ROLLBACK;
2004+
-- Secondly test the case of a parallel aggregate combiner function
2005+
-- returning NULL. For that use normal transition function, but a
2006+
-- combiner function returning NULL.
2007+
BEGIN ISOLATION LEVEL REPEATABLE READ;
2008+
CREATE FUNCTION balkifnull(int8, int8)
2009+
RETURNS int8
2010+
PARALLEL SAFE
2011+
STRICT
2012+
LANGUAGE plpgsql AS $$
2013+
BEGIN
2014+
IF $1 IS NULL THEN
2015+
RAISE 'erroneously called with NULL argument';
2016+
END IF;
2017+
RETURN NULL;
2018+
END$$;
2019+
CREATE AGGREGATE balk(
2020+
BASETYPE = int4,
2021+
SFUNC = int4_sum(int8, int4),
2022+
STYPE = int8,
2023+
COMBINEFUNC = balkifnull(int8, int8),
2024+
"PARALLEL" = SAFE,
2025+
INITCOND = '0'
2026+
);
2027+
-- force use of parallelism
2028+
ALTER TABLE tenk1 set (parallel_workers = 4);
2029+
SET LOCAL parallel_setup_cost=0;
2030+
SET LOCAL max_parallel_workers_per_gather=4;
2031+
SET LOCAL enable_indexscan = off;
2032+
SET LOCAL enable_bitmapscan = off;
2033+
EXPLAIN (COSTS OFF) SELECT balk(1) FROM tenk1;
2034+
QUERY PLAN
2035+
----------------------------------------------
2036+
Finalize Aggregate
2037+
-> Gather
2038+
Workers Planned: 4
2039+
-> Partial Aggregate
2040+
-> Parallel Seq Scan on tenk1
2041+
(5 rows)
2042+
2043+
SELECT balk(1) FROM tenk1;
2044+
balk
2045+
------
2046+
2047+
(1 row)
2048+
2049+
ROLLBACK;

src/test/regress/sql/aggregates.sql

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,3 +835,68 @@ create aggregate my_half_sum(int4)
835835
select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one);
836836

837837
rollback;
838+
839+
840+
-- test that the aggregate transition logic correctly handles
841+
-- transition / combine functions returning NULL
842+
843+
-- First test the case of a normal transition function returning NULL
844+
BEGIN;
845+
CREATE FUNCTION balkifnull(int8, int4)
846+
RETURNS int8
847+
STRICT
848+
LANGUAGE plpgsql AS $$
849+
BEGIN
850+
IF $1 IS NULL THEN
851+
RAISE 'erroneously called with NULL argument';
852+
END IF;
853+
RETURN NULL;
854+
END$$;
855+
856+
CREATE AGGREGATE balk(
857+
BASETYPE = int4,
858+
SFUNC = balkifnull(int8, int4),
859+
STYPE = int8,
860+
"PARALLEL" = SAFE,
861+
INITCOND = '0');
862+
863+
SELECT balk(1) FROM tenk1;
864+
865+
ROLLBACK;
866+
867+
-- Secondly test the case of a parallel aggregate combiner function
868+
-- returning NULL. For that use normal transition function, but a
869+
-- combiner function returning NULL.
870+
BEGIN ISOLATION LEVEL REPEATABLE READ;
871+
CREATE FUNCTION balkifnull(int8, int8)
872+
RETURNS int8
873+
PARALLEL SAFE
874+
STRICT
875+
LANGUAGE plpgsql AS $$
876+
BEGIN
877+
IF $1 IS NULL THEN
878+
RAISE 'erroneously called with NULL argument';
879+
END IF;
880+
RETURN NULL;
881+
END$$;
882+
883+
CREATE AGGREGATE balk(
884+
BASETYPE = int4,
885+
SFUNC = int4_sum(int8, int4),
886+
STYPE = int8,
887+
COMBINEFUNC = balkifnull(int8, int8),
888+
"PARALLEL" = SAFE,
889+
INITCOND = '0'
890+
);
891+
892+
-- force use of parallelism
893+
ALTER TABLE tenk1 set (parallel_workers = 4);
894+
SET LOCAL parallel_setup_cost=0;
895+
SET LOCAL max_parallel_workers_per_gather=4;
896+
SET LOCAL enable_indexscan = off;
897+
SET LOCAL enable_bitmapscan = off;
898+
899+
EXPLAIN (COSTS OFF) SELECT balk(1) FROM tenk1;
900+
SELECT balk(1) FROM tenk1;
901+
902+
ROLLBACK;

0 commit comments

Comments
 (0)