From 14f664ceea6c256630c41cb8dc7940e7b07c0091 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Sat, 15 Aug 2026 18:31:18 -0400
Subject: [PATCH v2 1/2] GiST: Deprecate F_TUPLES_DELETED.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Also add test coverage of gistprunepage(), which would have caught this
issue earlier.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-by: Michael Paquiër <michael@paquier.xyz>
Discussion: https://postgr.es/m/CAH2-WznbTsQCrjmd=eSawfPqcxCjSFUkk6Qzd3z+gpNte5i03Q@mail.gmail.com
---
 src/include/access/gist.h            |  8 ++------
 src/backend/access/gist/gistvacuum.c |  1 -
 src/backend/access/gist/gistxlog.c   |  3 ---
 src/test/regress/expected/gist.out   | 26 ++++++++++++++++++++++++++
 src/test/regress/sql/gist.sql        | 21 +++++++++++++++++++++
 5 files changed, 49 insertions(+), 10 deletions(-)

diff --git a/src/include/access/gist.h b/src/include/access/gist.h
index 9b385b13a..69a7945c5 100644
--- a/src/include/access/gist.h
+++ b/src/include/access/gist.h
@@ -48,8 +48,8 @@
  */
 #define F_LEAF				(1 << 0)	/* leaf page */
 #define F_DELETED			(1 << 1)	/* the page has been deleted */
-#define F_TUPLES_DELETED	(1 << 2)	/* some tuples on the page were
-										 * deleted */
+#define F_TUPLES_DELETED	(1 << 2)	/* old-style VACUUM FULL flag
+										 * (deprecated) */
 #define F_FOLLOW_RIGHT		(1 << 3)	/* page to the right has no downlink */
 #define F_HAS_GARBAGE		(1 << 4)	/* some tuples on the page are dead,
 										 * but not deleted yet */
@@ -174,10 +174,6 @@ typedef struct GISTENTRY
 
 #define GistPageIsDeleted(page) ( GistPageGetOpaque(page)->flags & F_DELETED)
 
-#define GistTuplesDeleted(page) ( GistPageGetOpaque(page)->flags & F_TUPLES_DELETED)
-#define GistMarkTuplesDeleted(page) ( GistPageGetOpaque(page)->flags |= F_TUPLES_DELETED)
-#define GistClearTuplesDeleted(page)	( GistPageGetOpaque(page)->flags &= ~F_TUPLES_DELETED)
-
 #define GistPageHasGarbage(page) ( GistPageGetOpaque(page)->flags & F_HAS_GARBAGE)
 #define GistMarkPageHasGarbage(page) ( GistPageGetOpaque(page)->flags |= F_HAS_GARBAGE)
 #define GistClearPageHasGarbage(page)	( GistPageGetOpaque(page)->flags &= ~F_HAS_GARBAGE)
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 686a04180..b87e98207 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -401,7 +401,6 @@ restart:
 			MarkBufferDirty(buffer);
 
 			PageIndexMultiDelete(page, todelete, ntodelete);
-			GistMarkTuplesDeleted(page);
 
 			if (RelationNeedsWAL(rel))
 			{
diff --git a/src/backend/access/gist/gistxlog.c b/src/backend/access/gist/gistxlog.c
index ae538dc81..1f8100f2a 100644
--- a/src/backend/access/gist/gistxlog.c
+++ b/src/backend/access/gist/gistxlog.c
@@ -114,8 +114,6 @@ gistRedoPageUpdateRecord(XLogReaderState *record)
 			data += sizeof(OffsetNumber) * xldata->ntodelete;
 
 			PageIndexMultiDelete(page, todelete, xldata->ntodelete);
-			if (GistPageIsLeaf(page))
-				GistMarkTuplesDeleted(page);
 		}
 
 		/* Add new tuples if any */
@@ -204,7 +202,6 @@ gistRedoDeleteRecord(XLogReaderState *record)
 		PageIndexMultiDelete(page, toDelete, xldata->ntodelete);
 
 		GistClearPageHasGarbage(page);
-		GistMarkTuplesDeleted(page);
 
 		PageSetLSN(page, lsn);
 		MarkBufferDirty(buffer);
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index ae5b522b3..ac79f94aa 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -423,6 +423,32 @@ select lower(r) = repeat('7', 200)::numeric as lower_ok,
 (1 row)
 
 drop table gist_ios_tupdesc;
+-- test deletion of LP_DEAD-marked index tuples
+create table gist_prune_tbl (k int, p point);
+create index gist_prune_tbl_p_index on gist_prune_tbl using gist (p);
+begin;
+insert into gist_prune_tbl select i, point(1, i) from generate_series(1, 600) i;
+rollback;
+set enable_bitmapscan = off;
+set enable_indexonlyscan = off;
+set enable_seqscan = off;
+select count(*) from gist_prune_tbl where p <@ box(point(0,0), point(2,1000));
+ count 
+-------
+     0
+(1 row)
+
+insert into gist_prune_tbl select i, point(1, i) from generate_series(1, 600) i;
+select count(*) from gist_prune_tbl where p <@ box(point(0,0), point(2,1000));
+ count 
+-------
+   600
+(1 row)
+
+reset enable_bitmapscan;
+reset enable_indexonlyscan;
+reset enable_seqscan;
+drop table gist_prune_tbl;
 -- Force an index build using buffering.
 create index gist_tbl_box_index_forcing_buffering on gist_tbl using gist (p)
   with (buffering=on, fillfactor=50);
diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql
index 1ebb1d9ee..57dcc0824 100644
--- a/src/test/regress/sql/gist.sql
+++ b/src/test/regress/sql/gist.sql
@@ -198,6 +198,27 @@ select lower(r) = repeat('7', 200)::numeric as lower_ok,
 
 drop table gist_ios_tupdesc;
 
+-- test deletion of LP_DEAD-marked index tuples
+create table gist_prune_tbl (k int, p point);
+create index gist_prune_tbl_p_index on gist_prune_tbl using gist (p);
+
+begin;
+insert into gist_prune_tbl select i, point(1, i) from generate_series(1, 600) i;
+rollback;
+
+set enable_bitmapscan = off;
+set enable_indexonlyscan = off;
+set enable_seqscan = off;
+
+select count(*) from gist_prune_tbl where p <@ box(point(0,0), point(2,1000));
+insert into gist_prune_tbl select i, point(1, i) from generate_series(1, 600) i;
+select count(*) from gist_prune_tbl where p <@ box(point(0,0), point(2,1000));
+
+reset enable_bitmapscan;
+reset enable_indexonlyscan;
+reset enable_seqscan;
+drop table gist_prune_tbl;
+
 -- Force an index build using buffering.
 create index gist_tbl_box_index_forcing_buffering on gist_tbl using gist (p)
   with (buffering=on, fillfactor=50);
-- 
2.53.0

