From 1cbf72ab58706dd194eaee2a620b27c8961f2001 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Mon, 27 Jul 2026 15:27:39 +0200
Subject: [PATCH v8 16/20] Add basic regression tests for filter pushdown

Adds three test suites specifically for Bloom filter pushdown, for
basic schemas - two relations, starjoin and snowflake.

The filter pushdown affects plans in a number of existing test suites,
in an accidental way. These tests are focused on filter pushdown,
generating plans with known plan.
---
 src/test/regress/expected/hashjoin_bloom.out  |  63 +++
 .../expected/hashjoin_bloom_snowflake.out     | 386 +++++++++++++++++
 .../regress/expected/hashjoin_bloom_star.out  | 408 ++++++++++++++++++
 src/test/regress/parallel_schedule            |   3 +
 src/test/regress/sql/hashjoin_bloom.sql       |  40 ++
 .../regress/sql/hashjoin_bloom_snowflake.sql  | 139 ++++++
 src/test/regress/sql/hashjoin_bloom_star.sql  | 147 +++++++
 7 files changed, 1186 insertions(+)
 create mode 100644 src/test/regress/expected/hashjoin_bloom.out
 create mode 100644 src/test/regress/expected/hashjoin_bloom_snowflake.out
 create mode 100644 src/test/regress/expected/hashjoin_bloom_star.out
 create mode 100644 src/test/regress/sql/hashjoin_bloom.sql
 create mode 100644 src/test/regress/sql/hashjoin_bloom_snowflake.sql
 create mode 100644 src/test/regress/sql/hashjoin_bloom_star.sql

