From 6fb6c8ac5951f87b0569696f6db4fbc89fe7fceb Mon Sep 17 00:00:00 2001 From: Alexander Kuzmenkov <36882414+akuzm@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:04:35 +0200 Subject: [PATCH 1/3] Fix NaN handling in min/max in columnar aggregation pipeline It's diffrent from other functions, they ignore NaNs. We fixed this before but missed some cases in https://github.com/timescale/timescaledb/pull/7584 --- .../function/minmax_arithmetic_single.c | 2 +- tsl/test/expected/vector_agg_functions.out | 15 +++++++++++++++ tsl/test/sql/vector_agg_functions.sql | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tsl/src/nodes/vector_agg/function/minmax_arithmetic_single.c b/tsl/src/nodes/vector_agg/function/minmax_arithmetic_single.c index 605f1bc1c32..4e1e73f42c8 100644 --- a/tsl/src/nodes/vector_agg/function/minmax_arithmetic_single.c +++ b/tsl/src/nodes/vector_agg/function/minmax_arithmetic_single.c @@ -46,7 +46,7 @@ static pg_attribute_always_inline void FUNCTION_NAME(one)(void *restrict agg_state, const CTYPE value) { FUNCTION_NAME(state) *state = (FUNCTION_NAME(state) *) agg_state; - if (!state->isvalid || PREDICATE(DATUM_TO_CTYPE(state->value), value) || isnan((double) value)) + if (!state->isvalid || PREDICATE(DATUM_TO_CTYPE(state->value), value)) { /* * Note that float8 Datum is by-reference on 32-bit systems, and this diff --git a/tsl/test/expected/vector_agg_functions.out b/tsl/test/expected/vector_agg_functions.out index 8ffe1117327..4dc6f058218 100644 --- a/tsl/test/expected/vector_agg_functions.out +++ b/tsl/test/expected/vector_agg_functions.out @@ -7158,4 +7158,19 @@ select ss, count(*), min(f1) from edges where f1 = 65 group by 1 order by 1; 11 | 1 | 65 12 | 1 | 65 +-- NaN handling in minmax is different from other functions. +select s, min(cfloat4), max(cfloat4) from aggfns group by s order by s; + s | min | max +---+-----------+---------- + 0 | -49.9949 | 49.9995 + 1 | -49.9974 | NaN + 2 | -49.9991 | Infinity + 3 | -Infinity | 49.9979 + 4 | -49.9999 | 49.9946 + 5 | -49.9942 | 49.9992 + 6 | -49.9995 | 49.9956 + 7 | -49.9984 | 49.9969 + 8 | -49.9969 | 49.9997 + 9 | -49.9911 | 49.9899 + reset max_parallel_workers_per_gather; diff --git a/tsl/test/sql/vector_agg_functions.sql b/tsl/test/sql/vector_agg_functions.sql index 87654b6e078..d04d95bcfcd 100644 --- a/tsl/test/sql/vector_agg_functions.sql +++ b/tsl/test/sql/vector_agg_functions.sql @@ -198,4 +198,7 @@ select ss, count(*), min(f1) from edges where f1 = 63 group by 1 order by 1; select ss, count(*), min(f1) from edges where f1 = 64 group by 1 order by 1; select ss, count(*), min(f1) from edges where f1 = 65 group by 1 order by 1; +-- NaN handling in minmax is different from other functions. +select s, min(cfloat4), max(cfloat4) from aggfns group by s order by s; + reset max_parallel_workers_per_gather; From d6a9d8186c758f8b925702af0dd75d3748517ec8 Mon Sep 17 00:00:00 2001 From: Alexander Kuzmenkov <36882414+akuzm@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:36:47 +0200 Subject: [PATCH 2/3] fix the test --- tsl/test/expected/vector_agg_functions.out | 22 ++++++++-------------- tsl/test/sql/vector_agg_functions.sql | 7 +++++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/tsl/test/expected/vector_agg_functions.out b/tsl/test/expected/vector_agg_functions.out index 4dc6f058218..815e079c453 100644 --- a/tsl/test/expected/vector_agg_functions.out +++ b/tsl/test/expected/vector_agg_functions.out @@ -7158,19 +7158,13 @@ select ss, count(*), min(f1) from edges where f1 = 65 group by 1 order by 1; 11 | 1 | 65 12 | 1 | 65 --- NaN handling in minmax is different from other functions. -select s, min(cfloat4), max(cfloat4) from aggfns group by s order by s; - s | min | max ----+-----------+---------- - 0 | -49.9949 | 49.9995 - 1 | -49.9974 | NaN - 2 | -49.9991 | Infinity - 3 | -Infinity | 49.9979 - 4 | -49.9999 | 49.9946 - 5 | -49.9942 | 49.9992 - 6 | -49.9995 | 49.9956 - 7 | -49.9984 | 49.9969 - 8 | -49.9969 | 49.9997 - 9 | -49.9911 | 49.9899 +-- NaN handling in Postgres minmax is different from other functions -- it treats +-- NaN as bigger than any number. This test has a filter that ensures that +-- NaNs go first in the source data, because some potentially buggy conditions +-- can be only triggered in this case. +select ss, min(cfloat4), max(cfloat4) from aggfns where cfloat4 = any(array['nan'::float4, 33.308792]) group by ss order by ss; + ss | min | max +----+---------+----- + 11 | 33.3088 | NaN reset max_parallel_workers_per_gather; diff --git a/tsl/test/sql/vector_agg_functions.sql b/tsl/test/sql/vector_agg_functions.sql index d04d95bcfcd..da682cd3a32 100644 --- a/tsl/test/sql/vector_agg_functions.sql +++ b/tsl/test/sql/vector_agg_functions.sql @@ -198,7 +198,10 @@ select ss, count(*), min(f1) from edges where f1 = 63 group by 1 order by 1; select ss, count(*), min(f1) from edges where f1 = 64 group by 1 order by 1; select ss, count(*), min(f1) from edges where f1 = 65 group by 1 order by 1; --- NaN handling in minmax is different from other functions. -select s, min(cfloat4), max(cfloat4) from aggfns group by s order by s; +-- NaN handling in Postgres minmax is different from other functions -- it treats +-- NaN as bigger than any number. This test has a filter that ensures that +-- NaNs go first in the source data, because some potentially buggy conditions +-- can be only triggered in this case. +select ss, min(cfloat4), max(cfloat4) from aggfns where cfloat4 = any(array['nan'::float4, 33.308792]) group by ss order by ss; reset max_parallel_workers_per_gather; From d85f1d0f7f9dee2f7b835deea65b77d3766eeab4 Mon Sep 17 00:00:00 2001 From: Alexander Kuzmenkov <36882414+akuzm@users.noreply.github.com> Date: Thu, 25 Jun 2026 13:33:31 +0200 Subject: [PATCH 3/3] add changelog --- .unreleased/min-nan | 1 + 1 file changed, 1 insertion(+) create mode 100644 .unreleased/min-nan diff --git a/.unreleased/min-nan b/.unreleased/min-nan new file mode 100644 index 00000000000..023f6c978b1 --- /dev/null +++ b/.unreleased/min-nan @@ -0,0 +1 @@ +Fixes: #10052 Result of min/max aggregate functions in columnar aggregation pipeline possibly inconsistent with plain Postgres result