On Thu, Jul 9, 2026 at 10:02 PM Peter Geoghegan <[email protected]> wrote:
>
> I ran this against CI, and saw failures on 32-bit meson tied to tuple
> alignment. When MAXALIGN is 4, the assumptions about page layout
> underlying the test case break.
>
> Attached is V3, which directly addresses the alignment issue, and
> fully passes CI.
>

Thanks, the fixup-patch looks good to me. However, I was thinking
whether it is a good idea to refactor the common code into separate
functions like attached? This is atop your patches. BTW, I took help
from Claude to do this refactoring. I am fine with committing
/sql/hash_split.sql test along with the code-fix.

-- 
With Regards,
Amit Kapila.
diff --git a/src/backend/access/hash/hashsearch.c 
b/src/backend/access/hash/hashsearch.c
index 7882a531f4f..96a2641372f 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -30,6 +30,9 @@ static inline void _hash_saveitem(HashScanOpaque so, int 
itemIndex,
                                                                  OffsetNumber 
offnum, IndexTuple itup);
 static void _hash_readnext(IndexScanDesc scan, Buffer *bufp,
                                                   Page *pagep, HashPageOpaque 
*opaquep);
+static Buffer _hash_step_to_split_bucket(IndexScanDesc scan);
+static void _hash_step_to_populated_bucket(IndexScanDesc scan, Buffer *bufp,
+                                                                               
   Page *pagep, HashPageOpaque *opaquep);
 
 /*
  *     _hash_next() -- Get the next item in a scan.
@@ -77,22 +80,7 @@ _hash_next(IndexScanDesc scan, ScanDirection dir)
                        }
                        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;
+                               buf = _hash_step_to_split_bucket(scan);
 
                                if (!_hash_readpage(scan, &buf, dir))
                                        end_of_scan = true;
@@ -131,29 +119,7 @@ _hash_next(IndexScanDesc scan, ScanDirection dir)
                                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;
+                               _hash_step_to_populated_bucket(scan, &buf, 
&page, &opaque);
 
                                if (!_hash_readpage(scan, &buf, dir))
                                        end_of_scan = true;
@@ -177,6 +143,76 @@ _hash_next(IndexScanDesc scan, ScanDirection dir)
        return true;
 }
 
+/*
+ * Cross over from the bucket being populated to the bucket being split, on
+ * whose primary page we have held a pin since _hash_first.  Used both when
+ * a forward scan of the populated bucket's own chain runs out of pages, and
+ * (via _hash_readnext) when the chain-walk inside _hash_readpage does.
+ *
+ * Returns the split bucket's primary page, pinned and share-locked, and
+ * sets hashso_buc_split to indicate that we are now scanning that bucket.
+ */
+static Buffer
+_hash_step_to_split_bucket(IndexScanDesc scan)
+{
+       Relation        rel = scan->indexRelation;
+       HashScanOpaque so = (HashScanOpaque) scan->opaque;
+       Buffer          buf = so->hashso_split_bucket_buf;
+
+       /*
+        * buffer for bucket being split must be valid as we acquire the pin on
+        * it before the start of scan and retain it till end of scan.
+        */
+       Assert(BufferIsValid(buf));
+
+       LockBuffer(buf, BUFFER_LOCK_SHARE);
+       PredicateLockPage(rel, BufferGetBlockNumber(buf), scan->xs_snapshot);
+
+       so->hashso_buc_split = true;
+
+       return buf;
+}
+
+/*
+ * Cross over from the bucket being split back to the bucket being
+ * populated, on whose primary page we have held a pin since _hash_first,
+ * and walk to the end of its chain (backward scans read chains tail to
+ * head).  Used both when a backward scan of the split bucket's own chain
+ * runs out of pages, and (via _hash_readprev) when the chain-walk inside
+ * _hash_readpage does.
+ *
+ * On return, the output parameters describe the last page in the
+ * populated bucket's chain, pinned and share-locked, and hashso_buc_split
+ * has been cleared to indicate that we are now scanning that bucket.  The
+ * flag must be cleared only after the chain walk; otherwise _hash_readnext
+ * would cross back over to the split bucket on reaching the end of the
+ * chain, instead of stopping there.
+ */
+static void
+_hash_step_to_populated_bucket(IndexScanDesc scan, Buffer *bufp,
+                                                          Page *pagep, 
HashPageOpaque *opaquep)
+{
+       HashScanOpaque so = (HashScanOpaque) scan->opaque;
+
+       *bufp = so->hashso_bucket_buf;
+
+       /*
+        * buffer for bucket being populated must be valid as we acquire the pin
+        * on it before the start of scan and retain it till end of scan.
+        */
+       Assert(BufferIsValid(*bufp));
+
+       LockBuffer(*bufp, BUFFER_LOCK_SHARE);
+       *pagep = BufferGetPage(*bufp);
+       *opaquep = HashPageGetOpaque(*pagep);
+
+       /* move to the end of bucket chain */
+       while (BlockNumberIsValid((*opaquep)->hasho_nextblkno))
+               _hash_readnext(scan, bufp, pagep, opaquep);
+
+       so->hashso_buc_split = false;
+}
+
 /*
  * Advance to next page in a bucket, if any.  If we are scanning the bucket
  * being populated during split operation then this function advances to the
@@ -216,23 +252,7 @@ _hash_readnext(IndexScanDesc scan,
                 * end of bucket, scan bucket being split if there was a split 
in
                 * progress at the start of scan.
                 */
-               *bufp = so->hashso_split_bucket_buf;
-
-               /*
-                * buffer for bucket being split must be valid as we acquire 
the pin
-                * on it before the start of scan and retain it till end of 
scan.
-                */
-               Assert(BufferIsValid(*bufp));
-
-               LockBuffer(*bufp, BUFFER_LOCK_SHARE);
-               PredicateLockPage(rel, BufferGetBlockNumber(*bufp), 
scan->xs_snapshot);
-
-               /*
-                * setting hashso_buc_split to true indicates that we are 
scanning
-                * bucket being split.
-                */
-               so->hashso_buc_split = true;
-
+               *bufp = _hash_step_to_split_bucket(scan);
                block_found = true;
        }
 
@@ -299,27 +319,7 @@ _hash_readprev(IndexScanDesc scan,
                 * end of bucket, scan bucket being populated if there was a 
split in
                 * progress at the start of scan.
                 */
-               *bufp = so->hashso_bucket_buf;
-
-               /*
-                * buffer for bucket being populated must be valid as we 
acquire the
-                * pin on it before the start of scan and retain it till end of 
scan.
-                */
-               Assert(BufferIsValid(*bufp));
-
-               LockBuffer(*bufp, BUFFER_LOCK_SHARE);
-               *pagep = BufferGetPage(*bufp);
-               *opaquep = HashPageGetOpaque(*pagep);
-
-               /* move to the end of bucket chain */
-               while (BlockNumberIsValid((*opaquep)->hasho_nextblkno))
-                       _hash_readnext(scan, bufp, pagep, opaquep);
-
-               /*
-                * setting hashso_buc_split to false indicates that we are 
scanning
-                * bucket being populated.
-                */
-               so->hashso_buc_split = false;
+               _hash_step_to_populated_bucket(scan, bufp, pagep, opaquep);
        }
 }
 

Reply via email to