From 86591de1659a9d9b686a19797354fa75f768e1e0 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Mon, 27 Jul 2026 17:42:09 +0200
Subject: [PATCH v8 18/20] Add a multi-column FK join to the simple test

Tests 2-way join join matching a multi-column FK constraint.
---
 src/test/regress/expected/hashjoin_bloom.out | 66 ++++++++++++++++++++
 src/test/regress/sql/hashjoin_bloom.sql      | 38 +++++++++++
 2 files changed, 104 insertions(+)

diff --git a/src/test/regress/expected/hashjoin_bloom.out b/src/test/regress/expected/hashjoin_bloom.out
index fd6d895f4cc..793aff233a3 100644
--- a/src/test/regress/expected/hashjoin_bloom.out
+++ b/src/test/regress/expected/hashjoin_bloom.out
@@ -61,3 +61,69 @@ WHERE d.r < 0.5;
 
 DROP TABLE bloom_simple_dim;
 DROP TABLE bloom_simple_fact;
+-- a schema with multi-column foreign key, to test estimates
+CREATE TABLE bloom_multi_dim (id1 int, id2 int, r real, primary key (id1, id2));
+CREATE TABLE bloom_multi_fact (id1 int, id2 int, padding text, foreign key (id1, id2) references bloom_multi_dim(id1,id2));
+INSERT INTO bloom_multi_dim SELECT i, i, (i / 1000.0) FROM generate_series(1,1000) s(i);
+INSERT INTO bloom_multi_fact
+SELECT
+    1 + mod(i, 1000),
+    1 + mod(i, 1000),
+    md5(i::text)
+FROM generate_series(1,30000) s(i);
+VACUUM ANALYZE;
+-- simple query, no join is selective enough for a filter
+EXPLAIN (ANALYZE, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
+SELECT *
+FROM bloom_multi_fact f
+JOIN bloom_multi_dim d ON (f.id1 = d.id1 AND f.id2 = d.id2);
+                                                   QUERY PLAN                                                   
+----------------------------------------------------------------------------------------------------------------
+ Hash Join  (cost=31.00..769.50 rows=30000 width=53) (actual rows=30000.00 loops=1)
+   Hash Cond: ((f.id1 = d.id1) AND (f.id2 = d.id2))
+   ->  Seq Scan on bloom_multi_fact f  (cost=0.00..581.00 rows=30000 width=41) (actual rows=30000.00 loops=1)
+   ->  Hash  (cost=16.00..16.00 rows=1000 width=12) (actual rows=1000.00 loops=1)
+         Buckets: 1024  Batches: 1  Memory Usage: 51kB
+         ->  Seq Scan on bloom_multi_dim d  (cost=0.00..16.00 rows=1000 width=12) (actual rows=1000.00 loops=1)
+(6 rows)
+
+-- join is 75% selective (not enough for a filter to be created)
+EXPLAIN (ANALYZE, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
+SELECT *
+FROM bloom_multi_fact f
+JOIN bloom_multi_dim d ON (f.id1 = d.id1 AND f.id2 = d.id2)
+WHERE d.r < 0.75;
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
+ Hash Join  (cost=29.73..768.24 rows=22470 width=53) (actual rows=22470.00 loops=1)
+   Hash Cond: ((f.id1 = d.id1) AND (f.id2 = d.id2))
+   ->  Seq Scan on bloom_multi_fact f  (cost=0.00..581.00 rows=30000 width=41) (actual rows=30000.00 loops=1)
+   ->  Hash  (cost=18.50..18.50 rows=749 width=12) (actual rows=749.00 loops=1)
+         Buckets: 1024  Batches: 1  Memory Usage: 41kB
+         ->  Seq Scan on bloom_multi_dim d  (cost=0.00..18.50 rows=749 width=12) (actual rows=749.00 loops=1)
+               Filter: (r < '0.75'::double precision)
+               Rows Removed by Filter: 251
+(8 rows)
+
+-- join is 50% selective (enough for a filter to be pushed down)
+EXPLAIN (ANALYZE, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
+SELECT *
+FROM bloom_multi_fact f
+JOIN bloom_multi_dim d ON (f.id1 = d.id1 AND f.id2 = d.id2)
+WHERE d.r < 0.5;
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
+ Hash Join  (cost=25.98..685.58 rows=14970 width=53) (actual rows=14970.00 loops=1)
+   Hash Cond: ((f.id1 = d.id1) AND (f.id2 = d.id2))
+   ->  Seq Scan on bloom_multi_fact f  (cost=0.00..581.00 rows=14970 width=41) (actual rows=14970.00 loops=1)
+         Bloom Filter 1: keys=(id1, id2) expected=49.9% checked=29999 rejected=15030 (50.1%)
+   ->  Hash  (cost=18.50..18.50 rows=499 width=12) (actual rows=499.00 loops=1)
+         Buckets: 1024  Batches: 1  Memory Usage: 30kB
+         Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=29999 rejected=15030
+         ->  Seq Scan on bloom_multi_dim d  (cost=0.00..18.50 rows=499 width=12) (actual rows=499.00 loops=1)
+               Filter: (r < '0.5'::double precision)
+               Rows Removed by Filter: 501
+(10 rows)
+
+DROP TABLE bloom_multi_fact;
+DROP TABLE bloom_multi_dim;
diff --git a/src/test/regress/sql/hashjoin_bloom.sql b/src/test/regress/sql/hashjoin_bloom.sql
index f0a2a9cc496..a91e88dcf8a 100644
--- a/src/test/regress/sql/hashjoin_bloom.sql
+++ b/src/test/regress/sql/hashjoin_bloom.sql
@@ -38,3 +38,41 @@ WHERE d.r < 0.5;
 
 DROP TABLE bloom_simple_dim;
 DROP TABLE bloom_simple_fact;
+
+-- a schema with multi-column foreign key, to test estimates
+CREATE TABLE bloom_multi_dim (id1 int, id2 int, r real, primary key (id1, id2));
+CREATE TABLE bloom_multi_fact (id1 int, id2 int, padding text, foreign key (id1, id2) references bloom_multi_dim(id1,id2));
+
+INSERT INTO bloom_multi_dim SELECT i, i, (i / 1000.0) FROM generate_series(1,1000) s(i);
+
+INSERT INTO bloom_multi_fact
+SELECT
+    1 + mod(i, 1000),
+    1 + mod(i, 1000),
+    md5(i::text)
+FROM generate_series(1,30000) s(i);
+
+VACUUM ANALYZE;
+
+-- simple query, no join is selective enough for a filter
+EXPLAIN (ANALYZE, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
+SELECT *
+FROM bloom_multi_fact f
+JOIN bloom_multi_dim d ON (f.id1 = d.id1 AND f.id2 = d.id2);
+
+-- join is 75% selective (not enough for a filter to be created)
+EXPLAIN (ANALYZE, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
+SELECT *
+FROM bloom_multi_fact f
+JOIN bloom_multi_dim d ON (f.id1 = d.id1 AND f.id2 = d.id2)
+WHERE d.r < 0.75;
+
+-- join is 50% selective (enough for a filter to be pushed down)
+EXPLAIN (ANALYZE, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
+SELECT *
+FROM bloom_multi_fact f
+JOIN bloom_multi_dim d ON (f.id1 = d.id1 AND f.id2 = d.id2)
+WHERE d.r < 0.5;
+
+DROP TABLE bloom_multi_fact;
+DROP TABLE bloom_multi_dim;
-- 
2.55.0

