public inbox for [email protected]  
help / color / mirror / Atom feed
From: Peter Geoghegan <[email protected]>
To: Amit Kapila <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Cc: Robert Haas <[email protected]>
Subject: Re: Hash index bucket split bug
Date: Thu, 9 Jul 2026 11:24:59 -0400
Message-ID: <CAH2-WznKyrDzTA+wvBYw0zbUe9f2JyUOG-Jm368Cs03sgEB_1Q@mail.gmail.com> (raw)
In-Reply-To: <CAA4eK1+pd3T8ULEF6P-9AzpTCdWMG+ciWYopa+9xjj-urEwmRg@mail.gmail.com>
References: <CAH2-Wz=4CJK9ysZzbzxBGGTkgAg9ib8cJyCx=9q_d1r8tf0ang@mail.gmail.com>
	<CAA4eK1+pd3T8ULEF6P-9AzpTCdWMG+ciWYopa+9xjj-urEwmRg@mail.gmail.com>

On Thu, Jul 9, 2026 at 6:45 AM Amit Kapila <[email protected]> wrote:
> I am getting following regression diff even after applying the patch:
> -step s1_split: <... completed>
>  step s2_scan:
>      SELECT count(*) FROM hash_split_test
>       WHERE v = (SELECT k FROM hash_split_key);
> @@ -68,3 +67,4 @@
>     11
>  (1 row)

This is just a minor test flakiness issue with the isolation test.
Apparently, we need to force a deterministic report position by adding
a no-op step to the splitter's session.

Attached v2 has a more stable version of the isolation test (no other
changes compared to V1). Does this version work for you?

The isolation test is just for illustrative purposes. I don't think
that it's committable.

-- 
Peter Geoghegan


Attachments:

  [application/octet-stream] v2-0001-Fix-hash-index-scans-concurrent-with-bucket-split.patch (2.7K, ../CAH2-WznKyrDzTA+wvBYw0zbUe9f2JyUOG-Jm368Cs03sgEB_1Q@mail.gmail.com/2-v2-0001-Fix-hash-index-scans-concurrent-with-bucket-split.patch)
  download | inline diff:
From a25ac1014587bc29c9ee4f2d6406d25afbf759ee Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <[email protected]>
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



  [application/octet-stream] v2-0002-Add-tests-for-hash-index-scans-concurrent-with-bu.patch (17.2K, ../CAH2-WznKyrDzTA+wvBYw0zbUe9f2JyUOG-Jm368Cs03sgEB_1Q@mail.gmail.com/3-v2-0002-Add-tests-for-hash-index-scans-concurrent-with-bu.patch)
  download | inline diff:
From 86090e0249806587125b781fab537b5cc086d3d8 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <[email protected]>
Date: Wed, 8 Jul 2026 15:18:06 -0400
Subject: [PATCH v2 2/2] Add tests for hash index scans concurrent with bucket
 splits.

Test the bug fixed by the preceding commit at two levels, using two new
injection points in _hash_splitbucket: an error-mode point placed
before tuple relocation begins (where existing code can fail anyway),
and a wait-mode point placed after tuple relocation, where the splitter
holds no buffer content locks.

A regress test uses the error-mode point to interrupt a split partway
through, then verifies that forward, backward, and bitmap scans of the
affected bucket pair all agree with a seqscan.  The bug isn't specific
to interrupted splits: interrupting a split is just the most convenient
way to hold the bucket pair in the same state that scans observe while
any split is in progress, making the test deterministic without a
second session.

An isolation test proves that claim directly: it pauses a splitter on
the wait-mode point and runs the same scans from a second session while
the split remains genuinely in progress, then lets the split finish and
scans once more.

Co-Authored-By: Claude Fable 5 <[email protected]>
---
 src/backend/access/hash/hashpage.c            |   5 +
 src/test/modules/injection_points/Makefile    |   3 +-
 .../injection_points/expected/hash-split.out  |  71 ++++++++++
 .../injection_points/expected/hash_split.out  | 131 ++++++++++++++++++
 src/test/modules/injection_points/meson.build |   2 +
 .../injection_points/specs/hash-split.spec    |  85 ++++++++++++
 .../injection_points/sql/hash_split.sql       |  80 +++++++++++
 7 files changed, 376 insertions(+), 1 deletion(-)
 create mode 100644 src/test/modules/injection_points/expected/hash-split.out
 create mode 100644 src/test/modules/injection_points/expected/hash_split.out
 create mode 100644 src/test/modules/injection_points/specs/hash-split.spec
 create mode 100644 src/test/modules/injection_points/sql/hash_split.sql

diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8099b0d02..c2e7213c4 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -35,6 +35,7 @@
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 
 static bool _hash_alloc_buckets(Relation rel, BlockNumber firstblock,
@@ -1104,6 +1105,8 @@ _hash_splitbucket(Relation rel,
 	npage = BufferGetPage(nbuf);
 	nopaque = HashPageGetOpaque(npage);
 
+	INJECTION_POINT("hash-split-before-relocation", NULL);
+
 	/* Copy the predicate locks from old bucket to new bucket. */
 	PredicateLockPageSplit(rel,
 						   BufferGetBlockNumber(bucket_obuf),
@@ -1252,6 +1255,8 @@ _hash_splitbucket(Relation rel,
 			/* be tidy */
 			for (i = 0; i < nitups; i++)
 				pfree(itups[i]);
+
+			INJECTION_POINT("hash-split-after-relocation", NULL);
 			break;
 		}
 
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index c01d2fb09..23e00207b 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -9,10 +9,11 @@ EXTENSION = injection_points
 DATA = injection_points--1.0.sql
 PGFILEDESC = "injection_points - facility for injection points"
 
-REGRESS = injection_points hashagg reindex_conc vacuum
+REGRESS = injection_points hashagg reindex_conc vacuum hash_split
 REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
 
 ISOLATION = basic \
+	    hash-split \
 	    inplace \
 	    repack \
 	    repack_temporal \
diff --git a/src/test/modules/injection_points/expected/hash-split.out b/src/test/modules/injection_points/expected/hash-split.out
new file mode 100644
index 000000000..49620b79c
--- /dev/null
+++ b/src/test/modules/injection_points/expected/hash-split.out
@@ -0,0 +1,71 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_split s2_insert s2_scan s2_back s2_detach s2_wakeup s1_noop s2_scan
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_split: 
+    INSERT INTO hash_split_test
+      SELECT 1000000 + g FROM hash_split_geom, generate_series(1, 320 * nbuckets) g;
+ <waiting ...>
+step s2_insert: 
+    INSERT INTO hash_split_test SELECT k FROM hash_split_key;
+
+step s2_scan: 
+    SELECT count(*) FROM hash_split_test
+     WHERE v = (SELECT k FROM hash_split_key);
+
+count
+-----
+   11
+(1 row)
+
+step s2_back: 
+    BEGIN;
+    DECLARE c SCROLL CURSOR FOR
+      SELECT v = (SELECT k FROM hash_split_key) FROM hash_split_test
+       WHERE v = (SELECT k FROM hash_split_key);
+    MOVE FORWARD ALL IN c;
+    FETCH BACKWARD ALL FROM c;
+    COMMIT;
+
+?column?
+--------
+t       
+t       
+t       
+t       
+t       
+t       
+t       
+t       
+t       
+t       
+t       
+(11 rows)
+
+step s2_detach: SELECT injection_points_detach('hash-split-after-relocation');
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+step s2_wakeup: SELECT injection_points_wakeup('hash-split-after-relocation');
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s1_split: <... completed>
+step s1_noop: 
+step s2_scan: 
+    SELECT count(*) FROM hash_split_test
+     WHERE v = (SELECT k FROM hash_split_key);
+
+count
+-----
+   11
+(1 row)
+
diff --git a/src/test/modules/injection_points/expected/hash_split.out b/src/test/modules/injection_points/expected/hash_split.out
new file mode 100644
index 000000000..0754618c2
--- /dev/null
+++ b/src/test/modules/injection_points/expected/hash_split.out
@@ -0,0 +1,131 @@
+-- Test hash index scans of a bucket pair whose split is incomplete
+--
+-- The behavior tested here is not specific to incomplete splits: it
+-- applies to any scan that runs while the bucket pair's split-in-progress
+-- flags are set, which is the case for the duration of every split.
+-- Interrupting a split is merely the most convenient way to hold the
+-- bucket pair in that state: it makes the tests below deterministic,
+-- without any need for a second session.  The hash-split isolation test
+-- provides equivalent coverage for scans that run during a live split.
+CREATE EXTENSION injection_points;
+SELECT injection_points_set_local();
+ injection_points_set_local 
+----------------------------
+ 
+(1 row)
+
+CREATE TABLE hash_split_test (v int4) WITH (autovacuum_enabled = false);
+CREATE INDEX hash_split_index ON hash_split_test USING hash (v);
+-- Determine the layout of the (never-split) index: one metapage, nbuckets
+-- bucket pages, and one bitmap page.  The index's first bucket split will
+-- split bucket 0, creating bucket nbuckets.
+CREATE TEMP TABLE hash_split_geom AS
+  SELECT pg_relation_size('hash_split_index') /
+         current_setting('block_size')::bigint - 2 AS nbuckets;
+-- sanity: initial bucket counts are always powers of two
+SELECT nbuckets > 0 AND (nbuckets & (nbuckets - 1)) = 0 AS nbuckets_ok
+  FROM hash_split_geom;
+ nbuckets_ok 
+-------------
+ t
+(1 row)
+
+-- Choose a value that maps to bucket 0 before the split and to the new
+-- bucket afterwards
+CREATE TEMP TABLE hash_split_key AS
+  SELECT min(v)::int4 AS k
+    FROM generate_series(1, 10000) v, hash_split_geom
+   WHERE (hashint4(v::int4) & (2 * nbuckets - 1)) = nbuckets;
+INSERT INTO hash_split_test
+  SELECT k FROM hash_split_key, generate_series(1, 10);
+-- Error out during the first bucket split, leaving it incomplete
+SELECT injection_points_attach('hash-split-before-relocation', 'error');
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+INSERT INTO hash_split_test
+  SELECT 1000000 + g FROM hash_split_geom, generate_series(1, 350 * nbuckets) g;
+ERROR:  error triggered for injection point hash-split-before-relocation
+SELECT injection_points_detach('hash-split-before-relocation');
+ injection_points_detach 
+-------------------------
+ 
+(1 row)
+
+-- This insertion goes to the new bucket, which is still flagged as being
+-- populated by the incomplete split
+INSERT INTO hash_split_test SELECT k FROM hash_split_key;
+-- Scans of that bucket must visit both buckets of the incomplete split:
+-- the fresh row from the new bucket, plus the 10 older rows that remain
+-- in bucket 0
+SET enable_seqscan = off;
+SET enable_bitmapscan = off;
+EXPLAIN (COSTS OFF)
+  SELECT count(*) FROM hash_split_test
+   WHERE v = (SELECT k FROM hash_split_key);
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Aggregate
+   InitPlan expr_1
+     ->  Seq Scan on hash_split_key
+           Disabled: true
+   ->  Index Scan using hash_split_index on hash_split_test
+         Index Cond: (v = (InitPlan expr_1).col1)
+(6 rows)
+
+SELECT count(*) FROM hash_split_test
+ WHERE v = (SELECT k FROM hash_split_key);
+ count 
+-------
+    11
+(1 row)
+
+-- Backward scans must visit both buckets too, in the opposite order
+BEGIN;
+DECLARE c SCROLL CURSOR FOR
+  SELECT v = (SELECT k FROM hash_split_key) FROM hash_split_test
+   WHERE v = (SELECT k FROM hash_split_key);
+MOVE FORWARD ALL IN c;
+FETCH BACKWARD ALL FROM c;
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+(11 rows)
+
+COMMIT;
+-- Same count via a bitmap scan, which shares the underlying scan code
+SET enable_indexscan = off;
+SET enable_bitmapscan = on;
+SELECT count(*) FROM hash_split_test
+ WHERE v = (SELECT k FROM hash_split_key);
+ count 
+-------
+    11
+(1 row)
+
+-- And the ground truth, via the heap
+SET enable_bitmapscan = off;
+RESET enable_seqscan;
+SELECT count(*) FROM hash_split_test
+ WHERE v = (SELECT k FROM hash_split_key);
+ count 
+-------
+    11
+(1 row)
+
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+DROP TABLE hash_split_test;
+DROP EXTENSION injection_points;
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index 59dba1cb0..a0b0c4827 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -36,6 +36,7 @@ tests += {
       'hashagg',
       'reindex_conc',
       'vacuum',
+      'hash_split',
     ],
     'regress_args': ['--dlpath', meson.project_build_root() / 'src/test/regress'],
     # The injection points are cluster-wide, so disable installcheck
@@ -44,6 +45,7 @@ tests += {
   'isolation': {
     'specs': [
       'basic',
+      'hash-split',
       'inplace',
       'repack',
       'repack_temporal',
diff --git a/src/test/modules/injection_points/specs/hash-split.spec b/src/test/modules/injection_points/specs/hash-split.spec
new file mode 100644
index 000000000..1a8e95c4c
--- /dev/null
+++ b/src/test/modules/injection_points/specs/hash-split.spec
@@ -0,0 +1,85 @@
+# Test hash index scans that run while a bucket split is in progress.
+#
+# The interesting window opens once the split has relocated tuples to the
+# new bucket, before the buckets' split-in-progress flags are cleared.  A
+# concurrent scan whose value belongs to the new bucket must visit both
+# buckets of the pair: it skips the moved-by-split tuples in the new
+# bucket, so it has to read their authoritative copies from the old
+# bucket, plus any tuples inserted into the new bucket after the split
+# began.
+#
+# s1 performs the split, pausing inside that window on a wait injection
+# point (placed where the splitter holds no buffer content locks).  s2
+# then inserts a matching row (which goes to the new bucket) and scans,
+# both forwards and backwards.
+
+setup
+{
+    CREATE EXTENSION injection_points;
+    CREATE TABLE hash_split_test (v int4) WITH (autovacuum_enabled = false);
+    CREATE INDEX hash_split_index ON hash_split_test USING hash (v);
+    CREATE TABLE hash_split_geom AS
+      SELECT pg_relation_size('hash_split_index') /
+             current_setting('block_size')::bigint - 2 AS nbuckets;
+    CREATE TABLE hash_split_key AS
+      SELECT min(v)::int4 AS k
+        FROM generate_series(1, 10000) v, hash_split_geom
+       WHERE (hashint4(v::int4) & (2 * nbuckets - 1)) = nbuckets;
+    INSERT INTO hash_split_test
+      SELECT k FROM hash_split_key, generate_series(1, 10);
+}
+
+teardown
+{
+    DROP TABLE hash_split_test, hash_split_geom, hash_split_key;
+    DROP EXTENSION injection_points;
+}
+
+session s1
+setup
+{
+    SELECT injection_points_set_local();
+    SELECT injection_points_attach('hash-split-after-relocation', 'wait');
+}
+step s1_split
+{
+    INSERT INTO hash_split_test
+      SELECT 1000000 + g FROM hash_split_geom, generate_series(1, 320 * nbuckets) g;
+}
+step s1_noop { }
+
+session s2
+setup
+{
+    SET enable_seqscan = off;
+    SET enable_bitmapscan = off;
+}
+step s2_insert
+{
+    INSERT INTO hash_split_test SELECT k FROM hash_split_key;
+}
+step s2_scan
+{
+    SELECT count(*) FROM hash_split_test
+     WHERE v = (SELECT k FROM hash_split_key);
+}
+step s2_back
+{
+    BEGIN;
+    DECLARE c SCROLL CURSOR FOR
+      SELECT v = (SELECT k FROM hash_split_key) FROM hash_split_test
+       WHERE v = (SELECT k FROM hash_split_key);
+    MOVE FORWARD ALL IN c;
+    FETCH BACKWARD ALL FROM c;
+    COMMIT;
+}
+step s2_detach { SELECT injection_points_detach('hash-split-after-relocation'); }
+step s2_wakeup { SELECT injection_points_wakeup('hash-split-after-relocation'); }
+
+# Scan during the paused split, then let the split finish and scan again.
+# The detach must happen before the wakeup: the same INSERT can trigger
+# another split, which must not hit the injection point again.  The no-op
+# step in the splitter's session forces the isolation tester to wait for
+# s1_split to finish, keeping the position of its completion report stable
+# regardless of how long the remaining inserts take (see basic.spec).
+permutation s1_split s2_insert s2_scan s2_back s2_detach s2_wakeup s1_noop s2_scan
diff --git a/src/test/modules/injection_points/sql/hash_split.sql b/src/test/modules/injection_points/sql/hash_split.sql
new file mode 100644
index 000000000..010b65882
--- /dev/null
+++ b/src/test/modules/injection_points/sql/hash_split.sql
@@ -0,0 +1,80 @@
+-- Test hash index scans of a bucket pair whose split is incomplete
+--
+-- The behavior tested here is not specific to incomplete splits: it
+-- applies to any scan that runs while the bucket pair's split-in-progress
+-- flags are set, which is the case for the duration of every split.
+-- Interrupting a split is merely the most convenient way to hold the
+-- bucket pair in that state: it makes the tests below deterministic,
+-- without any need for a second session.  The hash-split isolation test
+-- provides equivalent coverage for scans that run during a live split.
+CREATE EXTENSION injection_points;
+SELECT injection_points_set_local();
+
+CREATE TABLE hash_split_test (v int4) WITH (autovacuum_enabled = false);
+CREATE INDEX hash_split_index ON hash_split_test USING hash (v);
+
+-- Determine the layout of the (never-split) index: one metapage, nbuckets
+-- bucket pages, and one bitmap page.  The index's first bucket split will
+-- split bucket 0, creating bucket nbuckets.
+CREATE TEMP TABLE hash_split_geom AS
+  SELECT pg_relation_size('hash_split_index') /
+         current_setting('block_size')::bigint - 2 AS nbuckets;
+-- sanity: initial bucket counts are always powers of two
+SELECT nbuckets > 0 AND (nbuckets & (nbuckets - 1)) = 0 AS nbuckets_ok
+  FROM hash_split_geom;
+
+-- Choose a value that maps to bucket 0 before the split and to the new
+-- bucket afterwards
+CREATE TEMP TABLE hash_split_key AS
+  SELECT min(v)::int4 AS k
+    FROM generate_series(1, 10000) v, hash_split_geom
+   WHERE (hashint4(v::int4) & (2 * nbuckets - 1)) = nbuckets;
+
+INSERT INTO hash_split_test
+  SELECT k FROM hash_split_key, generate_series(1, 10);
+
+-- Error out during the first bucket split, leaving it incomplete
+SELECT injection_points_attach('hash-split-before-relocation', 'error');
+INSERT INTO hash_split_test
+  SELECT 1000000 + g FROM hash_split_geom, generate_series(1, 350 * nbuckets) g;
+SELECT injection_points_detach('hash-split-before-relocation');
+
+-- This insertion goes to the new bucket, which is still flagged as being
+-- populated by the incomplete split
+INSERT INTO hash_split_test SELECT k FROM hash_split_key;
+
+-- Scans of that bucket must visit both buckets of the incomplete split:
+-- the fresh row from the new bucket, plus the 10 older rows that remain
+-- in bucket 0
+SET enable_seqscan = off;
+SET enable_bitmapscan = off;
+EXPLAIN (COSTS OFF)
+  SELECT count(*) FROM hash_split_test
+   WHERE v = (SELECT k FROM hash_split_key);
+SELECT count(*) FROM hash_split_test
+ WHERE v = (SELECT k FROM hash_split_key);
+
+-- Backward scans must visit both buckets too, in the opposite order
+BEGIN;
+DECLARE c SCROLL CURSOR FOR
+  SELECT v = (SELECT k FROM hash_split_key) FROM hash_split_test
+   WHERE v = (SELECT k FROM hash_split_key);
+MOVE FORWARD ALL IN c;
+FETCH BACKWARD ALL FROM c;
+COMMIT;
+
+-- Same count via a bitmap scan, which shares the underlying scan code
+SET enable_indexscan = off;
+SET enable_bitmapscan = on;
+SELECT count(*) FROM hash_split_test
+ WHERE v = (SELECT k FROM hash_split_key);
+-- And the ground truth, via the heap
+SET enable_bitmapscan = off;
+RESET enable_seqscan;
+SELECT count(*) FROM hash_split_test
+ WHERE v = (SELECT k FROM hash_split_key);
+RESET enable_indexscan;
+RESET enable_bitmapscan;
+
+DROP TABLE hash_split_test;
+DROP EXTENSION injection_points;
-- 
2.53.0



view thread (4+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected]
  Subject: Re: Hash index bucket split bug
  In-Reply-To: <CAH2-WznKyrDzTA+wvBYw0zbUe9f2JyUOG-Jm368Cs03sgEB_1Q@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox