From 6ce3c4bcccffb9b093567884ae41a103972d9e30 Mon Sep 17 00:00:00 2001 From: Shihao Date: Sun, 27 Sep 2026 09:26:57 -0700 Subject: [PATCH v6 4/4] Fix GiST KNN searches with a NaN in a key or the query point computeDistance() cannot place a point next to a box when either has a NaN. It raised "inconsistent point values", failed an assertion, or ranked the row too early. Use distance 0 there instead. Leaf keys of box_ops must be exact, so they use the operator. Reported-by: Manu Discussion: https://postgr.es/m/19705-548fda77321e062d@postgresql.org --- src/backend/access/gist/gistproc.c | 30 ++++++++++++-- src/test/regress/expected/gist.out | 65 ++++++++++++++++++++++++++++++ src/test/regress/sql/gist.sql | 29 +++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c index f9160dc2bee..d45cc4b310d 100644 --- a/src/backend/access/gist/gistproc.c +++ b/src/backend/access/gist/gistproc.c @@ -1356,6 +1356,17 @@ computeDistance(bool isLeaf, BOX *box, Point *point) return result; } +/* + * computeDistance() places the point in one of the regions around the box, + * which a NaN in either of them makes impossible. Callers use 0 instead, the + * only lower bound that is always safe. + */ +static inline bool +distance_has_nan(const BOX *box, const Point *point) +{ + return box_has_nan(box) || isnan(point->x) || isnan(point->y); +} + static bool gist_point_consistent_internal(StrategyNumber strategy, bool isLeaf, BOX *key, Point *query) @@ -1566,8 +1577,10 @@ gist_point_distance(PG_FUNCTION_ARGS) switch (strategyGroup) { case PointStrategyNumberGroup: - /* A NaN internal key tells us nothing, see box_has_nan() */ - if (nan_internal_key(entry)) + /* Leaf keys are points, which point_distance() handles */ + if (!GIST_LEAF(entry) && + distance_has_nan(DatumGetBoxP(entry->key), + PG_GETARG_POINT_P(1))) distance = 0.0; else distance = computeDistance(GIST_LEAF(entry), @@ -1592,8 +1605,8 @@ gist_bbox_distance(GISTENTRY *entry, Datum query, StrategyNumber strategy) switch (strategyGroup) { case PointStrategyNumberGroup: - /* A NaN internal key tells us nothing, see box_has_nan() */ - if (nan_internal_key(entry)) + if (distance_has_nan(DatumGetBoxP(entry->key), + DatumGetPointP(query))) distance = 0.0; else distance = computeDistance(false, @@ -1622,6 +1635,15 @@ gist_box_distance(PG_FUNCTION_ARGS) distance = gist_bbox_distance(entry, query, strategy); + /* + * A leaf key is the indexed box, so its distance must be exact. 0 is + * not, and index-only scans cannot recheck, so ask the operator. + */ + if (GIST_LEAF(entry) && + distance_has_nan(DatumGetBoxP(entry->key), DatumGetPointP(query))) + distance = DatumGetFloat8(DirectFunctionCall2(dist_bp, + entry->key, query)); + PG_RETURN_FLOAT8(distance); } diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out index e7f1a27f866..c6e2c00396b 100644 --- a/src/test/regress/expected/gist.out +++ b/src/test/regress/expected/gist.out @@ -550,3 +550,68 @@ select count(*) from gist_nan_point_tbl where p <@ circle '<(50,50),50>'; reset enable_seqscan; reset enable_bitmapscan; drop table gist_nan_point_tbl; +-- KNN with a NaN in a leaf key or in the query point +create table gist_nan_knn_tbl (b box, p polygon, pt point); +insert into gist_nan_knn_tbl + select b, polygon(b), center(b) + from (select box(point(i, j), point(i + 1, j + 1)) as b + from generate_series(0, 29) i, generate_series(0, 29) j) s; +insert into gist_nan_knn_tbl values + ('(NaN,NaN),(0,0)', '((NaN,NaN),(0,0),(1,1))', '(NaN,NaN)'), + ('(1,NaN),(0,0)', '((0,0),(NaN,1),(1,1))', '(1,NaN)'); +create index gist_nan_knn_tbl_b on gist_nan_knn_tbl using gist (b); +create index gist_nan_knn_tbl_p on gist_nan_knn_tbl using gist (p); +create index gist_nan_knn_tbl_pt on gist_nan_knn_tbl using gist (pt); +set enable_seqscan = off; +set enable_bitmapscan = off; +select b <-> point '(0.5,0.5)' from gist_nan_knn_tbl + order by b <-> point '(0.5,0.5)' limit 5; + ?column? +-------------------- + 0 + 0.5 + 0.5 + 0.5 + 0.7071067811865476 +(5 rows) + +-- the NaN distance sorts last +select b <-> point '(0.5,0.5)' from gist_nan_knn_tbl + order by b <-> point '(0.5,0.5)' offset 901; + ?column? +---------- + NaN +(1 row) + +select p <-> point '(0.5,0.5)' from gist_nan_knn_tbl + order by p <-> point '(0.5,0.5)' limit 5; + ?column? +---------- + 0 + 0 + 0 + 0.5 + 0.5 +(5 rows) + +select b <-> point '(1,NaN)' from gist_nan_knn_tbl + order by b <-> point '(1,NaN)' limit 3; + ?column? +---------- + NaN + NaN + NaN +(3 rows) + +select pt <-> point '(1,NaN)' from gist_nan_knn_tbl + order by pt <-> point '(1,NaN)' limit 3; + ?column? +---------- + NaN + NaN + NaN +(3 rows) + +reset enable_seqscan; +reset enable_bitmapscan; +drop table gist_nan_knn_tbl; diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql index b9df6490ca5..b880da19a42 100644 --- a/src/test/regress/sql/gist.sql +++ b/src/test/regress/sql/gist.sql @@ -275,3 +275,32 @@ select count(*) from gist_nan_point_tbl where p <@ circle '<(50,50),50>'; reset enable_seqscan; reset enable_bitmapscan; drop table gist_nan_point_tbl; + +-- KNN with a NaN in a leaf key or in the query point +create table gist_nan_knn_tbl (b box, p polygon, pt point); +insert into gist_nan_knn_tbl + select b, polygon(b), center(b) + from (select box(point(i, j), point(i + 1, j + 1)) as b + from generate_series(0, 29) i, generate_series(0, 29) j) s; +insert into gist_nan_knn_tbl values + ('(NaN,NaN),(0,0)', '((NaN,NaN),(0,0),(1,1))', '(NaN,NaN)'), + ('(1,NaN),(0,0)', '((0,0),(NaN,1),(1,1))', '(1,NaN)'); +create index gist_nan_knn_tbl_b on gist_nan_knn_tbl using gist (b); +create index gist_nan_knn_tbl_p on gist_nan_knn_tbl using gist (p); +create index gist_nan_knn_tbl_pt on gist_nan_knn_tbl using gist (pt); +set enable_seqscan = off; +set enable_bitmapscan = off; +select b <-> point '(0.5,0.5)' from gist_nan_knn_tbl + order by b <-> point '(0.5,0.5)' limit 5; +-- the NaN distance sorts last +select b <-> point '(0.5,0.5)' from gist_nan_knn_tbl + order by b <-> point '(0.5,0.5)' offset 901; +select p <-> point '(0.5,0.5)' from gist_nan_knn_tbl + order by p <-> point '(0.5,0.5)' limit 5; +select b <-> point '(1,NaN)' from gist_nan_knn_tbl + order by b <-> point '(1,NaN)' limit 3; +select pt <-> point '(1,NaN)' from gist_nan_knn_tbl + order by pt <-> point '(1,NaN)' limit 3; +reset enable_seqscan; +reset enable_bitmapscan; +drop table gist_nan_knn_tbl; -- 2.37.1 (Apple Git-137.1)