From 8c4a9b52cc6c58d82260617a1e134f8069a22fec Mon Sep 17 00:00:00 2001
From: Jakub Wartak <jakub.wartak@enterprisedb.com>
Date: Thu, 12 Nov 2026 10:06:08 +0100
Subject: [PATCH v7_12112026 2/4] amcheck: verify against dangling btree
 pointers to HOT tuples too

Author: Andrey Borodin <amborodin@acm.org>
Author: Jakub Wartak <jakub.wartak@enterprisedb.com>
Discussion: https://postgr.es/m/432626F9-65DF-4F0D-B345-26CFC3E2CFAC@yandex-team.ru
---
 contrib/amcheck/verify_nbtree.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 59516bde6e6..91c24f121df 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -3219,6 +3219,21 @@ bt_report_dangling_index_entry(BtreeCheckState *state, ItemPointer tid,
 									targetblock, offset,
 									blkno, offnum)));
 
+	/*
+	 * Valid btree tuple should never point directly to a HOT tuple (HOT
+	 * should be only reached by chain).
+	 */
+	if (ItemIdIsNormal(lp) &&
+		HeapTupleHeaderIsHeapOnly((HeapTupleHeader) PageGetItem(page, lp)))
+		ereport(ERROR,
+				(errcode(ERRCODE_INDEX_CORRUPTED),
+				 errmsg("index tuple in index \"%s\" points to heap-only tuple in table \"%s\"",
+						RelationGetRelationName(state->rel),
+						RelationGetRelationName(state->heaprel)),
+				 errdetail_internal("Index tid=(%u,%u) points to heap tid=(%u,%u), which is not the root of a HOT chain.",
+									targetblock, offset,
+									blkno, offnum)));
+
 	/* LP_DEAD, LP_REDIRECT or a dead LP_NORMAL tuple: not corruption */
 	UnlockReleaseBuffer(buffer);
 }
-- 
2.43.0

