From 530b30cbc795fca94959ed1ee077234cab434bc6 Mon Sep 17 00:00:00 2001 From: Rui Zhao Date: Sun, 27 Sep 2026 23:21:24 +0800 Subject: [PATCH] Fix SP-GiST INCLUDE-only index-only scans An SP-GiST opclass can make included columns returnable while its key column cannot be reconstructed. spggettransform nevertheless passed the stored leaf datum as a value of the key input type. For the polygon opclass the stored value is a box, so heap_form_tuple can read beyond the datum while treating it as a polygon. Only reconstruct the key when the opclass supports it, and mark the key column NULL when the original value cannot be reconstructed. The planner only chooses this index-only scan when all attributes the query needs are returnable. Add a regression test for returning an included column from a polygon SP-GiST index. --- src/backend/access/spgist/spgscan.c | 8 ++++--- .../regress/expected/create_index_spgist.out | 21 +++++++++++++++++++ src/test/regress/sql/create_index_spgist.sql | 12 +++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c index 1332f855f35..bb9bae8cf60 100644 --- a/src/backend/access/spgist/spgscan.c +++ b/src/backend/access/spgist/spgscan.c @@ -1389,14 +1389,15 @@ spggettransform(IndexScanDesc scan, IndexScanBatch batch, int item) bool leafIsnulls[INDEX_MAX_KEYS]; Datum leafValue = (Datum) 0; MemoryContext oldcxt; + bool returnKey = so->state.config.canReturnData; Assert(scan->numberOfOrderBys == 0); Assert(sbatch->blkno != InvalidBlockNumber); - /* Reconstruct the key value via leaf_consistent */ + /* Reconstruct the key value via leaf_consistent, when possible */ leafTuple = (SpGistLeafTuple) (batch->tuples + batch->items[item].tupleOffset); - if (!sbatch->isNull) + if (!sbatch->isNull && returnKey) { spgLeafConsistentIn in; spgLeafConsistentOut out; @@ -1440,8 +1441,9 @@ spggettransform(IndexScanDesc scan, IndexScanBatch batch, int item) spgDeformLeafTuple(leafTuple, so->state.leafTupDesc, leafDatums, leafIsnulls, sbatch->isNull); + /* The planner isn't interested in a key the opclass can't return */ leafDatums[spgKeyColumn] = leafValue; - leafIsnulls[spgKeyColumn] = sbatch->isNull; + leafIsnulls[spgKeyColumn] = sbatch->isNull || !returnKey; scan->xs_hitup = heap_form_tuple(so->reconTupDesc, leafDatums, leafIsnulls); diff --git a/src/test/regress/expected/create_index_spgist.out b/src/test/regress/expected/create_index_spgist.out index da730dd479e..81805be1f71 100644 --- a/src/test/regress/expected/create_index_spgist.out +++ b/src/test/regress/expected/create_index_spgist.out @@ -590,6 +590,27 @@ SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10; (10 rows) RESET extra_float_digits; +-- test an index-only scan that returns only an included column +CREATE TEMP TABLE spgist_poly_include (id int, p polygon); +-- make a stored BOX look like a large varlena value if treated as a POLYGON +INSERT INTO spgist_poly_include VALUES + (7, polygon(box(point(0,0), point(1.0000000009313226,1)))); +CREATE INDEX spgist_poly_include_idx ON spgist_poly_include + USING spgist (p) INCLUDE (id); +VACUUM (FREEZE, ANALYZE) spgist_poly_include; +EXPLAIN (COSTS OFF) +SELECT id FROM spgist_poly_include; + QUERY PLAN +---------------------------------------------------------------------- + Index Only Scan using spgist_poly_include_idx on spgist_poly_include +(1 row) + +SELECT id FROM spgist_poly_include; + id +---- + 7 +(1 row) + -- check ORDER BY distance to NULL SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt, p <-> '0,0' LIMIT 1) FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt); diff --git a/src/test/regress/sql/create_index_spgist.sql b/src/test/regress/sql/create_index_spgist.sql index 660bfc61930..11b5038671d 100644 --- a/src/test/regress/sql/create_index_spgist.sql +++ b/src/test/regress/sql/create_index_spgist.sql @@ -234,6 +234,18 @@ SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10; SELECT p, dist FROM quad_point_tbl_ord_seq1 ORDER BY p <-> '0,0' LIMIT 10; RESET extra_float_digits; +-- test an index-only scan that returns only an included column +CREATE TEMP TABLE spgist_poly_include (id int, p polygon); +-- make a stored BOX look like a large varlena value if treated as a POLYGON +INSERT INTO spgist_poly_include VALUES + (7, polygon(box(point(0,0), point(1.0000000009313226,1)))); +CREATE INDEX spgist_poly_include_idx ON spgist_poly_include + USING spgist (p) INCLUDE (id); +VACUUM (FREEZE, ANALYZE) spgist_poly_include; +EXPLAIN (COSTS OFF) +SELECT id FROM spgist_poly_include; +SELECT id FROM spgist_poly_include; + -- check ORDER BY distance to NULL SELECT (SELECT p FROM kd_point_tbl ORDER BY p <-> pt, p <-> '0,0' LIMIT 1) FROM (VALUES (point '1,2'), (NULL), ('1234,5678')) pts(pt); -- 2.43.7