From b3a6658da4d2a133636c0fbe94503e66b7541b9a Mon Sep 17 00:00:00 2001 From: Shihao Date: Mon, 21 Sep 2026 19:06:37 -0400 Subject: [PATCH v1 1/2] Fix BRIN box_inclusion_ops losing rows when a box has a NaN coordinate BRIN summarizes each page range of a box column with bound_box(). That function used float8_max(), which treats NaN as larger than any other value, so one box with a NaN coordinate put NaN into the summary. The box operators compare with plain C comparisons, which are all false for NaN. So brin_inclusion_consistent() decided that the range could not match any query, and every row of the range was skipped, including the rows that have no NaN at all. Make bound_box() return an infinite bound on any side where an input coordinate is NaN. The summary then matches every query on that axis, the range gets scanned, and the recheck sorts out the rows. Summaries that already hold a NaN are not repaired by this. Users with NaN values in a box column under a BRIN index need to REINDEX it. Bug: #19705 Reported-by: Ke Discussion: https://postgr.es/m/19705-548fda77321e062d@postgresql.org Backpatch-through: 14 --- src/backend/utils/adt/geo_ops.c | 34 +++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c index 73324b91fe5..09ae2bdaad2 100644 --- a/src/backend/utils/adt/geo_ops.c +++ b/src/backend/utils/adt/geo_ops.c @@ -4407,6 +4407,32 @@ point_box(PG_FUNCTION_ARGS) PG_RETURN_BOX_P(box); } +/* + * Helpers for boxes_bound_box + * + * A NaN coordinate has no position, so no finite bound can be said to include + * it. We make the bound infinite on that side instead of letting the NaN + * through. This matters because the box operators use plain C comparisons, + * which are all false for NaN. BRIN's box_inclusion_ops uses bound_box to + * summarize a page range, and a NaN in the summary would make every operator + * say that the range cannot match, hiding all rows of the range. + */ +static inline float8 +bound_box_high(float8 val1, float8 val2) +{ + if (unlikely(isnan(val1) || isnan(val2))) + return get_float8_infinity(); + return float8_max(val1, val2); +} + +static inline float8 +bound_box_low(float8 val1, float8 val2) +{ + if (unlikely(isnan(val1) || isnan(val2))) + return -get_float8_infinity(); + return float8_min(val1, val2); +} + /* * Smallest bounding box that includes both of the given boxes */ @@ -4419,10 +4445,10 @@ boxes_bound_box(PG_FUNCTION_ARGS) container = palloc_object(BOX); - container->high.x = float8_max(box1->high.x, box2->high.x); - container->low.x = float8_min(box1->low.x, box2->low.x); - container->high.y = float8_max(box1->high.y, box2->high.y); - container->low.y = float8_min(box1->low.y, box2->low.y); + container->high.x = bound_box_high(box1->high.x, box2->high.x); + container->low.x = bound_box_low(box1->low.x, box2->low.x); + container->high.y = bound_box_high(box1->high.y, box2->high.y); + container->low.y = bound_box_low(box1->low.y, box2->low.y); PG_RETURN_BOX_P(container); } -- 2.37.1 (Apple Git-137.1)