On Mon, Apr 22, 2024 at 1:01 PM Melanie Plageman
<melanieplage...@gmail.com> wrote:
>
> On Thu, Apr 18, 2024 at 5:39 AM Tomas Vondra
> <tomas.von...@enterprisedb.com> wrote:
> >
> > On 4/18/24 09:10, Michael Paquier wrote:
> > > On Sun, Apr 07, 2024 at 10:54:56AM -0400, Melanie Plageman wrote:
> > >> I've added an open item [1], because what's one open item when you can
> > >> have two? (me)
> > >
> > > And this is still an open item as of today.  What's the plan to move
> > > forward here?
> >
> > AFAIK the plan is to replace the asserts with actually resetting the
> > rs_empty_tuples_pending field to 0, as suggested by Melanie a week ago.
> > I assume she was busy with the post-freeze AM reworks last week, so this
> > was on a back burner.
>
> yep, sorry. Also I took a few days off. I'm just catching up today. I
> want to pop in one of Richard or Tomas' examples as a test, since it
> seems like it would add some coverage. I will have a patch soon.

The patch with a fix is attached. I put the test in
src/test/regress/sql/join.sql. It isn't the perfect location because
it is testing something exercisable with a join but not directly
related to the fact that it is a join. I also considered
src/test/regress/sql/select.sql, but it also isn't directly related to
the query being a SELECT query. If there is a better place for a test
of a bitmap heap scan edge case, let me know.

One other note: there is some concurrency effect in the parallel
schedule group containing "join" where you won't trip the assert if
all the tests in that group in the parallel schedule are run. But, if
you would like to verify that the test exercises the correct code,
just reduce the group containing "join".

- Melanie
From 6ad777979c335f6cc16d3936defb634176a44995 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplage...@gmail.com>
Date: Tue, 23 Apr 2024 11:45:37 -0400
Subject: [PATCH v1] BitmapHeapScan: Remove incorrect assert

04e72ed617be pushed the skip fetch optimization (allowing bitmap heap
scans to operate like index-only scans if none of the underlying data is
needed) into heap AM-specific bitmap heap scan code.

04e72ed617be added an assert that all tuples in blocks eligible for the
optimization had been NULL-filled and emitted by the end of the scan.
This assert is incorrect when not all tuples need be scanned to execute
the query; for example: a join in which not all inner tuples need to be
scanned before skipping to the next outer tuple.

Author: Melanie Plageman
Reviewed-by: Richard Guo, Tomas Vondra
Discussion: https://postgr.es/m/CAMbWs48orzZVXa7-vP9Nt7vQWLTE04Qy4PePaLQYsVNQgo6qRg%40mail.gmail.com
---
 src/backend/access/heap/heapam.c   |  4 ++--
 src/test/regress/expected/join.out | 36 ++++++++++++++++++++++++++++++
 src/test/regress/sql/join.sql      | 24 ++++++++++++++++++++
 3 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 4a4cf76269d..8906f161320 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1184,7 +1184,7 @@ heap_rescan(TableScanDesc sscan, ScanKey key, bool set_params,
 		scan->rs_vmbuffer = InvalidBuffer;
 	}
 
-	Assert(scan->rs_empty_tuples_pending == 0);
+	scan->rs_empty_tuples_pending = 0;
 
 	/*
 	 * The read stream is reset on rescan. This must be done before
@@ -1216,7 +1216,7 @@ heap_endscan(TableScanDesc sscan)
 	if (BufferIsValid(scan->rs_vmbuffer))
 		ReleaseBuffer(scan->rs_vmbuffer);
 
-	Assert(scan->rs_empty_tuples_pending == 0);
+	scan->rs_empty_tuples_pending = 0;
 
 	/*
 	 * Must free the read stream before freeing the BufferAccessStrategy.
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 8b640c2fc2f..ce73939c267 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -8956,3 +8956,39 @@ where exists (select 1 from j3
 (13 rows)
 
 drop table j3;
+-- Check the case when:
+--   1. A join doesn't require all inner tuples to be scanned for each outer
+--      tuple, and
+--   2. The inner side is scanned using a bitmap heap scan, and
+--   3. The bitmap heap scan is eligible for the "skip fetch" optimization.
+--      This optimization is usable when no data from the underlying table is
+--      needed.
+CREATE TABLE skip_fetch (a INT, b INT) WITH (fillfactor=10);
+INSERT INTO skip_fetch SELECT i % 3, i FROM GENERATE_SERIES(0,30) i;
+CREATE INDEX ON skip_fetch(a);
+VACUUM (ANALYZE) skip_fetch;
+SET enable_indexonlyscan = off;
+set enable_indexscan = off;
+SET enable_seqscan = off;
+EXPLAIN (COSTS OFF)
+SELECT t1.a FROM skip_fetch t1 LEFT JOIN skip_fetch t2 ON t2.a = 1 WHERE t2.a IS NULL;
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Nested Loop Anti Join
+   ->  Seq Scan on skip_fetch t1
+   ->  Materialize
+         ->  Bitmap Heap Scan on skip_fetch t2
+               Recheck Cond: (a = 1)
+               ->  Bitmap Index Scan on skip_fetch_a_idx
+                     Index Cond: (a = 1)
+(7 rows)
+
+SELECT t1.a FROM skip_fetch t1 LEFT JOIN skip_fetch t2 ON t2.a = 1 WHERE t2.a IS NULL;
+ a 
+---
+(0 rows)
+
+RESET enable_indexonlyscan;
+RESET enable_indexscan;
+RESET enable_seqscan;
+DROP TABLE skip_fetch;
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index c4c6c7b8ba2..700631cd938 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -3374,3 +3374,27 @@ where exists (select 1 from j3
       and t1.unique1 < 1;
 
 drop table j3;
+
+-- Check the case when:
+--   1. A join doesn't require all inner tuples to be scanned for each outer
+--      tuple, and
+--   2. The inner side is scanned using a bitmap heap scan, and
+--   3. The bitmap heap scan is eligible for the "skip fetch" optimization.
+--      This optimization is usable when no data from the underlying table is
+--      needed.
+CREATE TABLE skip_fetch (a INT, b INT) WITH (fillfactor=10);
+INSERT INTO skip_fetch SELECT i % 3, i FROM GENERATE_SERIES(0,30) i;
+CREATE INDEX ON skip_fetch(a);
+VACUUM (ANALYZE) skip_fetch;
+
+SET enable_indexonlyscan = off;
+set enable_indexscan = off;
+SET enable_seqscan = off;
+EXPLAIN (COSTS OFF)
+SELECT t1.a FROM skip_fetch t1 LEFT JOIN skip_fetch t2 ON t2.a = 1 WHERE t2.a IS NULL;
+SELECT t1.a FROM skip_fetch t1 LEFT JOIN skip_fetch t2 ON t2.a = 1 WHERE t2.a IS NULL;
+
+RESET enable_indexonlyscan;
+RESET enable_indexscan;
+RESET enable_seqscan;
+DROP TABLE skip_fetch;
-- 
2.40.1

Reply via email to