mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-27 04:47:18 +02:00
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 a7de3dc5c3
. 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
This commit is contained in:
parent
7c84bc0b3d
commit
c253b722f6
@ -1064,6 +1064,17 @@ advance_combine_function(AggState *aggstate,
|
|||||||
pergroupstate->noTransValue = false;
|
pergroupstate->noTransValue = false;
|
||||||
return;
|
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 */
|
/* We run the combine functions in per-input-tuple memory context */
|
||||||
|
@ -1974,3 +1974,76 @@ NOTICE: sum_transfn called with 4
|
|||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
rollback;
|
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;
|
||||||
|
SET LOCAL enable_indexscan = off;
|
||||||
|
SET LOCAL enable_bitmapscan = off;
|
||||||
|
EXPLAIN (COSTS OFF) SELECT balk(1) FROM tenk1;
|
||||||
|
QUERY PLAN
|
||||||
|
----------------------------------------------
|
||||||
|
Finalize Aggregate
|
||||||
|
-> Gather
|
||||||
|
Workers Planned: 4
|
||||||
|
-> Partial Aggregate
|
||||||
|
-> Parallel Seq Scan on tenk1
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
SELECT balk(1) FROM tenk1;
|
||||||
|
balk
|
||||||
|
------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
|
@ -835,3 +835,68 @@ create aggregate my_half_sum(int4)
|
|||||||
select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one);
|
select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one);
|
||||||
|
|
||||||
rollback;
|
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;
|
||||||
|
SET LOCAL enable_indexscan = off;
|
||||||
|
SET LOCAL enable_bitmapscan = off;
|
||||||
|
|
||||||
|
EXPLAIN (COSTS OFF) SELECT balk(1) FROM tenk1;
|
||||||
|
SELECT balk(1) FROM tenk1;
|
||||||
|
|
||||||
|
ROLLBACK;
|
||||||
|
Loading…
Reference in New Issue
Block a user