diff --git a/src/backend/access/spgist/spgquadtreeproc.c b/src/backend/access/spgist/spgquadtreeproc.c index ce6464f7e04..d9bf2ed0176 100644 --- a/src/backend/access/spgist/spgquadtreeproc.c +++ b/src/backend/access/spgist/spgquadtreeproc.c @@ -39,6 +39,32 @@ spg_quad_config(PG_FUNCTION_ARGS) #define SPTEST(f, x, y) \ DatumGetBool(DirectFunctionCall2(f, PointPGetDatum(x), PointPGetDatum(y))) +/* + * Compare one coordinate of a point against that of the centroid + */ +static int +getAxisSide(float8 tst, float8 centroid) +{ + /* + * Test FPeq first so that points on a centroid axis get the same result + * as getQuadrant. + */ + if (FPeq(tst, centroid)) + return 0; + if (FPgt(tst, centroid)) + return 1; + if (FPlt(tst, centroid)) + return -1; + /* fall back to exact comparison */ + if (tst > centroid) + return 1; + if (tst < centroid) + return -1; + + elog(ERROR, "getQuadrant: impossible case"); + return 0; +} + /* * Determine which quadrant a point falls into, relative to the centroid. * @@ -54,6 +80,9 @@ spg_quad_config(PG_FUNCTION_ARGS) static int16 getQuadrant(Point *centroid, Point *tst) { + int xside; + int yside; + if ((SPTEST(point_above, tst, centroid) || SPTEST(point_horiz, tst, centroid)) && (SPTEST(point_right, tst, centroid) || @@ -74,8 +103,18 @@ getQuadrant(Point *centroid, Point *tst) SPTEST(point_left, tst, centroid)) return 4; - elog(ERROR, "getQuadrant: impossible case"); - return 0; + /* + * The fuzzy tests above can all fail on one axis, because FPeq() rounds a + * difference while FPlt() and FPgt() round a sum. Compare exactly only + * on that axis. On the other axis, keep the result of the fuzzy tests, + * because searches route by that result too. + */ + xside = getAxisSide(tst->x, centroid->x); + yside = getAxisSide(tst->y, centroid->y); + + if (xside >= 0) + return (yside >= 0) ? 1 : 2; + return (yside <= 0) ? 3 : 4; } /* Returns bounding box of a given quadrant inside given bounding box */ diff --git a/src/test/regress/expected/spgist.out b/src/test/regress/expected/spgist.out index 2e911285600..064b0a69cae 100644 --- a/src/test/regress/expected/spgist.out +++ b/src/test/regress/expected/spgist.out @@ -94,3 +94,19 @@ 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)); +-- Bug #19597: needs exact comparison on one axis, within EPSILON on the other +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)); + count +------- + 1 +(1 row) + +reset enable_seqscan; diff --git a/src/test/regress/sql/spgist.sql b/src/test/regress/sql/spgist.sql index 4828ede68c3..c7bfae347db 100644 --- a/src/test/regress/sql/spgist.sql +++ b/src/test/regress/sql/spgist.sql @@ -89,3 +89,16 @@ 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)); + +-- Bug #19597: needs exact comparison on one axis, within EPSILON on the other +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)); +reset enable_seqscan;