From 89345a0f561b5e61b0ba6781455e0a53ac5a4825 Mon Sep 17 00:00:00 2001
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Mon, 24 Aug 2026 22:45:44 +0500
Subject: [PATCH] Reject index-only scans when the index cannot return any
 columns

If the query needs no heap columns and the index returns none,
check_index_only() still succeeds because bms_is_subset(empty, empty)
is true.  Reject index-only scans when index_canreturn_attrs is empty.

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 | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3182..f6c408eef1f 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -2289,6 +2289,14 @@ 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.
+	 */
+	if (result && bms_is_empty(index_canreturn_attrs))
+		result = false;
+
 	bms_free(attrs_used);
 	bms_free(index_canreturn_attrs);
 
-- 
2.53.0

