From 618d13ff3039ce99d3cf2a36ee230e814320720c Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Mon, 27 Jul 2026 15:50:30 +0200
Subject: [PATCH v10 15/21] Show expected filter selectivity in EXPLAIN

The selectivity is included in EXPLAIN (VERBOSE) or EXPLAIN (ANALYZE)
---
 src/backend/commands/explain.c                |  4 ++
 src/test/regress/expected/join.out            | 20 ++++----
 src/test/regress/expected/returning.out       | 46 +++++++++----------
 src/test/regress/expected/select_parallel.out |  2 +-
 4 files changed, 38 insertions(+), 34 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index fb56b324a9f..a4964478ed3 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2676,6 +2676,10 @@ show_bloom_filter_info(PlanState *planstate, List *ancestors,
 			appendStringInfo(&buf, "Bloom Filter %d: keys=%s",
 							 bf->producer_id, keys.data);
 
+			if (es->verbose || es->analyze)
+				appendStringInfo(&buf, " expected=%.1f%%",
+								 bf->selectivity * 100.0);
+
 			/* include the counts only during ANALYZE */
 			if (es->analyze && bfs != NULL)
 			{
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index c5945f4f29a..e6dc78f6117 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -7618,7 +7618,7 @@ where exists (select 1 from t t4
                                  Hash Cond: (t5.b = t4.b)
                                  ->  Seq Scan on pg_temp.t t5
                                        Output: t5.a, t5.b
-                                       Bloom Filter 1: keys=(t5.b)
+                                       Bloom Filter 1: keys=(t5.b) expected=0.5%
                                  ->  Hash
                                        Output: t4.b, t4.a
                                        Bloom Filter 1
@@ -10364,14 +10364,14 @@ select * from j1 inner join j2 on j1.id > j2.id;
 -- ensure non-unique rel is not chosen as inner
 explain (verbose, costs off)
 select * from j1 inner join j3 on j1.id = j3.id;
-              QUERY PLAN              
---------------------------------------
+                     QUERY PLAN                      
+-----------------------------------------------------
  Hash Join
    Output: j1.id, j3.id
    Hash Cond: (j1.id = j3.id)
    ->  Seq Scan on public.j1
          Output: j1.id
-         Bloom Filter 1: keys=(j1.id)
+         Bloom Filter 1: keys=(j1.id) expected=66.7%
    ->  Hash
          Output: j3.id
          Bloom Filter 1
@@ -10466,15 +10466,15 @@ select * from j1 natural join j2;
 explain (verbose, costs off)
 select * from j1
 inner join (select distinct id from j3) j3 on j1.id = j3.id;
-                  QUERY PLAN                   
------------------------------------------------
+                     QUERY PLAN                      
+-----------------------------------------------------
  Hash Join
    Output: j1.id, j3.id
    Inner Unique: true
    Hash Cond: (j1.id = j3.id)
    ->  Seq Scan on public.j1
          Output: j1.id
-         Bloom Filter 1: keys=(j1.id)
+         Bloom Filter 1: keys=(j1.id) expected=33.3%
    ->  Hash
          Output: j3.id
          Bloom Filter 1
@@ -10491,15 +10491,15 @@ inner join (select distinct id from j3) j3 on j1.id = j3.id;
 explain (verbose, costs off)
 select * from j1
 inner join (select id from j3 group by id) j3 on j1.id = j3.id;
-                  QUERY PLAN                   
------------------------------------------------
+                     QUERY PLAN                      
+-----------------------------------------------------
  Hash Join
    Output: j1.id, j3.id
    Inner Unique: true
    Hash Cond: (j1.id = j3.id)
    ->  Seq Scan on public.j1
          Output: j1.id
-         Bloom Filter 1: keys=(j1.id)
+         Bloom Filter 1: keys=(j1.id) expected=33.3%
    ->  Hash
          Output: j3.id
          Bloom Filter 1
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index dc44871b682..9ba8be9f774 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -713,32 +713,32 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
    Update on pg_temp.foo foo_1
    ->  Hash Join
          Output: foo_2.f1, (foo_2.f3 + 1), joinme.ctid, foo_2.ctid, joinme_1.ctid, joinme.other, foo_1.tableoid, foo_1.ctid, foo_2.tableoid
-         Hash Cond: (foo_1.f2 = joinme.f2j)
-         ->  Hash Join
-               Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, joinme_1.ctid, joinme_1.f2j
-               Hash Cond: (joinme_1.f2j = foo_1.f2)
-               ->  Seq Scan on pg_temp.joinme joinme_1
-                     Output: joinme_1.ctid, joinme_1.f2j
-                     Bloom Filter 1: keys=(joinme_1.f2j)
-               ->  Hash
-                     Output: foo_1.f2, foo_1.tableoid, foo_1.ctid
-                     Bloom Filter 1
-                     ->  Seq Scan on pg_temp.foo foo_1
-                           Output: foo_1.f2, foo_1.tableoid, foo_1.ctid
+         Hash Cond: (joinme_1.f2j = foo_1.f2)
+         ->  Seq Scan on pg_temp.joinme joinme_1
+               Output: joinme_1.ctid, joinme_1.f2j
          ->  Hash
-               Output: joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+               Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
                ->  Hash Join
-                     Output: joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
-                     Hash Cond: (joinme.f2j = foo_2.f2)
-                     ->  Seq Scan on pg_temp.joinme
-                           Output: joinme.ctid, joinme.other, joinme.f2j
-                           Bloom Filter 2: keys=(joinme.f2j)
+                     Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+                     Hash Cond: (joinme.f2j = foo_1.f2)
+                     ->  Hash Join
+                           Output: joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+                           Hash Cond: (joinme.f2j = foo_2.f2)
+                           ->  Seq Scan on pg_temp.joinme
+                                 Output: joinme.ctid, joinme.other, joinme.f2j
+                                 Bloom Filter 1: keys=(joinme.f2j) expected=0.5%
+                                 Bloom Filter 2: keys=(joinme.f2j) expected=2.0%
+                           ->  Hash
+                                 Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+                                 Bloom Filter 1
+                                 ->  Seq Scan on pg_temp.foo foo_2
+                                       Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+                                       Filter: (foo_2.f3 = 57)
                      ->  Hash
-                           Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+                           Output: foo_1.f2, foo_1.tableoid, foo_1.ctid
                            Bloom Filter 2
-                           ->  Seq Scan on pg_temp.foo foo_2
-                                 Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
-                                 Filter: (foo_2.f3 = 57)
+                           ->  Seq Scan on pg_temp.foo foo_1
+                                 Output: foo_1.f2, foo_1.tableoid, foo_1.ctid
 (31 rows)
 
 UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
@@ -797,7 +797,7 @@ UPDATE joinview SET f3 = f3 + 1, f4 = 7 WHERE f3 = 58
          Hash Cond: (joinme.f2j = foo.f2)
          ->  Seq Scan on pg_temp.joinme
                Output: joinme.other, joinme.ctid, joinme.f2j
-               Bloom Filter 1: keys=(joinme.f2j)
+               Bloom Filter 1: keys=(joinme.f2j) expected=0.5%
          ->  Hash
                Output: foo.f3, foo.f1, foo.f2, foo.f4, foo.ctid, foo.tableoid
                Bloom Filter 1
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 8f800a66c5e..9d6d213d3e1 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -1133,7 +1133,7 @@ explain (costs off, verbose)
          Hash Cond: ((a.unique1 = b.unique1) AND (a.two = (row_number() OVER w1)))
          ->  Seq Scan on public.tenk1 a
                Output: a.unique1, a.unique2, a.two, a.four, a.ten, a.twenty, a.hundred, a.thousand, a.twothousand, a.fivethous, a.tenthous, a.odd, a.even, a.stringu1, a.stringu2, a.string4
-               Bloom Filter 1: keys=(a.unique1, a.two)
+               Bloom Filter 1: keys=(a.unique1, a.two) expected=0.5%
          ->  Hash
                Output: b.unique1, (row_number() OVER w1)
                Bloom Filter 1
-- 
2.50.1 (Apple Git-155)

