From 895ed0791769b8c93df2e1bd46ef6c61ab791c4e Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Thu, 5 Nov 2026 16:29:28 +0530 Subject: [PATCH v3 2/2] Fix NaN ordering in cube GiST searches An internal GiST key containing NaN can cover finite values. Returning NaN as its distance does not give a lower bound on the distances of its descendants, so nearest-neighbor scans can return rows out of order. Return negative infinity when an internal distance evaluates to NaN. Leaf entries still return the exact value of the ordering operator. Also use float8_min() and float8_max() when selecting coordinate bounds, as the ~> operator does. Bug: #19603 Reported-by: Aleksander Alekseev Author: Ayush Tiwari Discussion: https://postgr.es/m/19603-7b1f783d5bbfe791@postgresql.org --- contrib/cube/cube.c | 13 +++++++++---- contrib/cube/expected/cube.out | 22 ++++++++++++++++++++++ contrib/cube/sql/cube.sql | 13 +++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c index 95e57a4185b..edccc82889a 100644 --- a/contrib/cube/cube.c +++ b/contrib/cube/cube.c @@ -1448,9 +1448,9 @@ g_cube_distance(PG_FUNCTION_ARGS) { /* For leaf just return required upper/lower bound */ if (upper) - retval = Max(cube->x[index], cube->x[index + DIM(cube)]); + retval = float8_max(cube->x[index], cube->x[index + DIM(cube)]); else - retval = Min(cube->x[index], cube->x[index + DIM(cube)]); + retval = float8_min(cube->x[index], cube->x[index + DIM(cube)]); } else { @@ -1462,9 +1462,9 @@ g_cube_distance(PG_FUNCTION_ARGS) * inversed value. */ if (!inverse) - retval = Min(cube->x[index], cube->x[index + DIM(cube)]); + retval = float8_min(cube->x[index], cube->x[index + DIM(cube)]); else - retval = Max(cube->x[index], cube->x[index + DIM(cube)]); + retval = float8_max(cube->x[index], cube->x[index + DIM(cube)]); } } } @@ -1501,6 +1501,11 @@ g_cube_distance(PG_FUNCTION_ARGS) break; } } + + /* NaN does not bound the subtree's distances from below */ + if (!GIST_LEAF(entry) && isnan(retval)) + retval = -get_float8_infinity(); + PG_RETURN_FLOAT8(retval); } diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out index ac3870adcb4..dbf5f43ee31 100644 --- a/contrib/cube/expected/cube.out +++ b/contrib/cube/expected/cube.out @@ -1988,3 +1988,25 @@ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upp (15 rows) RESET enable_indexscan; +-- Opposite coordinate orders cannot both follow the index's page order. +TRUNCATE test_cube; +INSERT INTO test_cube +SELECT cube(ARRAY[number::float8, -number::float8], + ARRAY['NaN'::float8, 'NaN'::float8]) +FROM generate_series(1, 1000) AS numbers(number); +SET enable_seqscan = false; +SELECT ARRAY(SELECT c~>(-1) FROM test_cube ORDER BY c~>(-1)) = + ARRAY(SELECT generate_series(-1000, -1)::float8) AS correct_order; + correct_order +--------------- + t +(1 row) + +SELECT ARRAY(SELECT c~>(-3) FROM test_cube ORDER BY c~>(-3)) = + ARRAY(SELECT generate_series(1, 1000)::float8) AS correct_order; + correct_order +--------------- + t +(1 row) + +RESET enable_seqscan; diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql index 9e363ad8956..9f4bb25b149 100644 --- a/contrib/cube/sql/cube.sql +++ b/contrib/cube/sql/cube.sql @@ -443,3 +443,16 @@ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by rig SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound RESET enable_indexscan; + +-- Opposite coordinate orders cannot both follow the index's page order. +TRUNCATE test_cube; +INSERT INTO test_cube +SELECT cube(ARRAY[number::float8, -number::float8], + ARRAY['NaN'::float8, 'NaN'::float8]) +FROM generate_series(1, 1000) AS numbers(number); +SET enable_seqscan = false; +SELECT ARRAY(SELECT c~>(-1) FROM test_cube ORDER BY c~>(-1)) = + ARRAY(SELECT generate_series(-1000, -1)::float8) AS correct_order; +SELECT ARRAY(SELECT c~>(-3) FROM test_cube ORDER BY c~>(-3)) = + ARRAY(SELECT generate_series(1, 1000)::float8) AS correct_order; +RESET enable_seqscan; -- 2.34.1