There's a bug in the logic for hash index scans: we fail to fully account for concurrent bucket splits. This can lead to incorrect results, as an affected scan may fail to return all matching rows. This is due to an oversight in commit 7c75ef57, so all stable branches are affected.
The underlying problem is that _hash_next fails to step from one bucket of an in-progress to the other. In other words, it lacks the required transition logic that _hash_readnext already has (and _hash_readprev along with it). Note that the transition ordinarily happens anyway, inside _hash_readpage's no-matches loop, which is why this went unnoticed for so long: _hash_next only fails to make the transition when the last page it reads in the first bucket contains at least one matching tuple. In practice that means a tuple inserted into the bucket being populated after the split began. The attached bug fix patch v1-0001-* simply adds the missing handling to _hash_next (copied from _hash_readnext and _hash_readprev). I stumbled upon this bug when I asked Claude code to review a patch of mine that adds support for the new amgetbatch interface to the hash index AM (allowing hash indexes to perform I/O prefetching of heap blocks). I had Claude rewrite its original reproducer into a regression test and an isolation test, both of which appear in the second patch. Both tests are useful in their own way. The regression test runs quickly and so might be worth committing alongside the fix -- but it depends on leaving behind an incomplete split. The isolation test demonstrates that the bug doesn't hinge upon an incomplete split. The incomplete split case is particularly nasty, though, because the potential for wrong answers persists until an inserter completes the split. -- Peter Geoghegan
v1-0001-Fix-hash-index-scans-concurrent-with-bucket-split.patch
Description: Binary data
v1-0002-Add-tests-for-hash-index-scans-concurrent-with-bu.patch
Description: Binary data
