From f88fbf5c894e88da85efc11683732b543b022c62 Mon Sep 17 00:00:00 2001 From: reshke Date: Thu, 24 Sep 2026 08:10:07 +0300 Subject: [PATCH v5] Check for NaN point in GiST polygon and circle searches. The polygon/circle strategy groups of GiST consistent function check leaf points with its bounding box and fast-filter check. Points with NaN coordinate always fails, so such points were not returned by index serach while poly_contain_pt/circle_contain_pt would actaully match them. This was actaully the case in regression test - "SELECT count(*) FROM point_tbl WHERE f1 <@ polygon ..." in create_index.sql has answered 5 all along while the index run answered 4. Also asserts has been updated with NaN check. Per BUG 19705 Discussion: https://postgr.es/m/19705-548fda77321e062d@postgresql.org --- src/backend/access/gist/gistproc.c | 42 ++++++++++++++-------- src/test/regress/expected/create_index.out | 2 +- src/test/regress/expected/gist.out | 14 ++++++++ src/test/regress/sql/gist.sql | 4 +++ 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c index 0f405d4450f..f0c598e1bda 100644 --- a/src/backend/access/gist/gistproc.c +++ b/src/backend/access/gist/gistproc.c @@ -1482,11 +1482,16 @@ gist_point_consistent(PG_FUNCTION_ARGS) { POLYGON *query = PG_GETARG_POLYGON_P(1); - result = DatumGetBool(DirectFunctionCall5(gist_poly_consistent, - PointerGetDatum(entry), - PolygonPGetDatum(query), - Int16GetDatum(RTOverlapStrategyNumber), - 0, PointerGetDatum(recheck))); + /* + * A NaN point fails the bounding-box prefilter, though the + * exact operator below may still match it, as on the heap. + */ + result = box_has_nan(DatumGetBoxP(entry->key)) || + DatumGetBool(DirectFunctionCall5(gist_poly_consistent, + PointerGetDatum(entry), + PolygonPGetDatum(query), + Int16GetDatum(RTOverlapStrategyNumber), + 0, PointerGetDatum(recheck))); if (GIST_LEAF(entry) && result) { @@ -1496,8 +1501,10 @@ gist_point_consistent(PG_FUNCTION_ARGS) */ BOX *box = DatumGetBoxP(entry->key); - Assert(box->high.x == box->low.x - && box->high.y == box->low.y); + Assert((box->high.x == box->low.x || + (isnan(box->high.x) && isnan(box->low.x))) && + (box->high.y == box->low.y || + (isnan(box->high.y) && isnan(box->low.y)))); result = DatumGetBool(DirectFunctionCall2(poly_contain_pt, PolygonPGetDatum(query), PointPGetDatum(&box->high))); @@ -1509,11 +1516,16 @@ gist_point_consistent(PG_FUNCTION_ARGS) { CIRCLE *query = PG_GETARG_CIRCLE_P(1); - result = DatumGetBool(DirectFunctionCall5(gist_circle_consistent, - PointerGetDatum(entry), - CirclePGetDatum(query), - Int16GetDatum(RTOverlapStrategyNumber), - 0, PointerGetDatum(recheck))); + /* + * A NaN point fails the bounding-box prefilter, though the + * exact operator below may still match it, as on the heap. + */ + result = box_has_nan(DatumGetBoxP(entry->key)) || + DatumGetBool(DirectFunctionCall5(gist_circle_consistent, + PointerGetDatum(entry), + CirclePGetDatum(query), + Int16GetDatum(RTOverlapStrategyNumber), + 0, PointerGetDatum(recheck))); if (GIST_LEAF(entry) && result) { @@ -1523,8 +1535,10 @@ gist_point_consistent(PG_FUNCTION_ARGS) */ BOX *box = DatumGetBoxP(entry->key); - Assert(box->high.x == box->low.x - && box->high.y == box->low.y); + Assert((box->high.x == box->low.x || + (isnan(box->high.x) && isnan(box->low.x))) && + (box->high.y == box->low.y || + (isnan(box->high.y) && isnan(box->low.y)))); result = DatumGetBool(DirectFunctionCall2(circle_contain_pt, CirclePGetDatum(query), PointPGetDatum(&box->high))); diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 7b2640f0e04..272b679bbac 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -380,7 +380,7 @@ SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50, SELECT count(*) FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)'; count ------- - 4 + 5 (1 row) EXPLAIN (COSTS OFF) diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out index 174f8b4251f..e7f1a27f866 100644 --- a/src/test/regress/expected/gist.out +++ b/src/test/regress/expected/gist.out @@ -533,6 +533,20 @@ select count(*) from gist_nan_point_tbl where p ~= point '(3,3)'; 1 (1 row) +-- a NaN point fails the bounding-box prefilter of the polygon and circle +-- contains strategies, so the exact operator decides, as on the heap +select count(*) from gist_nan_point_tbl where p <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)'; + count +------- + 7602 +(1 row) + +select count(*) from gist_nan_point_tbl where p <@ circle '<(50,50),50>'; + count +------- + 7843 +(1 row) + reset enable_seqscan; reset enable_bitmapscan; drop table gist_nan_point_tbl; diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql index 654318fb4db..b9df6490ca5 100644 --- a/src/test/regress/sql/gist.sql +++ b/src/test/regress/sql/gist.sql @@ -268,6 +268,10 @@ select count(*) from gist_nan_point_tbl where p ~= point '(NaN,NaN)'; select count(*) from gist_nan_point_tbl where p ~= point '(NaN,3)'; select count(*) from gist_nan_point_tbl where p ~= point '(3,NaN)'; select count(*) from gist_nan_point_tbl where p ~= point '(3,3)'; +-- a NaN point fails the bounding-box prefilter of the polygon and circle +-- contains strategies, so the exact operator decides, as on the heap +select count(*) from gist_nan_point_tbl where p <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)'; +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; -- 2.43.0