From 43eb14f03c05fdbe11b1b62676dab6ac857355cd Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@postgresql.org>
Date: Thu, 8 Oct 2026 10:18:53 +0700
Subject: [PATCH v4 1/2] Fix reachable "impossible case" in SP-GiST quad tree
 getQuadrant()

getQuadrant() places a point on each axis with the fuzzy comparisons
FPlt(), FPgt() and FPeq(), which do not cover every case.  FPeq()
compares the rounded difference against EPSILON, while FPlt() and FPgt()
round the sum of one operand and EPSILON.  When the centroid plus
EPSILON rounds up, a point equal to that rounded sum is more than
EPSILON above the centroid, so FPeq() is false; FPgt() compares it with
the same rounded sum, so it is false too, and FPlt() is false because
the point is above the centroid.  The same gap exists below the
centroid, when the centroid minus EPSILON rounds down.  No quadrant
matches, and index builds, inserts and searches fail with
"getQuadrant: impossible case".

To fix, fall back to exact comparisons when none of the fuzzy tests
match.  That gives every finite value a quadrant; NaN coordinates still
reach the error, as before.  Points that matched a fuzzy test before
are routed as before, so existing indexes remain valid.

Bug: #19597
Reported-by: Michael Malis <malis@pgrust.com>
Author: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Reviewed-by: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reviewed-by: Pierre Forstmann <pierre.forstmann@gmail.com>
Discussion: https://postgr.es/m/19597-39c532e61d78dff6@postgresql.org
Backpatch-through: 14
---
 src/backend/access/spgist/spgquadtreeproc.c | 14 ++++++++++++++
 src/test/regress/expected/spgist.out        |  5 +++++
 src/test/regress/sql/spgist.sql             |  6 ++++++
 3 files changed, 25 insertions(+)

diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c
index 946dabc4527..7fe69e95649 100644
--- a/src/backend/access/spgist/spgquadtreeproc.c
+++ b/src/backend/access/spgist/spgquadtreeproc.c
@@ -76,6 +76,20 @@ getQuadrant(Point *centroid, Point *tst)
 		SPTEST(point_left, tst, centroid))
 		return 4;
 
+	/*
+	 * The fuzzy tests above can all fail on one axis, because FPeq() rounds
+	 * a difference while FPlt() and FPgt() round a sum.  Exact comparisons
+	 * always pick a quadrant; use them with the same axis tie-breaking.
+	 */
+	if (tst->y >= centroid->y && tst->x >= centroid->x)
+		return 1;
+	if (tst->y < centroid->y && tst->x >= centroid->x)
+		return 2;
+	if (tst->y <= centroid->y && tst->x < centroid->x)
+		return 3;
+	if (tst->y > centroid->y && tst->x < centroid->x)
+		return 4;
+
 	elog(ERROR, "getQuadrant: impossible case");
 	return 0;
 }
diff --git a/src/test/regress/expected/spgist.out b/src/test/regress/expected/spgist.out
index 2e911285600..6e29bf5fd16 100644
--- a/src/test/regress/expected/spgist.out
+++ b/src/test/regress/expected/spgist.out
@@ -94,3 +94,8 @@ select box(point(i,j))
   from generate_series(1,100,5) i,
        generate_series(1,10,5) j;
 -- leave this table around, to help in testing dump/restore
+-- Bug #19597: coverage of getQuadrant for a point just over EPSILON from the centroid
+create table spgist_quad_fp_tbl(p point);
+insert into spgist_quad_fp_tbl select point(i, 5) from generate_series(1, 300) i;
+create index spgist_quad_fp_idx on spgist_quad_fp_tbl using spgist(p);
+insert into spgist_quad_fp_tbl values (point(3, 5 + 1e-6));
diff --git a/src/test/regress/sql/spgist.sql b/src/test/regress/sql/spgist.sql
index 4828ede68c3..b631a87fedb 100644
--- a/src/test/regress/sql/spgist.sql
+++ b/src/test/regress/sql/spgist.sql
@@ -89,3 +89,9 @@ select box(point(i,j))
   from generate_series(1,100,5) i,
        generate_series(1,10,5) j;
 -- leave this table around, to help in testing dump/restore
+
+-- Bug #19597: coverage of getQuadrant for a point just over EPSILON from the centroid
+create table spgist_quad_fp_tbl(p point);
+insert into spgist_quad_fp_tbl select point(i, 5) from generate_series(1, 300) i;
+create index spgist_quad_fp_idx on spgist_quad_fp_tbl using spgist(p);
+insert into spgist_quad_fp_tbl values (point(3, 5 + 1e-6));
-- 
2.55.0