diff --git a/src/test/regress/expected/hashjoin_bloom.out b/src/test/regress/expected/hashjoin_bloom.out
new file mode 100644
index 00000000000..fd6d895f4cc
--- /dev/null
+++ b/src/test/regress/expected/hashjoin_bloom.out
@@ -0,0 +1,63 @@
+-- tests to validate bloom filter pushdown
+-- no parallel query support for now
+SET max_parallel_workers_per_gather = 0;
+-- a couple very simple join queries
+CREATE TABLE bloom_simple_dim (id int, r real);
+CREATE TABLE bloom_simple_fact (id int, padding text);
+INSERT INTO bloom_simple_dim SELECT i, random() FROM generate_series(1,1000) s(i);
+INSERT INTO bloom_simple_fact
+SELECT
+    1 + mod(i, 1000),
+    md5(i::text)
+FROM generate_series(1,100000) s(i);
+VACUUM ANALYZE;
+-- simple query, no join is selective enough for a filter
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_simple_fact f
+JOIN bloom_simple_dim d ON (f.id = d.id);
+                 QUERY PLAN                 
+--------------------------------------------
+ Hash Join
+   Hash Cond: (f.id = d.id)
+   ->  Seq Scan on bloom_simple_fact f
+   ->  Hash
+         ->  Seq Scan on bloom_simple_dim d
+(5 rows)
+
+-- join is 75% selective (not enough for a filter to be created)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_simple_fact f
+JOIN bloom_simple_dim d ON (f.id = d.id)
+WHERE d.r < 0.75;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id = d.id)
+   ->  Seq Scan on bloom_simple_fact f
+   ->  Hash
+         ->  Seq Scan on bloom_simple_dim d
+               Filter: (r < '0.75'::double precision)
+(6 rows)
+
+-- join is 50% selective (enough for a filter to be pushed down)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_simple_fact f
+JOIN bloom_simple_dim d ON (f.id = d.id)
+WHERE d.r < 0.5;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id = d.id)
+   ->  Seq Scan on bloom_simple_fact f
+         Bloom Filter 1: keys=(id)
+   ->  Hash
+         Bloom Filter 1
+         ->  Seq Scan on bloom_simple_dim d
+               Filter: (r < '0.5'::double precision)
+(8 rows)
+
+DROP TABLE bloom_simple_dim;
+DROP TABLE bloom_simple_fact;
diff --git a/src/test/regress/expected/hashjoin_bloom_snowflake.out b/src/test/regress/expected/hashjoin_bloom_snowflake.out
new file mode 100644
index 00000000000..dbe7663614f
--- /dev/null
+++ b/src/test/regress/expected/hashjoin_bloom_snowflake.out
@@ -0,0 +1,386 @@
+-- tests to validate bloom filter pushdown
+-- no parallel query support for now
+SET max_parallel_workers_per_gather = 0;
+-- simple snowflake queries (fact + up to 7 dimensions)
+CREATE TABLE bloom_snowflake_dim_1_1 (id int, r real);
+CREATE TABLE bloom_snowflake_dim_1_2 (id int, r real);
+CREATE TABLE bloom_snowflake_dim_1 (id int, id11 int, id12 int, r real);
+CREATE TABLE bloom_snowflake_dim_2_1 (id int, r real);
+CREATE TABLE bloom_snowflake_dim_2_2 (id int, r real);
+CREATE TABLE bloom_snowflake_dim_2 (id int, id21 int, id22 int, r real);
+CREATE TABLE bloom_snowflake_fact (id1 int, id2 int, padding text);
+INSERT INTO bloom_snowflake_dim_1_1 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_1_2 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_1 SELECT i, 1 + mod(i, 10000), 1 + mod(i, 10000), random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_2_1 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_2_2 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_2 SELECT i, 1 + mod(i, 10000), 1 + mod(i, 10000), random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_fact
+SELECT
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    md5(i::text)
+FROM generate_series(1,1000000) s(i);
+VACUUM ANALYZE;
+-- simple query, no join is selective enough for a filter
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id);
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id1 = d1.id)
+   ->  Hash Join
+         Hash Cond: (f.id2 = d2.id)
+         ->  Seq Scan on bloom_snowflake_fact f
+         ->  Hash
+               ->  Hash Join
+                     Hash Cond: (d2.id22 = d22.id)
+                     ->  Hash Join
+                           Hash Cond: (d2.id21 = d21.id)
+                           ->  Seq Scan on bloom_snowflake_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_snowflake_dim_2_1 d21
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_2_2 d22
+   ->  Hash
+         ->  Hash Join
+               Hash Cond: (d1.id12 = d12.id)
+               ->  Hash Join
+                     Hash Cond: (d1.id11 = d11.id)
+                     ->  Seq Scan on bloom_snowflake_dim_1 d1
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_1_1 d11
+               ->  Hash
+                     ->  Seq Scan on bloom_snowflake_dim_1_2 d12
+(25 rows)
+
+-- join is 75% selective (not enough for a filter to be created)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d1.r < 0.75;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id2 = d2.id)
+   ->  Hash Join
+         Hash Cond: (f.id1 = d1.id)
+         ->  Seq Scan on bloom_snowflake_fact f
+         ->  Hash
+               ->  Hash Join
+                     Hash Cond: (d12.id = d1.id12)
+                     ->  Seq Scan on bloom_snowflake_dim_1_2 d12
+                     ->  Hash
+                           ->  Hash Join
+                                 Hash Cond: (d11.id = d1.id11)
+                                 ->  Seq Scan on bloom_snowflake_dim_1_1 d11
+                                 ->  Hash
+                                       ->  Seq Scan on bloom_snowflake_dim_1 d1
+                                             Filter: (r < '0.75'::double precision)
+   ->  Hash
+         ->  Hash Join
+               Hash Cond: (d2.id22 = d22.id)
+               ->  Hash Join
+                     Hash Cond: (d2.id21 = d21.id)
+                     ->  Seq Scan on bloom_snowflake_dim_2 d2
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_2_1 d21
+               ->  Hash
+                     ->  Seq Scan on bloom_snowflake_dim_2_2 d22
+(26 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d11.r < 0.75;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id2 = d2.id)
+   ->  Hash Join
+         Hash Cond: (f.id1 = d1.id)
+         ->  Seq Scan on bloom_snowflake_fact f
+         ->  Hash
+               ->  Hash Join
+                     Hash Cond: (d12.id = d1.id12)
+                     ->  Seq Scan on bloom_snowflake_dim_1_2 d12
+                     ->  Hash
+                           ->  Hash Join
+                                 Hash Cond: (d1.id11 = d11.id)
+                                 ->  Seq Scan on bloom_snowflake_dim_1 d1
+                                 ->  Hash
+                                       ->  Seq Scan on bloom_snowflake_dim_1_1 d11
+                                             Filter: (r < '0.75'::double precision)
+   ->  Hash
+         ->  Hash Join
+               Hash Cond: (d2.id22 = d22.id)
+               ->  Hash Join
+                     Hash Cond: (d2.id21 = d21.id)
+                     ->  Seq Scan on bloom_snowflake_dim_2 d2
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_2_1 d21
+               ->  Hash
+                     ->  Seq Scan on bloom_snowflake_dim_2_2 d22
+(26 rows)
+
+-- join is 50% selective (good enough for pushdown)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d1.r < 0.5;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id1 = d1.id)
+   ->  Hash Join
+         Hash Cond: (f.id2 = d2.id)
+         ->  Seq Scan on bloom_snowflake_fact f
+               Bloom Filter 3: keys=(id1)
+         ->  Hash
+               ->  Hash Join
+                     Hash Cond: (d2.id22 = d22.id)
+                     ->  Hash Join
+                           Hash Cond: (d2.id21 = d21.id)
+                           ->  Seq Scan on bloom_snowflake_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_snowflake_dim_2_1 d21
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_2_2 d22
+   ->  Hash
+         Bloom Filter 3
+         ->  Hash Join
+               Hash Cond: (d12.id = d1.id12)
+               ->  Seq Scan on bloom_snowflake_dim_1_2 d12
+                     Bloom Filter 2: keys=(id)
+               ->  Hash
+                     Bloom Filter 2
+                     ->  Hash Join
+                           Hash Cond: (d11.id = d1.id11)
+                           ->  Seq Scan on bloom_snowflake_dim_1_1 d11
+                                 Bloom Filter 1: keys=(id)
+                           ->  Hash
+                                 Bloom Filter 1
+                                 ->  Seq Scan on bloom_snowflake_dim_1 d1
+                                       Filter: (r < '0.5'::double precision)
+(32 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d11.r < 0.5;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id1 = d1.id)
+   ->  Hash Join
+         Hash Cond: (f.id2 = d2.id)
+         ->  Seq Scan on bloom_snowflake_fact f
+               Bloom Filter 3: keys=(id1)
+         ->  Hash
+               ->  Hash Join
+                     Hash Cond: (d2.id22 = d22.id)
+                     ->  Hash Join
+                           Hash Cond: (d2.id21 = d21.id)
+                           ->  Seq Scan on bloom_snowflake_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_snowflake_dim_2_1 d21
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_2_2 d22
+   ->  Hash
+         Bloom Filter 3
+         ->  Hash Join
+               Hash Cond: (d12.id = d1.id12)
+               ->  Seq Scan on bloom_snowflake_dim_1_2 d12
+                     Bloom Filter 2: keys=(id)
+               ->  Hash
+                     Bloom Filter 2
+                     ->  Hash Join
+                           Hash Cond: (d1.id11 = d11.id)
+                           ->  Seq Scan on bloom_snowflake_dim_1 d1
+                                 Bloom Filter 1: keys=(id11)
+                           ->  Hash
+                                 Bloom Filter 1
+                                 ->  Seq Scan on bloom_snowflake_dim_1_1 d11
+                                       Filter: (r < '0.5'::double precision)
+(32 rows)
+
+-- increase the accepted build size (includes the fact)
+SET bloom_filter_pushdown_max_build_relids = 4;
+-- join is 50% selective (good enough for pushdown)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d1.r < 0.5;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id1 = d1.id)
+   ->  Hash Join
+         Hash Cond: (f.id2 = d2.id)
+         ->  Seq Scan on bloom_snowflake_fact f
+               Bloom Filter 3: keys=(id1)
+         ->  Hash
+               ->  Hash Join
+                     Hash Cond: (d2.id22 = d22.id)
+                     ->  Hash Join
+                           Hash Cond: (d2.id21 = d21.id)
+                           ->  Seq Scan on bloom_snowflake_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_snowflake_dim_2_1 d21
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_2_2 d22
+   ->  Hash
+         Bloom Filter 3
+         ->  Hash Join
+               Hash Cond: (d12.id = d1.id12)
+               ->  Seq Scan on bloom_snowflake_dim_1_2 d12
+                     Bloom Filter 2: keys=(id)
+               ->  Hash
+                     Bloom Filter 2
+                     ->  Hash Join
+                           Hash Cond: (d11.id = d1.id11)
+                           ->  Seq Scan on bloom_snowflake_dim_1_1 d11
+                                 Bloom Filter 1: keys=(id)
+                           ->  Hash
+                                 Bloom Filter 1
+                                 ->  Seq Scan on bloom_snowflake_dim_1 d1
+                                       Filter: (r < '0.5'::double precision)
+(32 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d11.r < 0.5;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id1 = d1.id)
+   ->  Hash Join
+         Hash Cond: (f.id2 = d2.id)
+         ->  Seq Scan on bloom_snowflake_fact f
+               Bloom Filter 3: keys=(id1)
+         ->  Hash
+               ->  Hash Join
+                     Hash Cond: (d2.id22 = d22.id)
+                     ->  Hash Join
+                           Hash Cond: (d2.id21 = d21.id)
+                           ->  Seq Scan on bloom_snowflake_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_snowflake_dim_2_1 d21
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_2_2 d22
+   ->  Hash
+         Bloom Filter 3
+         ->  Hash Join
+               Hash Cond: (d12.id = d1.id12)
+               ->  Seq Scan on bloom_snowflake_dim_1_2 d12
+                     Bloom Filter 2: keys=(id)
+               ->  Hash
+                     Bloom Filter 2
+                     ->  Hash Join
+                           Hash Cond: (d1.id11 = d11.id)
+                           ->  Seq Scan on bloom_snowflake_dim_1 d1
+                                 Bloom Filter 1: keys=(id11)
+                           ->  Hash
+                                 Bloom Filter 1
+                                 ->  Seq Scan on bloom_snowflake_dim_1_1 d11
+                                       Filter: (r < '0.5'::double precision)
+(32 rows)
+
+-- needed to stabilize the join order
+SET join_collapse_limit = 1;
+-- two joins with 75% selectivity (not enough for pushdown individually,
+-- but good enough when combined)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d11.r < 0.75 AND d12.r < 0.75;
+                                   QUERY PLAN                                   
+--------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (d2.id22 = d22.id)
+   ->  Hash Join
+         Hash Cond: (d2.id21 = d21.id)
+         ->  Hash Join
+               Hash Cond: (f.id2 = d2.id)
+               ->  Hash Join
+                     Hash Cond: (d1.id12 = d12.id)
+                     ->  Hash Join
+                           Hash Cond: (d1.id11 = d11.id)
+                           ->  Hash Join
+                                 Hash Cond: (f.id1 = d1.id)
+                                 ->  Seq Scan on bloom_snowflake_fact f
+                                 ->  Hash
+                                       ->  Seq Scan on bloom_snowflake_dim_1 d1
+                           ->  Hash
+                                 ->  Seq Scan on bloom_snowflake_dim_1_1 d11
+                                       Filter: (r < '0.75'::double precision)
+                     ->  Hash
+                           ->  Seq Scan on bloom_snowflake_dim_1_2 d12
+                                 Filter: (r < '0.75'::double precision)
+               ->  Hash
+                     ->  Seq Scan on bloom_snowflake_dim_2 d2
+         ->  Hash
+               ->  Seq Scan on bloom_snowflake_dim_2_1 d21
+   ->  Hash
+         ->  Seq Scan on bloom_snowflake_dim_2_2 d22
+(27 rows)
+
+RESET join_collapse_limit;
+RESET bloom_filter_pushdown_max_build_relids;
+DROP TABLE bloom_snowflake_dim_1_1;
+DROP TABLE bloom_snowflake_dim_1_2;
+DROP TABLE bloom_snowflake_dim_1;
+DROP TABLE bloom_snowflake_dim_2_1;
+DROP TABLE bloom_snowflake_dim_2_2;
+DROP TABLE bloom_snowflake_dim_2;
+DROP TABLE bloom_snowflake_fact;
diff --git a/src/test/regress/expected/hashjoin_bloom_star.out b/src/test/regress/expected/hashjoin_bloom_star.out
new file mode 100644
index 00000000000..7e6a60d3266
--- /dev/null
+++ b/src/test/regress/expected/hashjoin_bloom_star.out
@@ -0,0 +1,408 @@
+-- tests to validate bloom filter pushdown
+-- no parallel query support for now
+SET max_parallel_workers_per_gather = 0;
+-- needed, because the filter pushdown eliminates one of the inputs for
+-- choosing join order (reduction of result size)
+SET join_collapse_limit = 1;
+-- simple starjoin queries (fact + up to 7 dimensions)
+CREATE TABLE bloom_star_dim_1 (id int, r real);
+CREATE TABLE bloom_star_dim_2 (id int, r real);
+CREATE TABLE bloom_star_dim_3 (id int, r real);
+CREATE TABLE bloom_star_dim_4 (id int, r real);
+CREATE TABLE bloom_star_dim_5 (id int, r real);
+CREATE TABLE bloom_star_dim_6 (id int, r real);
+CREATE TABLE bloom_star_dim_7 (id int, r real);
+CREATE TABLE bloom_star_fact (id1 int, id2 int, id3 int, id4 int, id5 int, id6 int, id7 int, padding text);
+INSERT INTO bloom_star_dim_1 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_2 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_3 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_4 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_5 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_6 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_7 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_fact
+SELECT
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    md5(i::text)
+FROM generate_series(1,1000000) s(i);
+VACUUM ANALYZE;
+-- simple query, no join is selective enough for a filter
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id);
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id7 = d7.id)
+   ->  Hash Join
+         Hash Cond: (f.id6 = d6.id)
+         ->  Hash Join
+               Hash Cond: (f.id5 = d5.id)
+               ->  Hash Join
+                     Hash Cond: (f.id4 = d4.id)
+                     ->  Hash Join
+                           Hash Cond: (f.id3 = d3.id)
+                           ->  Hash Join
+                                 Hash Cond: (f.id2 = d2.id)
+                                 ->  Hash Join
+                                       Hash Cond: (f.id1 = d1.id)
+                                       ->  Seq Scan on bloom_star_fact f
+                                       ->  Hash
+                                             ->  Seq Scan on bloom_star_dim_1 d1
+                                 ->  Hash
+                                       ->  Seq Scan on bloom_star_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_star_dim_3 d3
+                     ->  Hash
+                           ->  Seq Scan on bloom_star_dim_4 d4
+               ->  Hash
+                     ->  Seq Scan on bloom_star_dim_5 d5
+         ->  Hash
+               ->  Seq Scan on bloom_star_dim_6 d6
+   ->  Hash
+         ->  Seq Scan on bloom_star_dim_7 d7
+(29 rows)
+
+-- last join is 75% selective (not enough for a filter to be created)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d7.r < 0.75;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id7 = d7.id)
+   ->  Hash Join
+         Hash Cond: (f.id6 = d6.id)
+         ->  Hash Join
+               Hash Cond: (f.id5 = d5.id)
+               ->  Hash Join
+                     Hash Cond: (f.id4 = d4.id)
+                     ->  Hash Join
+                           Hash Cond: (f.id3 = d3.id)
+                           ->  Hash Join
+                                 Hash Cond: (f.id2 = d2.id)
+                                 ->  Hash Join
+                                       Hash Cond: (f.id1 = d1.id)
+                                       ->  Seq Scan on bloom_star_fact f
+                                       ->  Hash
+                                             ->  Seq Scan on bloom_star_dim_1 d1
+                                 ->  Hash
+                                       ->  Seq Scan on bloom_star_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_star_dim_3 d3
+                     ->  Hash
+                           ->  Seq Scan on bloom_star_dim_4 d4
+               ->  Hash
+                     ->  Seq Scan on bloom_star_dim_5 d5
+         ->  Hash
+               ->  Seq Scan on bloom_star_dim_6 d6
+   ->  Hash
+         ->  Seq Scan on bloom_star_dim_7 d7
+               Filter: (r < '0.75'::double precision)
+(30 rows)
+
+-- last join is 50% selective (good enough for a filter push down)
+-- the join will be executed done last
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d7.r < 0.5;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id7 = d7.id)
+   ->  Hash Join
+         Hash Cond: (f.id6 = d6.id)
+         ->  Hash Join
+               Hash Cond: (f.id5 = d5.id)
+               ->  Hash Join
+                     Hash Cond: (f.id4 = d4.id)
+                     ->  Hash Join
+                           Hash Cond: (f.id3 = d3.id)
+                           ->  Hash Join
+                                 Hash Cond: (f.id2 = d2.id)
+                                 ->  Hash Join
+                                       Hash Cond: (f.id1 = d1.id)
+                                       ->  Seq Scan on bloom_star_fact f
+                                             Bloom Filter 1: keys=(id7)
+                                       ->  Hash
+                                             ->  Seq Scan on bloom_star_dim_1 d1
+                                 ->  Hash
+                                       ->  Seq Scan on bloom_star_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_star_dim_3 d3
+                     ->  Hash
+                           ->  Seq Scan on bloom_star_dim_4 d4
+               ->  Hash
+                     ->  Seq Scan on bloom_star_dim_5 d5
+         ->  Hash
+               ->  Seq Scan on bloom_star_dim_6 d6
+   ->  Hash
+         Bloom Filter 1
+         ->  Seq Scan on bloom_star_dim_7 d7
+               Filter: (r < '0.5'::double precision)
+(32 rows)
+
+-- first join is 50% selective (good enough for a filter push down)
+-- the join will be executed done last
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d1.r < 0.5;
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id7 = d7.id)
+   ->  Hash Join
+         Hash Cond: (f.id6 = d6.id)
+         ->  Hash Join
+               Hash Cond: (f.id5 = d5.id)
+               ->  Hash Join
+                     Hash Cond: (f.id4 = d4.id)
+                     ->  Hash Join
+                           Hash Cond: (f.id3 = d3.id)
+                           ->  Hash Join
+                                 Hash Cond: (f.id2 = d2.id)
+                                 ->  Hash Join
+                                       Hash Cond: (f.id1 = d1.id)
+                                       ->  Seq Scan on bloom_star_fact f
+                                             Bloom Filter 1: keys=(id1)
+                                       ->  Hash
+                                             Bloom Filter 1
+                                             ->  Seq Scan on bloom_star_dim_1 d1
+                                                   Filter: (r < '0.5'::double precision)
+                                 ->  Hash
+                                       ->  Seq Scan on bloom_star_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_star_dim_3 d3
+                     ->  Hash
+                           ->  Seq Scan on bloom_star_dim_4 d4
+               ->  Hash
+                     ->  Seq Scan on bloom_star_dim_5 d5
+         ->  Hash
+               ->  Seq Scan on bloom_star_dim_6 d6
+   ->  Hash
+         ->  Seq Scan on bloom_star_dim_7 d7
+(32 rows)
+
+-- two joins 50% selective
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d1.r < 0.4 AND d7.r < 0.5;
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id7 = d7.id)
+   ->  Hash Join
+         Hash Cond: (f.id6 = d6.id)
+         ->  Hash Join
+               Hash Cond: (f.id5 = d5.id)
+               ->  Hash Join
+                     Hash Cond: (f.id4 = d4.id)
+                     ->  Hash Join
+                           Hash Cond: (f.id3 = d3.id)
+                           ->  Hash Join
+                                 Hash Cond: (f.id2 = d2.id)
+                                 ->  Hash Join
+                                       Hash Cond: (f.id1 = d1.id)
+                                       ->  Seq Scan on bloom_star_fact f
+                                             Bloom Filter 1: keys=(id1)
+                                             Bloom Filter 2: keys=(id7)
+                                       ->  Hash
+                                             Bloom Filter 1
+                                             ->  Seq Scan on bloom_star_dim_1 d1
+                                                   Filter: (r < '0.4'::double precision)
+                                 ->  Hash
+                                       ->  Seq Scan on bloom_star_dim_2 d2
+                           ->  Hash
+                                 ->  Seq Scan on bloom_star_dim_3 d3
+                     ->  Hash
+                           ->  Seq Scan on bloom_star_dim_4 d4
+               ->  Hash
+                     ->  Seq Scan on bloom_star_dim_5 d5
+         ->  Hash
+               ->  Seq Scan on bloom_star_dim_6 d6
+   ->  Hash
+         Bloom Filter 2
+         ->  Seq Scan on bloom_star_dim_7 d7
+               Filter: (r < '0.5'::double precision)
+(35 rows)
+
+-- all joins selective for a filter (more than how many we allow)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d1.r < 0.3 AND d2.r < 0.35 AND d3.r < 0.4 AND d4.r < 0.45 AND d5.r < 0.5 AND d6.r < 0.55 AND d7.r < 0.6;
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id7 = d7.id)
+   ->  Hash Join
+         Hash Cond: (f.id6 = d6.id)
+         ->  Hash Join
+               Hash Cond: (f.id5 = d5.id)
+               ->  Hash Join
+                     Hash Cond: (f.id4 = d4.id)
+                     ->  Hash Join
+                           Hash Cond: (f.id3 = d3.id)
+                           ->  Hash Join
+                                 Hash Cond: (f.id2 = d2.id)
+                                 ->  Hash Join
+                                       Hash Cond: (f.id1 = d1.id)
+                                       ->  Seq Scan on bloom_star_fact f
+                                             Bloom Filter 1: keys=(id1)
+                                             Bloom Filter 2: keys=(id2)
+                                             Bloom Filter 3: keys=(id3)
+                                       ->  Hash
+                                             Bloom Filter 1
+                                             ->  Seq Scan on bloom_star_dim_1 d1
+                                                   Filter: (r < '0.3'::double precision)
+                                 ->  Hash
+                                       Bloom Filter 2
+                                       ->  Seq Scan on bloom_star_dim_2 d2
+                                             Filter: (r < '0.35'::double precision)
+                           ->  Hash
+                                 Bloom Filter 3
+                                 ->  Seq Scan on bloom_star_dim_3 d3
+                                       Filter: (r < '0.4'::double precision)
+                     ->  Hash
+                           ->  Seq Scan on bloom_star_dim_4 d4
+                                 Filter: (r < '0.45'::double precision)
+               ->  Hash
+                     ->  Seq Scan on bloom_star_dim_5 d5
+                           Filter: (r < '0.5'::double precision)
+         ->  Hash
+               ->  Seq Scan on bloom_star_dim_6 d6
+                     Filter: (r < '0.55'::double precision)
+   ->  Hash
+         ->  Seq Scan on bloom_star_dim_7 d7
+               Filter: (r < '0.6'::double precision)
+(42 rows)
+
+SET bloom_filter_pushdown_max = 10;
+-- all joins selective for a filter
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d1.r < 0.3 AND d2.r < 0.35 AND d3.r < 0.4 AND d4.r < 0.45 AND d5.r < 0.5 AND d6.r < 0.55 AND d7.r < 0.6;
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (f.id7 = d7.id)
+   ->  Hash Join
+         Hash Cond: (f.id6 = d6.id)
+         ->  Hash Join
+               Hash Cond: (f.id5 = d5.id)
+               ->  Hash Join
+                     Hash Cond: (f.id4 = d4.id)
+                     ->  Hash Join
+                           Hash Cond: (f.id3 = d3.id)
+                           ->  Hash Join
+                                 Hash Cond: (f.id2 = d2.id)
+                                 ->  Hash Join
+                                       Hash Cond: (f.id1 = d1.id)
+                                       ->  Seq Scan on bloom_star_fact f
+                                             Bloom Filter 1: keys=(id1)
+                                             Bloom Filter 2: keys=(id2)
+                                             Bloom Filter 3: keys=(id3)
+                                             Bloom Filter 4: keys=(id4)
+                                             Bloom Filter 5: keys=(id5)
+                                             Bloom Filter 6: keys=(id6)
+                                             Bloom Filter 7: keys=(id7)
+                                       ->  Hash
+                                             Bloom Filter 1
+                                             ->  Seq Scan on bloom_star_dim_1 d1
+                                                   Filter: (r < '0.3'::double precision)
+                                 ->  Hash
+                                       Bloom Filter 2
+                                       ->  Seq Scan on bloom_star_dim_2 d2
+                                             Filter: (r < '0.35'::double precision)
+                           ->  Hash
+                                 Bloom Filter 3
+                                 ->  Seq Scan on bloom_star_dim_3 d3
+                                       Filter: (r < '0.4'::double precision)
+                     ->  Hash
+                           Bloom Filter 4
+                           ->  Seq Scan on bloom_star_dim_4 d4
+                                 Filter: (r < '0.45'::double precision)
+               ->  Hash
+                     Bloom Filter 5
+                     ->  Seq Scan on bloom_star_dim_5 d5
+                           Filter: (r < '0.5'::double precision)
+         ->  Hash
+               Bloom Filter 6
+               ->  Seq Scan on bloom_star_dim_6 d6
+                     Filter: (r < '0.55'::double precision)
+   ->  Hash
+         Bloom Filter 7
+         ->  Seq Scan on bloom_star_dim_7 d7
+               Filter: (r < '0.6'::double precision)
+(50 rows)
+
+RESET bloom_filter_pushdown_max;
+RESET join_collapse_limit;
+DROP TABLE bloom_star_dim_1;
+DROP TABLE bloom_star_dim_2;
+DROP TABLE bloom_star_dim_3;
+DROP TABLE bloom_star_dim_4;
+DROP TABLE bloom_star_dim_5;
+DROP TABLE bloom_star_dim_6;
+DROP TABLE bloom_star_dim_7;
+DROP TABLE bloom_star_fact;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fa0a6c47fb..0de7bdc3c56 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -146,3 +146,6 @@ test: fast_default
 # run tablespace test at the end because it drops the tablespace created during
 # setup that other tests may use.
 test: tablespace
