From 508938356962d91c14f26fcd69bac6a62f778d28 Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Fri, 16 Oct 2026 15:05:03 +0700
Subject: [PATCH v1] Check all four box corners in SP-GiST quad tree <@ search

When the centroid lies outside the query box, spg_quad_inner_consistent()
keeps only the quadrants that contain a corner of the box.  It meant to
test all four corners, but the last step set x back to low.x instead of
y to low.y, so it tested (low.x, high.y) twice and never (high.x, low.y).

With exact comparisons the missing corner would not matter: a box that
does not contain the centroid lies on one side of it on at least one
axis, so (high.x, low.y) shares a quadrant with another corner.  But
getQuadrant() compares with FPeq(), while the containment test is exact.
When a box edge lies within EPSILON of the centroid, the corner on that
edge counts as being on the centroid's line, and (high.x, low.y) can be
the only corner in its quadrant.  That quadrant was then skipped, and an
index scan for "point <@ box" missed rows that a seqscan returns.

Backpatch to all supported branches.
---
 src/backend/access/spgist/spgquadtreeproc.c |  2 +-
 src/test/regress/expected/spgist.out        | 10 ++++++++++
 src/test/regress/sql/spgist.sql             |  5 +++++
 3 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c
index e038c998472..485b3467489 100644
--- a/src/backend/access/spgist/spgquadtreeproc.c
+++ b/src/backend/access/spgist/spgquadtreeproc.c
@@ -393,7 +393,7 @@ spg_quad_inner_consistent(PG_FUNCTION_ARGS)
 					r |= 1 << getQuadrant(centroid, &p);
 					p = boxQuery->high;
 					r |= 1 << getQuadrant(centroid, &p);
-					p.x = boxQuery->low.x;
+					p.y = boxQuery->low.y;
 					r |= 1 << getQuadrant(centroid, &p);
 
 					which &= r;
diff --git a/src/test/regress/expected/spgist.out b/src/test/regress/expected/spgist.out
index 064b0a69cae..4b018bbfb95 100644
--- a/src/test/regress/expected/spgist.out
+++ b/src/test/regress/expected/spgist.out
@@ -109,4 +109,14 @@ select count(*) from spgist_quad_fp_tbl
      1
 (1 row)
 
+-- <@ must check the box corner (high.x, low.y): high.y is within EPSILON
+-- of the centroid, so only that corner reaches the right-below quadrant
+insert into spgist_quad_fp_tbl values (point(200, 5 - 5e-6));
+select count(*) from spgist_quad_fp_tbl
+  where p <@ box(point(0, 4.99999), point(400, 4.9999995));
+ count 
+-------
+     1
+(1 row)
+
 reset enable_seqscan;
diff --git a/src/test/regress/sql/spgist.sql b/src/test/regress/sql/spgist.sql
index c7bfae347db..c9d46b1c7fd 100644
--- a/src/test/regress/sql/spgist.sql
+++ b/src/test/regress/sql/spgist.sql
@@ -101,4 +101,9 @@ insert into spgist_quad_fp_tbl values (point(114 - 5e-7, 5 + 1e-6));
 set enable_seqscan = off;
 select count(*) from spgist_quad_fp_tbl
   where p <@ box(point(113.999999, 5.0000004), point(113.9999999, 5.0000016));
+-- <@ must check the box corner (high.x, low.y): high.y is within EPSILON
+-- of the centroid, so only that corner reaches the right-below quadrant
+insert into spgist_quad_fp_tbl values (point(200, 5 - 5e-6));
+select count(*) from spgist_quad_fp_tbl
+  where p <@ box(point(0, 4.99999), point(400, 4.9999995));
 reset enable_seqscan;
-- 
2.56.0

