From d9280942f4ae7556da4384df596bc599207fceae Mon Sep 17 00:00:00 2001
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Tue, 25 Aug 2026 00:20:18 +0500
Subject: [PATCH 1/2] Reject index-only scans when the index cannot return any
 columns

If the query needs no heap columns and no index column is returnable,
check_index_only() still succeeds because bms_is_subset(empty, empty)
is true.  Track any_canreturn while building the key-column bitmap and
reject index-only scans when none are returnable (including expression
columns, which the bitmap omits).

Bug: 19638
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Reported-by: Manuel Reyes Bravo <manuelreyesbravo@gmail.com>
Discussion: https://www.postgresql.org/message-id/19638-277d0f73dfaeaec8@postgresql.org
---
 src/backend/optimizer/path/indxpath.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3182..0d4b37149d1 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -2226,6 +2226,7 @@ static bool
 check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 {
 	bool		result;
+	bool		any_canreturn;
 	Bitmapset  *attrs_used = NULL;
 	Bitmapset  *index_canreturn_attrs = NULL;
 	ListCell   *lc;
@@ -2269,9 +2270,14 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 	 * Construct a bitmapset of columns that the index can return back in an
 	 * index-only scan.
 	 */
+	any_canreturn = false;
 	for (i = 0; i < index->ncolumns; i++)
 	{
 		int			attno = index->indexkeys[i];
+		bool		col_canreturn = index->canreturn[i];
+
+		if (col_canreturn)
+			any_canreturn = true;
 
 		/*
 		 * For the moment, we just ignore index expressions.  It might be nice
@@ -2280,7 +2286,7 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 		if (attno == 0)
 			continue;
 
-		if (index->canreturn[i])
+		if (col_canreturn)
 			index_canreturn_attrs =
 				bms_add_member(index_canreturn_attrs,
 							   attno - FirstLowInvalidHeapAttributeNumber);
@@ -2289,6 +2295,15 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index)
 	/* Do we have all the necessary attributes? */
 	result = bms_is_subset(attrs_used, index_canreturn_attrs);
 
+	/*
+	 * bms_is_subset() is true when attrs_used is empty, even if the index
+	 * returns nothing.  That would allow a broken index-only scan for AMs
+	 * with amcanreturn == NULL.  Expression columns can be returnable even
+	 * when the key-column bitmap is empty, so test canreturn[] directly.
+	 */
+	if (result && !any_canreturn)
+		result = false;
+
 	bms_free(attrs_used);
 	bms_free(index_canreturn_attrs);
 
-- 
2.53.0