+
+# tests for hashjoin bloom filter pushdown
+test: hashjoin_bloom hashjoin_bloom_star hashjoin_bloom_snowflake
diff --git a/src/test/regress/sql/hashjoin_bloom.sql b/src/test/regress/sql/hashjoin_bloom.sql
new file mode 100644
index 00000000000..f0a2a9cc496
--- /dev/null
+++ b/src/test/regress/sql/hashjoin_bloom.sql
@@ -0,0 +1,40 @@
+-- tests to validate bloom filter pushdown
+-- no parallel query support for now
+SET max_parallel_workers_per_gather = 0;
+
+-- a couple very simple join queries
+CREATE TABLE bloom_simple_dim (id int, r real);
+CREATE TABLE bloom_simple_fact (id int, padding text);
+
+INSERT INTO bloom_simple_dim SELECT i, random() FROM generate_series(1,1000) s(i);
+
+INSERT INTO bloom_simple_fact
+SELECT
+    1 + mod(i, 1000),
+    md5(i::text)
+FROM generate_series(1,100000) s(i);
+
+VACUUM ANALYZE;
+
+-- simple query, no join is selective enough for a filter
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_simple_fact f
+JOIN bloom_simple_dim d ON (f.id = d.id);
+
+-- join is 75% selective (not enough for a filter to be created)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_simple_fact f
+JOIN bloom_simple_dim d ON (f.id = d.id)
+WHERE d.r < 0.75;
+
+-- join is 50% selective (enough for a filter to be pushed down)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_simple_fact f
+JOIN bloom_simple_dim d ON (f.id = d.id)
+WHERE d.r < 0.5;
+
+DROP TABLE bloom_simple_dim;
+DROP TABLE bloom_simple_fact;
diff --git a/src/test/regress/sql/hashjoin_bloom_snowflake.sql b/src/test/regress/sql/hashjoin_bloom_snowflake.sql
new file mode 100644
index 00000000000..e5dcb2dea05
--- /dev/null
+++ b/src/test/regress/sql/hashjoin_bloom_snowflake.sql
@@ -0,0 +1,139 @@
+-- tests to validate bloom filter pushdown
+-- no parallel query support for now
+SET max_parallel_workers_per_gather = 0;
+
+-- simple snowflake queries (fact + up to 7 dimensions)
+CREATE TABLE bloom_snowflake_dim_1_1 (id int, r real);
+CREATE TABLE bloom_snowflake_dim_1_2 (id int, r real);
+CREATE TABLE bloom_snowflake_dim_1 (id int, id11 int, id12 int, r real);
+CREATE TABLE bloom_snowflake_dim_2_1 (id int, r real);
+CREATE TABLE bloom_snowflake_dim_2_2 (id int, r real);
+CREATE TABLE bloom_snowflake_dim_2 (id int, id21 int, id22 int, r real);
+
+CREATE TABLE bloom_snowflake_fact (id1 int, id2 int, padding text);
+
+INSERT INTO bloom_snowflake_dim_1_1 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_1_2 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_1 SELECT i, 1 + mod(i, 10000), 1 + mod(i, 10000), random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_2_1 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_2_2 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_snowflake_dim_2 SELECT i, 1 + mod(i, 10000), 1 + mod(i, 10000), random() FROM generate_series(1,10000) s(i);
+
+INSERT INTO bloom_snowflake_fact
+SELECT
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    md5(i::text)
+FROM generate_series(1,1000000) s(i);
+
+VACUUM ANALYZE;
+
+-- simple query, no join is selective enough for a filter
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id);
+
+-- join is 75% selective (not enough for a filter to be created)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d1.r < 0.75;
+
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d11.r < 0.75;
+
+-- join is 50% selective (good enough for pushdown)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d1.r < 0.5;
+
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d11.r < 0.5;
+
+-- increase the accepted build size (includes the fact)
+SET bloom_filter_pushdown_max_build_relids = 4;
+
+-- join is 50% selective (good enough for pushdown)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d1.r < 0.5;
+
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d11.r < 0.5;
+
+-- needed to stabilize the join order
+SET join_collapse_limit = 1;
+
+-- two joins with 75% selectivity (not enough for pushdown individually,
+-- but good enough when combined)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_snowflake_fact f
+JOIN bloom_snowflake_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_snowflake_dim_1_1 d11 ON (d1.id11 = d11.id)
+JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id)
+JOIN bloom_snowflake_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id)
+JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id)
+WHERE d11.r < 0.75 AND d12.r < 0.75;
+
+RESET join_collapse_limit;
+RESET bloom_filter_pushdown_max_build_relids;
+
+DROP TABLE bloom_snowflake_dim_1_1;
+DROP TABLE bloom_snowflake_dim_1_2;
+DROP TABLE bloom_snowflake_dim_1;
+DROP TABLE bloom_snowflake_dim_2_1;
+DROP TABLE bloom_snowflake_dim_2_2;
+DROP TABLE bloom_snowflake_dim_2;
+DROP TABLE bloom_snowflake_fact;
diff --git a/src/test/regress/sql/hashjoin_bloom_star.sql b/src/test/regress/sql/hashjoin_bloom_star.sql
new file mode 100644
index 00000000000..7ee3069ccd6
--- /dev/null
+++ b/src/test/regress/sql/hashjoin_bloom_star.sql
@@ -0,0 +1,147 @@
+-- tests to validate bloom filter pushdown
+-- no parallel query support for now
+SET max_parallel_workers_per_gather = 0;
+
+-- needed, because the filter pushdown eliminates one of the inputs for
+-- choosing join order (reduction of result size)
+SET join_collapse_limit = 1;
+
+-- simple starjoin queries (fact + up to 7 dimensions)
+CREATE TABLE bloom_star_dim_1 (id int, r real);
+CREATE TABLE bloom_star_dim_2 (id int, r real);
+CREATE TABLE bloom_star_dim_3 (id int, r real);
+CREATE TABLE bloom_star_dim_4 (id int, r real);
+CREATE TABLE bloom_star_dim_5 (id int, r real);
+CREATE TABLE bloom_star_dim_6 (id int, r real);
+CREATE TABLE bloom_star_dim_7 (id int, r real);
+
+CREATE TABLE bloom_star_fact (id1 int, id2 int, id3 int, id4 int, id5 int, id6 int, id7 int, padding text);
+
+INSERT INTO bloom_star_dim_1 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_2 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_3 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_4 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_5 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_6 SELECT i, random() FROM generate_series(1,10000) s(i);
+INSERT INTO bloom_star_dim_7 SELECT i, random() FROM generate_series(1,10000) s(i);
+
+INSERT INTO bloom_star_fact
+SELECT
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    1 + mod(i, 10000),
+    md5(i::text)
+FROM generate_series(1,1000000) s(i);
+
+VACUUM ANALYZE;
+
+-- simple query, no join is selective enough for a filter
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id);
+
+-- last join is 75% selective (not enough for a filter to be created)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d7.r < 0.75;
+
+-- last join is 50% selective (good enough for a filter push down)
+-- the join will be executed done last
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d7.r < 0.5;
+
+-- first join is 50% selective (good enough for a filter push down)
+-- the join will be executed done last
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d1.r < 0.5;
+
+-- two joins 50% selective
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d1.r < 0.4 AND d7.r < 0.5;
+
+-- all joins selective for a filter (more than how many we allow)
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d1.r < 0.3 AND d2.r < 0.35 AND d3.r < 0.4 AND d4.r < 0.45 AND d5.r < 0.5 AND d6.r < 0.55 AND d7.r < 0.6;
+
+
+SET bloom_filter_pushdown_max = 10;
+
+-- all joins selective for a filter
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM bloom_star_fact f
+JOIN bloom_star_dim_1 d1 ON (f.id1 = d1.id)
+JOIN bloom_star_dim_2 d2 ON (f.id2 = d2.id)
+JOIN bloom_star_dim_3 d3 ON (f.id3 = d3.id)
+JOIN bloom_star_dim_4 d4 ON (f.id4 = d4.id)
+JOIN bloom_star_dim_5 d5 ON (f.id5 = d5.id)
+JOIN bloom_star_dim_6 d6 ON (f.id6 = d6.id)
+JOIN bloom_star_dim_7 d7 ON (f.id7 = d7.id)
+WHERE d1.r < 0.3 AND d2.r < 0.35 AND d3.r < 0.4 AND d4.r < 0.45 AND d5.r < 0.5 AND d6.r < 0.55 AND d7.r < 0.6;
+
+RESET bloom_filter_pushdown_max;
+RESET join_collapse_limit;
+
+DROP TABLE bloom_star_dim_1;
+DROP TABLE bloom_star_dim_2;
+DROP TABLE bloom_star_dim_3;
+DROP TABLE bloom_star_dim_4;
+DROP TABLE bloom_star_dim_5;
+DROP TABLE bloom_star_dim_6;
+DROP TABLE bloom_star_dim_7;
+DROP TABLE bloom_star_fact;
-- 
2.55.0

