From a25ac1014587bc29c9ee4f2d6406d25afbf759ee Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Wed, 8 Jul 2026 14:18:05 -0400
Subject: [PATCH v2 1/2] Fix hash index scans concurrent with bucket splits.

Oversight in commit 7c75ef57, which introduced page-at-a-time processing
of hash index scans.
---
 src/backend/access/hash/hashsearch.c | 54 ++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 89d1c5bc6..7882a531f 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -75,6 +75,28 @@ _hash_next(IndexScanDesc scan, ScanDirection dir)
 				if (!_hash_readpage(scan, &buf, dir))
 					end_of_scan = true;
 			}
+			else if (so->hashso_buc_populated && !so->hashso_buc_split)
+			{
+				/*
+				 * end of bucket being populated: continue the scan in the
+				 * bucket being split, on whose primary page we have held a
+				 * pin since _hash_first
+				 */
+				buf = so->hashso_split_bucket_buf;
+				Assert(BufferIsValid(buf));
+				LockBuffer(buf, BUFFER_LOCK_SHARE);
+				PredicateLockPage(rel, BufferGetBlockNumber(buf),
+								  scan->xs_snapshot);
+
+				/*
+				 * setting hashso_buc_split to true indicates that we are
+				 * scanning bucket being split.
+				 */
+				so->hashso_buc_split = true;
+
+				if (!_hash_readpage(scan, &buf, dir))
+					end_of_scan = true;
+			}
 			else
 				end_of_scan = true;
 		}
@@ -104,6 +126,38 @@ _hash_next(IndexScanDesc scan, ScanDirection dir)
 				if (!_hash_readpage(scan, &buf, dir))
 					end_of_scan = true;
 			}
+			else if (so->hashso_buc_populated && so->hashso_buc_split)
+			{
+				Page		page;
+				HashPageOpaque opaque;
+
+				/*
+				 * start of bucket being split: continue the scan from the
+				 * last page in the chain of the bucket being populated, on
+				 * whose primary page we have held a pin since _hash_first
+				 */
+				buf = so->hashso_bucket_buf;
+				Assert(BufferIsValid(buf));
+				LockBuffer(buf, BUFFER_LOCK_SHARE);
+				page = BufferGetPage(buf);
+				opaque = HashPageGetOpaque(page);
+
+				/* move to the end of bucket chain */
+				while (BlockNumberIsValid(opaque->hasho_nextblkno))
+					_hash_readnext(scan, &buf, &page, &opaque);
+
+				/*
+				 * setting hashso_buc_split to false indicates that we are
+				 * scanning the bucket being populated.  Only set it after
+				 * the chain walk above; otherwise _hash_readnext would
+				 * advance to the bucket being split on reaching the end of
+				 * the chain, instead of stopping there.
+				 */
+				so->hashso_buc_split = false;
+
+				if (!_hash_readpage(scan, &buf, dir))
+					end_of_scan = true;
+			}
 			else
 				end_of_scan = true;
 		}
-- 
2.53.0

