public inbox for [email protected]
help / color / mirror / Atom feedFrom: Matheus Alcantara <[email protected]>
To: Tomas Vondra <[email protected]>
To: Andrew Dunstan <[email protected]>
To: PostgreSQL Hackers <[email protected]>
Subject: Re: hashjoins vs. Bloom filters (yet again)
Date: Thu, 02 Jul 2026 17:31:18 -0300
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
On Wed Jul 1, 2026 at 10:25 AM -03, Tomas Vondra wrote:
> (d) At planning / execution this works similarly to v1, except that all
> the decisions were already done. The code in createplan.c and executor
> is more for book-keeping and allowing lookup of filters.
>
I'm wondering if the adaptive probing can mess the execution of the
choose plan. Let's say that a plan was chosen with bloom filters to
pushdown because it would reduce e.g 50% of the rows, what if at
runtime the bloom filter is proved to not be effective and it get
disabled making the scan node to produce 100% of the rows to the node
above that is not expecting? Do we end up with the same issue "expected
x actual rows"?
I think that if we keep using the unnefective bloom filter the scan node
will still produce more rows than expected, but perhaps this is easier
to understand?
> I haven't incorporated the two patches posted by Andrew:
>
> 1) making it work with CustomScans
>
> 2) supporting per-key filters
>
> 3) allow eager creation of filters (disable delayed Hash build)
>
> I agree those seem like a worthwhile improvements, and the patches
> seemed to be OK too, but I was focusing on reworking the planning. Based
> on some off-list discussion, Andrew (or one of his colleagues) should be
> able to adjust those for this v3 patch.
>
I'm attaching a new v4 patchset incorporating Andrew patches with test
cases. 0001 and 0002 are your v3 untouched, 0003 is some tests added to
exercice the CustomScan path and 0004 is the Andrew changes with a few
changes required from v3 version:
Unlike the v1 PoC that pushed filters down in create_hashjoin_plan
(where it could simply walk the finished plan tree and accept any scan
node), the filters are now decided during bottom-up path construction,
so a scan only receives a filter if a filter-bearing path was generated
for its base relation. So the main change is teaching path generation
about the custom scan.
> In fact, I'm thinking it might be better to abstract the "filter" and
> stop thinking just about Bloom filters. There probably are other kinds
> of interesting filters, so maybe we should not assume all filters are
> Bloom filters. Those filters probably won't be that different, it's more
> about not putting "Bloom" in naming etc.
>
I was also thinking about this when reading v1. I was checking other
databases and I realize that Trino [1] and Duckdb (I didn't find any
documentation from Duckdb but you can see this information on explain
analyse output) have "Dynamic Filtering" and IIUC is a similar
optimization that this patch is about.
I'm wondering if we could name as "Dynamic Filtering" or "Runtime filter
pushdown" or something else instead of Bloom Filters to make it more
generic.
On explain(analyze) output we can show as as "Runtime filter" and put
the columns (perhaps the values?) used on the filter.
I think that this can abstract and let the implementation decide if it
will use a bloom filter or not. It's a rough idea, but what do you
think?
I'm still thinking about your other points and about the XXX comments.
I'll share more soon.
[1] https://trino.io/docs/current/admin/dynamic-filtering.html
--
Matheus Alcantara
EDB: https://www.enterprisedb.com
From 1646d007f42fd4998e32a0a6b16cc6f7b4918a4d Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 20 Jun 2026 20:44:00 +0200
Subject: [PATCH v4 1/4] PoC: hashjoin bloom filter pushdown
When construction hashjoin plans, try to pushdown a Bloom filter built
on the hashtable to a scan node in the outer side of the join. This has
multiple significant benefits:
a) Probing a bloom filter is cheaper than probing a hash table, so if
a tuple gets eliminated using the bloom filter, we save cycles.
b) The Bloom filter is more compact, and so more cache efficient. The
hash table may not even split into memory, and the hashjoin has to
spill data to files. The Bloom filter may still fit into memory,
and eliminate many tuples on the outer side (which reduces the
amount of data spilled to disk).
c) The Bloom filter is pushed to a scan node, which may be multiple
steps before the join. This increases the benefit, because the
eliminated tuples don't need to pass through any of the nodes in
between. This futehr amplifies the difference between probing a
filter and probing the hashtable.
d) If a table joins with multiple other tables (e.g. in a starjoin), the
scan node may receive multiple Bloom filters. The selectivity of the
filters multiply, once again amplifying the benefits. If a scan gets
two filters, each discarding 90% of tuples, the scan will discard 99%
of tuples, i.e. 2 orders of magnitude fewer tuples.
This patch performs Bloom filter pushdown when constructing the plan,
after path selection. That means the filters are not considered when
estimating and costing the paths, and we only have a chance to do the
pushdown if we happen to a pick a plan with a hashjoin. If the pushdown
is what makes the plan fast (faster than plans without hashjoins), we
may not pick it. With our bottom-up planning it's hard to do better.
The decision which Bloom filters to build (and which scan nodes should
evaluate them) happens in create_hashjoin_plan. This registers the
filters in both the hashjoin and the recipient scan node, etc.
Then at execution time, the Hash node builds the filter with the
hashtable, and the scan node probes the Bloom filter similarly to
evaluating the regular quals.
How effective this is depends on how many tuples the filter eliminates.
A highly selective filter (e.g. discarding >90% tuples) is going to be a
win no matter what. But even a "poor" filter (e.g. discarding only 10%
tuples) may still be a win, if the hashjoin has to perform batching, and
thus spill data to disk.
It's hard to know in advance which filters are selective. The patch has
a simple adaptive logic, that disables filters that are not selective
enough (too many probes find a match), and the enables the filter when
the filter gets more selective.
There's a number of open questions to solve:
- Pushdown after path construction means we can't consider Bloom
filters when costing the paths, and the cost are as if no tuples were
eliminated by the scan node. Solving this with the bottom-up planning
is unlikely, or would have disadvantages (e.g. would increase the
number of paths we have to condsider).
- The EXPLAIN ANALYZE output can be somewhat confusing/misleading. The
path estimates don't consider how many rows may be eliminated by the
filter. This may lead to huge differences between estimated and actual
"rows" in the EXPLAIN output, even with perfect estimates. The EXPLAIN
now includes information about Bloom filters (number of probes and
number of discarded rows), but it's still hard to interpret.
- We have little control over the Bloom filter parameters. The library
picks most of the attributes on our behalf. Works reasonably well, but
we may need to know e.g. false positive rate (if it gets too high, the
filter becomes useless, and we should stop using it).
- We only push filters to scan nodes, and only through some other nodes
(e.g. through joins, sort, ...). We could expand this to also pushdown
through aggregations, etc.
- The patch does not support parallel queries. This can be addressed
later, it's certainly doable.
- Similarly, there's no support for partitioned tables (fixing this
should be simpler than supporting parallel queries).
- It might be interesting to allow the scan nodes to use the Bloom
filters in other ways. E.g. it might push the filter to storage, or
perhaps to remote node (with a ForeignScan), and let it do smart things
with it. The storage might prefilter data, foreign server could filter
data on the remote end. That'd require using some well defined and
portable library for the filter.
- The cost model determining which filters are effective is a bit crude
and based on empirical observations. For example the thresholds used
in the adaptive logic are somewhat arbitrary and need more thought.
- We could push filters into other nodes, not just scans. Might be
useful for more complex joins.
---
.../pg_plan_advice/expected/join_order.out | 52 +-
.../pg_plan_advice/expected/join_strategy.out | 20 +-
.../pg_plan_advice/expected/partitionwise.out | 40 +-
contrib/pg_plan_advice/expected/semijoin.out | 24 +-
.../expected/pg_stash_advice.out | 54 ++-
.../expected/level_tracking.out | 24 +-
.../postgres_fdw/expected/postgres_fdw.out | 8 +-
src/backend/commands/explain.c | 189 ++++++++
src/backend/executor/execUtils.c | 2 +
src/backend/executor/nodeBitmapHeapscan.c | 3 +
src/backend/executor/nodeHash.c | 60 +++
src/backend/executor/nodeHashjoin.c | 455 ++++++++++++++++++
src/backend/executor/nodeIndexonlyscan.c | 3 +
src/backend/executor/nodeIndexscan.c | 3 +
src/backend/executor/nodeSamplescan.c | 3 +
src/backend/executor/nodeSeqscan.c | 3 +
src/backend/executor/nodeTidrangescan.c | 3 +
src/backend/executor/nodeTidscan.c | 3 +
src/backend/lib/bloomfilter.c | 19 +
src/backend/optimizer/path/costsize.c | 1 +
src/backend/optimizer/plan/createplan.c | 299 ++++++++++++
src/backend/optimizer/plan/planner.c | 1 +
src/backend/optimizer/plan/setrefs.c | 63 +++
src/backend/utils/misc/guc_parameters.dat | 7 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/executor/execScan.h | 22 +-
src/include/executor/executor.h | 12 +
src/include/executor/nodeHashjoin.h | 9 +
src/include/lib/bloomfilter.h | 2 +
src/include/nodes/execnodes.h | 91 ++++
src/include/nodes/pathnodes.h | 3 +
src/include/nodes/plannodes.h | 51 ++
src/include/optimizer/cost.h | 1 +
src/test/regress/expected/aggregates.out | 8 +-
src/test/regress/expected/eager_aggregate.out | 166 ++++++-
src/test/regress/expected/join.out | 176 +++++--
src/test/regress/expected/join_hash.out | 28 +-
src/test/regress/expected/merge.out | 20 +-
src/test/regress/expected/misc_functions.out | 4 +-
.../regress/expected/partition_aggregate.out | 34 +-
src/test/regress/expected/partition_join.out | 452 ++++++++++++++---
src/test/regress/expected/predicate.out | 8 +-
src/test/regress/expected/privileges.out | 4 +-
src/test/regress/expected/returning.out | 10 +-
src/test/regress/expected/rowsecurity.out | 2 +
src/test/regress/expected/select_views.out | 2 +
src/test/regress/expected/stats_ext.out | 4 +-
src/test/regress/expected/subselect.out | 64 ++-
src/test/regress/expected/sysviews.out | 3 +-
src/test/regress/expected/updatable_views.out | 12 +-
src/test/regress/expected/window.out | 4 +-
src/test/regress/expected/with.out | 16 +-
src/test/regress/sql/rowsecurity.sql | 3 +
src/test/regress/sql/select_views.sql | 3 +
54 files changed, 2313 insertions(+), 241 deletions(-)
diff --git a/contrib/pg_plan_advice/expected/join_order.out b/contrib/pg_plan_advice/expected/join_order.out
index a5a9728e3fd..0e5f93a046f 100644
--- a/contrib/pg_plan_advice/expected/join_order.out
+++ b/contrib/pg_plan_advice/expected/join_order.out
@@ -27,17 +27,21 @@ SELECT * FROM jo_fact f
LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
Generated Plan Advice:
@@ -45,7 +49,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(d2 d1)
SEQ_SCAN(f d2 d1)
NO_GATHER(f d1 d2)
-(16 rows)
+(20 rows)
-- Force a few different join orders. Some of these are very inefficient,
-- but the planner considers them all viable.
@@ -56,17 +60,21 @@ SELECT * FROM jo_fact f
LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim1_id)
+ Bloom Filter 2: keys=(dim2_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on jo_dim2 d2
Filter: (val2 = 1)
Supplied Plan Advice:
@@ -76,7 +84,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(d1 d2)
SEQ_SCAN(f d1 d2)
NO_GATHER(f d1 d2)
-(18 rows)
+(22 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(f d2 d1)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -84,17 +92,21 @@ SELECT * FROM jo_fact f
LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
Supplied Plan Advice:
@@ -104,7 +116,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(d2 d1)
SEQ_SCAN(f d2 d1)
NO_GATHER(f d1 d2)
-(18 rows)
+(22 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(d1 f d2)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -120,7 +132,9 @@ SELECT * FROM jo_fact f
Hash Cond: (d1.id = f.dim1_id)
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_fact f
-> Hash
-> Seq Scan on jo_dim2 d2
@@ -132,7 +146,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(f d2)
SEQ_SCAN(d1 f d2)
NO_GATHER(f d1 d2)
-(18 rows)
+(20 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(f (d1 d2))';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -145,7 +159,9 @@ SELECT * FROM jo_fact f
Hash Join
Hash Cond: ((f.dim1_id = d1.id) AND (f.dim2_id = d2.id))
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim1_id, dim2_id)
-> Hash
+ Bloom Filter 1
-> Nested Loop
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
@@ -160,7 +176,7 @@ SELECT * FROM jo_fact f
HASH_JOIN((d1 d2))
SEQ_SCAN(f d1 d2)
NO_GATHER(f d1 d2)
-(18 rows)
+(20 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(f {d1 d2})';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -173,7 +189,9 @@ SELECT * FROM jo_fact f
Hash Join
Hash Cond: ((f.dim1_id = d1.id) AND (f.dim2_id = d2.id))
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim1_id, dim2_id)
-> Hash
+ Bloom Filter 1
-> Nested Loop
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
@@ -188,7 +206,7 @@ SELECT * FROM jo_fact f
HASH_JOIN((d1 d2))
SEQ_SCAN(f d1 d2)
NO_GATHER(f d1 d2)
-(18 rows)
+(20 rows)
COMMIT;
-- Force a join order by mentioning just a prefix of the join list.
@@ -199,17 +217,21 @@ SELECT * FROM jo_fact f
LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------
Hash Join
Hash Cond: (d2.id = f.dim2_id)
-> Seq Scan on jo_dim2 d2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
Supplied Plan Advice:
@@ -219,7 +241,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(d1 (f d1))
SEQ_SCAN(d2 f d1)
NO_GATHER(f d1 d2)
-(18 rows)
+(22 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(d2 d1)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
diff --git a/contrib/pg_plan_advice/expected/join_strategy.out b/contrib/pg_plan_advice/expected/join_strategy.out
index 0f9db692190..ce105856fda 100644
--- a/contrib/pg_plan_advice/expected/join_strategy.out
+++ b/contrib/pg_plan_advice/expected/join_strategy.out
@@ -15,19 +15,21 @@ VACUUM ANALYZE join_fact;
-- We expect a hash join by default.
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+---------------------------------------
Hash Join
Hash Cond: (f.dim_id = d.id)
-> Seq Scan on join_fact f
+ Bloom Filter 1: keys=(dim_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on join_dim d
Generated Plan Advice:
JOIN_ORDER(f d)
HASH_JOIN(d)
SEQ_SCAN(f d)
NO_GATHER(f d)
-(10 rows)
+(12 rows)
-- Try forcing each join method in turn with join_dim as the inner table.
-- All of these should work except for MERGE_JOIN_MATERIALIZE; that will
@@ -37,12 +39,14 @@ BEGIN;
SET LOCAL pg_plan_advice.advice = 'HASH_JOIN(d)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+---------------------------------------
Hash Join
Hash Cond: (f.dim_id = d.id)
-> Seq Scan on join_fact f
+ Bloom Filter 1: keys=(dim_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on join_dim d
Supplied Plan Advice:
HASH_JOIN(d) /* matched */
@@ -51,7 +55,7 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE)
HASH_JOIN(d)
SEQ_SCAN(f d)
NO_GATHER(f d)
-(12 rows)
+(14 rows)
SET LOCAL pg_plan_advice.advice = 'MERGE_JOIN_MATERIALIZE(d)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -162,7 +166,9 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE)
Hash Join
Hash Cond: (d.id = f.dim_id)
-> Seq Scan on join_dim d
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on join_fact f
Supplied Plan Advice:
HASH_JOIN(f) /* matched */
@@ -171,7 +177,7 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE)
HASH_JOIN(f)
SEQ_SCAN(d f)
NO_GATHER(f d)
-(12 rows)
+(14 rows)
SET LOCAL pg_plan_advice.advice = 'MERGE_JOIN_MATERIALIZE(f)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
diff --git a/contrib/pg_plan_advice/expected/partitionwise.out b/contrib/pg_plan_advice/expected/partitionwise.out
index 2b3d0a82443..3b003a927ac 100644
--- a/contrib/pg_plan_advice/expected/partitionwise.out
+++ b/contrib/pg_plan_advice/expected/partitionwise.out
@@ -60,7 +60,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_1.id = pt3_1.id)
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt3a pt3_1
Filter: (val3 = 1)
-> Index Scan using pt1a_pkey on pt1a pt1_1
@@ -71,7 +73,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_2.id = pt3_2.id)
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt3b pt3_2
Filter: (val3 = 1)
-> Index Scan using pt1b_pkey on pt1b pt1_2
@@ -82,7 +86,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_3.id = pt3_3.id)
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt3c pt3_3
Filter: (val3 = 1)
-> Index Scan using pt1c_pkey on pt1c pt1_3
@@ -101,7 +107,7 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2 pt3))
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(47 rows)
+(53 rows)
-- Suppress partitionwise join, or do it just partially.
BEGIN;
@@ -169,21 +175,27 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt1_1.id = pt2_1.id)
-> Seq Scan on pt1a pt1_1
Filter: (val1 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
-> Hash Join
Hash Cond: (pt1_2.id = pt2_2.id)
-> Seq Scan on pt1b pt1_2
Filter: (val1 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
-> Hash Join
Hash Cond: (pt1_3.id = pt2_3.id)
-> Seq Scan on pt1c pt1_3
Filter: (val1 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
-> Hash
@@ -209,7 +221,7 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2) pt3)
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(47 rows)
+(53 rows)
COMMIT;
-- Test conflicting advice.
@@ -227,7 +239,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_1.id = pt3_1.id)
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt3a pt3_1
Filter: (val3 = 1)
-> Index Scan using pt1a_pkey on pt1a pt1_1
@@ -238,7 +252,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_2.id = pt3_2.id)
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt3b pt3_2
Filter: (val3 = 1)
-> Index Scan using pt1b_pkey on pt1b pt1_2
@@ -249,7 +265,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_3.id = pt3_3.id)
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt3c pt3_3
Filter: (val3 = 1)
-> Index Scan using pt1c_pkey on pt1c pt1_3
@@ -271,7 +289,7 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2 pt3))
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(51 rows)
+(57 rows)
COMMIT;
-- Can't force a partitionwise join with a mismatched table.
@@ -321,7 +339,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt3_1.id = pt2_1.id)
-> Seq Scan on pt3a pt3_1
Filter: (val3 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
-> Index Scan using pt1a_pkey on pt1a pt1_1
@@ -332,7 +352,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_2.id = pt3_2.id)
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt3b pt3_2
Filter: (val3 = 1)
-> Index Scan using pt1b_pkey on pt1b pt1_2
@@ -343,7 +365,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_3.id = pt3_3.id)
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt3c pt3_3
Filter: (val3 = 1)
-> Index Scan using pt1c_pkey on pt1c pt1_3
@@ -364,7 +388,7 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2 pt3))
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(49 rows)
+(55 rows)
SET LOCAL pg_plan_advice.advice = 'JOIN_ORDER(pt3/pt3a pt2/pt2a pt1/pt1a)';
EXPLAIN (PLAN_ADVICE, COSTS OFF)
@@ -378,7 +402,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt3_1.id = pt2_1.id)
-> Seq Scan on pt3a pt3_1
Filter: (val3 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
-> Index Scan using pt1a_pkey on pt1a pt1_1
@@ -389,7 +415,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_2.id = pt3_2.id)
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt3b pt3_2
Filter: (val3 = 1)
-> Index Scan using pt1b_pkey on pt1b pt1_2
@@ -400,7 +428,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_3.id = pt3_3.id)
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt3c pt3_3
Filter: (val3 = 1)
-> Index Scan using pt1c_pkey on pt1c pt1_3
@@ -421,6 +451,6 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2 pt3))
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(49 rows)
+(55 rows)
COMMIT;
diff --git a/contrib/pg_plan_advice/expected/semijoin.out b/contrib/pg_plan_advice/expected/semijoin.out
index db6b069ec8e..f60778d0d38 100644
--- a/contrib/pg_plan_advice/expected/semijoin.out
+++ b/contrib/pg_plan_advice/expected/semijoin.out
@@ -75,7 +75,9 @@ SELECT * FROM sj_wide
Hash Semi Join
Hash Cond: ((sj_wide.id = "*VALUES*".column1) AND (sj_wide.val1 = "*VALUES*".column2))
-> Seq Scan on sj_wide
+ Bloom Filter 1: keys=(id, val1)
-> Hash
+ Bloom Filter 1
-> Values Scan on "*VALUES*"
Supplied Plan Advice:
SEMIJOIN_NON_UNIQUE("*VALUES*") /* matched */
@@ -85,7 +87,7 @@ SELECT * FROM sj_wide
SEQ_SCAN(sj_wide)
SEMIJOIN_NON_UNIQUE("*VALUES*")
NO_GATHER(sj_wide "*VALUES*")
-(13 rows)
+(15 rows)
COMMIT;
-- Because this table is narrower than the previous one, a sequential scan
@@ -100,7 +102,9 @@ SELECT * FROM sj_narrow
Hash Semi Join
Hash Cond: ((sj_narrow.id = "*VALUES*".column1) AND (sj_narrow.val1 = "*VALUES*".column2))
-> Seq Scan on sj_narrow
+ Bloom Filter 1: keys=(id, val1)
-> Hash
+ Bloom Filter 1
-> Values Scan on "*VALUES*"
Generated Plan Advice:
JOIN_ORDER(sj_narrow "*VALUES*")
@@ -108,7 +112,7 @@ SELECT * FROM sj_narrow
SEQ_SCAN(sj_narrow)
SEMIJOIN_NON_UNIQUE("*VALUES*")
NO_GATHER(sj_narrow "*VALUES*")
-(11 rows)
+(13 rows)
-- Here, we expect advising a unique semijoin to swith to the same plan that
-- we got with sj_wide, and advising a non-unique semijoin should not change
@@ -123,7 +127,9 @@ SELECT * FROM sj_narrow
Hash Join
Hash Cond: ((sj_narrow.id = "*VALUES*".column1) AND (sj_narrow.val1 = "*VALUES*".column2))
-> Seq Scan on sj_narrow
+ Bloom Filter 1: keys=(id, val1)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: "*VALUES*".column1, "*VALUES*".column2
-> Values Scan on "*VALUES*"
@@ -135,7 +141,7 @@ SELECT * FROM sj_narrow
SEQ_SCAN(sj_narrow)
SEMIJOIN_UNIQUE("*VALUES*")
NO_GATHER(sj_narrow "*VALUES*")
-(15 rows)
+(17 rows)
SET LOCAL pg_plan_advice.advice = 'semijoin_non_unique("*VALUES*")';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -146,7 +152,9 @@ SELECT * FROM sj_narrow
Hash Semi Join
Hash Cond: ((sj_narrow.id = "*VALUES*".column1) AND (sj_narrow.val1 = "*VALUES*".column2))
-> Seq Scan on sj_narrow
+ Bloom Filter 1: keys=(id, val1)
-> Hash
+ Bloom Filter 1
-> Values Scan on "*VALUES*"
Supplied Plan Advice:
SEMIJOIN_NON_UNIQUE("*VALUES*") /* matched */
@@ -156,7 +164,7 @@ SELECT * FROM sj_narrow
SEQ_SCAN(sj_narrow)
SEMIJOIN_NON_UNIQUE("*VALUES*")
NO_GATHER(sj_narrow "*VALUES*")
-(13 rows)
+(15 rows)
COMMIT;
-- In the above example, we made the outer side of the join unique, but here,
@@ -261,7 +269,9 @@ SELECT * FROM generate_series(1,1000) g
Hash Right Semi Join
Hash Cond: (sj_narrow.val1 = g.g)
-> Seq Scan on sj_narrow
+ Bloom Filter 1: keys=(val1)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series g
Supplied Plan Advice:
SEMIJOIN_NON_UNIQUE(sj_narrow) /* matched */
@@ -272,7 +282,7 @@ SELECT * FROM generate_series(1,1000) g
SEQ_SCAN(sj_narrow)
SEMIJOIN_NON_UNIQUE(sj_narrow)
NO_GATHER(g sj_narrow)
-(14 rows)
+(16 rows)
COMMIT;
-- However, mentioning the wrong side of the join should result in an advice
@@ -407,11 +417,13 @@ SELECT 1 FROM generate_series(1, 1000) g WHERE EXISTS
-> Unique
-> Nested Loop
-> Index Only Scan using sj_narrow_pkey on sj_narrow t2
+ Bloom Filter 1: keys=(id)
-> Materialize
-> Nested Loop Left Join
-> Result
-> Seq Scan on sj_narrow
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series g
Generated Plan Advice:
JOIN_ORDER(t2 ("*RESULT*" sj_narrow) g)
@@ -422,5 +434,5 @@ SELECT 1 FROM generate_series(1, 1000) g WHERE EXISTS
INDEX_ONLY_SCAN(t2 public.sj_narrow_pkey)
SEMIJOIN_UNIQUE((t2 sj_narrow "*RESULT*"))
NO_GATHER(g t2 sj_narrow "*RESULT*")
-(20 rows)
+(22 rows)
diff --git a/contrib/pg_stash_advice/expected/pg_stash_advice.out b/contrib/pg_stash_advice/expected/pg_stash_advice.out
index 788da854aa7..d62afaa6651 100644
--- a/contrib/pg_stash_advice/expected/pg_stash_advice.out
+++ b/contrib/pg_stash_advice/expected/pg_stash_advice.out
@@ -57,20 +57,24 @@ EXPLAIN (COSTS OFF)
SELECT * FROM aa_fact f LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on aa_dim1 d1
Filter: (val1 = 1)
-(11 rows)
+(15 rows)
-- Force an index scan on dim1
SELECT pg_set_stashed_advice('regress_stash', :'qid',
@@ -91,15 +95,19 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Index Scan using aa_dim1_pkey on aa_dim1 d1
Filter: (val1 = 1)
Supplied Plan Advice:
INDEX_SCAN(d1 aa_dim1_pkey) /* matched */
-(13 rows)
+(17 rows)
-- Force an alternative join order
SELECT pg_set_stashed_advice('regress_stash', :'qid',
@@ -113,22 +121,26 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim1_id)
+ Bloom Filter 2: keys=(dim2_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim1 d1
Filter: (val1 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
Supplied Plan Advice:
JOIN_ORDER(f d1 d2) /* matched */
-(13 rows)
+(17 rows)
-- Force an alternative join strategy
SELECT pg_set_stashed_advice('regress_stash', :'qid',
@@ -148,7 +160,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Index Scan using aa_dim1_pkey on aa_dim1 d1
@@ -156,7 +170,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
Filter: (val1 = 1)
Supplied Plan Advice:
NESTED_LOOP_PLAIN(d1) /* matched */
-(12 rows)
+(14 rows)
-- Add a useless extra entry to our test stash. Shouldn't change the result
-- from the previous test.
@@ -178,7 +192,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Index Scan using aa_dim1_pkey on aa_dim1 d1
@@ -186,7 +202,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
Filter: (val1 = 1)
Supplied Plan Advice:
NESTED_LOOP_PLAIN(d1) /* matched */
-(12 rows)
+(14 rows)
-- Try an empty stash to be sure it does nothing
SELECT pg_create_advice_stash('regress_empty_stash');
@@ -200,20 +216,24 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on aa_dim1 d1
Filter: (val1 = 1)
-(11 rows)
+(15 rows)
-- Test that we can list each stash individually and all of them together,
-- but not a nonexistent stash.
@@ -263,20 +283,24 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on aa_dim1 d1
Filter: (val1 = 1)
-(11 rows)
+(15 rows)
SELECT * FROM pg_get_advice_stashes() ORDER BY stash_name;
stash_name | num_entries
diff --git a/contrib/pg_stat_statements/expected/level_tracking.out b/contrib/pg_stat_statements/expected/level_tracking.out
index 832d65e97ca..db84cc6af01 100644
--- a/contrib/pg_stat_statements/expected/level_tracking.out
+++ b/contrib/pg_stat_statements/expected/level_tracking.out
@@ -189,9 +189,11 @@ EXPLAIN (COSTS OFF) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF) SELECT 1 UNION SELECT 2;
QUERY PLAN
@@ -315,9 +317,11 @@ EXPLAIN (COSTS OFF) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF) SELECT 1 UNION SELECT 2;
QUERY PLAN
@@ -536,9 +540,11 @@ EXPLAIN (COSTS OFF) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
QUERY PLAN
------------
@@ -664,9 +670,11 @@ EXPLAIN (COSTS OFF) MERGE INTO stats_track_tab USING (SELECT id FROM generate_se
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
QUERY PLAN
------------
@@ -772,9 +780,11 @@ EXPLAIN (COSTS OFF) WITH a AS (SELECT 4) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF) WITH a AS (select 4) SELECT 1 UNION SELECT 2;
QUERY PLAN
@@ -866,9 +876,11 @@ EXPLAIN (COSTS OFF) WITH a AS (SELECT 4) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF) WITH a AS (select 4) SELECT 1 UNION SELECT 2;
QUERY PLAN
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0805c56cb1b..428b7d8c2b3 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -11823,12 +11823,14 @@ INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AN
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.b = t1_3.b))
-> Seq Scan on public.async_p3 t2_3
Output: t2_3.a, t2_3.b, t2_3.c
+ Bloom Filter 1: keys=(t2_3.a, t2_3.b)
-> Hash
Output: t1_3.a, t1_3.b, t1_3.c
+ Bloom Filter 1
-> Seq Scan on public.async_p3 t1_3
Output: t1_3.a, t1_3.b, t1_3.c
Filter: ((t1_3.b % 100) = 0)
-(20 rows)
+(22 rows)
INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0;
SELECT * FROM join_tbl ORDER BY a1;
@@ -11886,12 +11888,14 @@ INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.b = t1_3.b))
-> Seq Scan on public.async_p3 t2_3
Output: t2_3.a, t2_3.b, t2_3.c
+ Bloom Filter 1: keys=(t2_3.a, t2_3.b)
-> Hash
Output: t1_3.a, t1_3.b, t1_3.c
+ Bloom Filter 1
-> Seq Scan on public.async_p3 t1_3
Output: t1_3.a, t1_3.b, t1_3.c
Filter: ((t1_3.b % 100) = 0)
-(20 rows)
+(22 rows)
INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0;
SELECT * FROM join_tbl ORDER BY a1;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a40d03d35f3..8893e36c8aa 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -25,6 +25,7 @@
#include "commands/prepare.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
+#include "lib/bloomfilter.h"
#include "libpq/pqformat.h"
#include "libpq/protocol.h"
#include "nodes/extensible.h"
@@ -92,6 +93,8 @@ static void show_scan_qual(List *qual, const char *qlabel,
static void show_upper_qual(List *qual, const char *qlabel,
PlanState *planstate, List *ancestors,
ExplainState *es);
+static void show_bloom_filter_info(PlanState *planstate, List *ancestors,
+ ExplainState *es);
static void show_sort_keys(SortState *sortstate, List *ancestors,
ExplainState *es);
static void show_incremental_sort_keys(IncrementalSortState *incrsortstate,
@@ -1978,6 +1981,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
show_indexsearches_info(planstate, es);
break;
case T_IndexOnlyScan:
@@ -1995,6 +1999,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (es->analyze)
ExplainPropertyFloat("Heap Fetches", NULL,
planstate->instrument->ntuples2, 0, es);
+ show_bloom_filter_info(planstate, ancestors, es);
show_indexsearches_info(planstate, es);
break;
case T_BitmapIndexScan:
@@ -2012,6 +2017,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
show_scan_io_usage((ScanState *) planstate, es);
break;
@@ -2030,6 +2036,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
if (IsA(plan, CteScan))
show_ctescan_info(castNode(CteScanState, planstate), es);
show_scan_io_usage((ScanState *) planstate, es);
@@ -2132,6 +2139,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
}
break;
case T_TidRangeScan:
@@ -2149,6 +2157,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
show_scan_io_usage((ScanState *) planstate, es);
}
break;
@@ -2578,6 +2587,120 @@ show_upper_qual(List *qual, const char *qlabel,
show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
}
+/*
+ * show_bloom_filter_info
+ * Show info about every bloom filter pushed down to a scan node.
+ *
+ * In TEXT format each filter is rendered on a single line, e.g.
+ *
+ * Bloom Filter N: (a, b) producer=3 checked=99999 rejected=99990
+ *
+ * The checked/rejected fields are omitted outside of ANALYZE). In
+ * structured formats we emit a group per filter with the same fields
+ * broken out as properties.
+ *
+ * Called from the per-recipient cases in ExplainNode; the recipient is
+ * expected to be a scan node in the current PoC, but nothing here
+ * depends on that.
+ */
+static void
+show_bloom_filter_info(PlanState *planstate, List *ancestors,
+ ExplainState *es)
+{
+ Plan *plan = planstate->plan;
+ ListCell *lc1,
+ *lc2;
+ List *deparse_cxt = NIL;
+ bool useprefix = false;
+
+ if (plan->bloom_filters == NIL)
+ return;
+
+ deparse_cxt = set_deparse_context_plan(es->deparse_cxt,
+ plan, ancestors);
+ useprefix = (IsA(plan, SubqueryScan) || es->verbose);
+
+ if (es->format != EXPLAIN_FORMAT_TEXT)
+ ExplainOpenGroup("Bloom Filters", "Bloom Filters", false, es);
+
+ /* print info about all the bloom filters */
+ forboth(lc1, plan->bloom_filters,
+ lc2, planstate->bloom_filters)
+ {
+ BloomFilter *bf = lfirst_node(BloomFilter, lc1);
+ BloomFilterState *bfs = (BloomFilterState *) lfirst(lc2);
+ ListCell *lc;
+ StringInfoData keys;
+ bool first = true;
+
+ initStringInfo(&keys);
+
+ /* deparse the filter expressions */
+ appendStringInfoChar(&keys, '(');
+ foreach(lc, bf->filter_exprs)
+ {
+ char *key;
+ Node *expr = (Node *) lfirst(lc);
+
+ if (!first)
+ appendStringInfoString(&keys, ", ");
+ key = deparse_expression(expr, deparse_cxt,
+ useprefix, false);
+ appendStringInfoString(&keys, key);
+ first = false;
+ }
+ appendStringInfoChar(&keys, ')');
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+
+ /* show the filter ID only when there are more filters */
+ appendStringInfo(&buf, "Bloom Filter %d: keys=%s",
+ bf->producer_id, keys.data);
+
+ /* include the counts only during ANALYZE */
+ if (es->analyze && bfs != NULL)
+ {
+ /* rejected fraction */
+ double frac = 100.0 * bfs->rejected / Max(1, bfs->checked);
+
+ appendStringInfo(&buf,
+ " checked=" UINT64_FORMAT
+ " rejected=" UINT64_FORMAT
+ " (%.1f%%)",
+ bfs->checked, bfs->rejected, frac);
+ }
+
+ ExplainIndentText(es);
+ appendStringInfoString(es->str, buf.data);
+ appendStringInfoChar(es->str, '\n');
+ pfree(buf.data);
+ }
+ else /* non-text format */
+ {
+ ExplainOpenGroup("Bloom Filter", NULL, true, es);
+ ExplainPropertyText("Keys", keys.data, es);
+ ExplainPropertyInteger("ID", NULL, bf->producer_id, es);
+ if (es->analyze && bfs != NULL)
+ {
+ ExplainPropertyFloat("Checked", NULL,
+ (double) bfs->checked, 0, es);
+ ExplainPropertyFloat("Rejected", NULL,
+ (double) bfs->rejected, 0, es);
+ }
+ ExplainCloseGroup("Bloom Filter", NULL, true, es);
+ }
+
+ pfree(keys.data);
+ }
+
+ if (es->format != EXPLAIN_FORMAT_TEXT)
+ ExplainCloseGroup("Bloom Filters", "Bloom Filters", false, es);
+}
+
/*
* Show the sort keys for a Sort node.
*/
@@ -3474,6 +3597,72 @@ show_hash_info(HashState *hashstate, ExplainState *es)
spacePeakKb);
}
}
+
+ /*
+ * Show infromation about the bloom filter produced by this Hash node
+ * (if any). For plain EXPLAIN, the filter is not initialized / built,
+ * but we still show available plan-time metadata (at least the ID).
+ */
+ if (hashstate->bloom_filter_id > 0)
+ {
+ int producer_id = hashstate->bloom_filter_id;
+ uint64 nbits = 0;
+ int nhashfns = 0;
+ uint64 bytes = 0;
+
+ if (hashstate->bloom_filter != NULL)
+ {
+ nbits = bloom_total_bits(hashstate->bloom_filter);
+ nhashfns = bloom_hash_funcs(hashstate->bloom_filter);
+ bytes = nbits / 8;
+ }
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ ExplainIndentText(es);
+ if (hashstate->bloom_filter != NULL)
+ appendStringInfo(es->str,
+ "Bloom Filter %d: bits=" UINT64_FORMAT
+ " hashes=%d memory=" UINT64_FORMAT "kB"
+ " checked=" UINT64_FORMAT " rejected=" UINT64_FORMAT "\n",
+ producer_id,
+ nbits,
+ nhashfns,
+ BYTES_TO_KILOBYTES(bytes),
+ hashstate->bloomFilterChecked,
+ hashstate->bloomFilterRejected);
+ else if (es->analyze)
+ appendStringInfo(es->str,
+ "Bloom Filter %d: (not initialized)\n",
+ producer_id);
+ else
+ appendStringInfo(es->str,
+ "Bloom Filter %d\n",
+ producer_id);
+ }
+ else
+ {
+ /* there can be just one bloom filter per fproducer (for now) */
+ ExplainOpenGroup("Bloom Filter", "Bloom Filter", true, es);
+
+ ExplainPropertyInteger("Producer", NULL, producer_id, es);
+ ExplainPropertyBool("Initialized",
+ (hashstate->bloom_filter != NULL), es);
+
+ if (hashstate->bloom_filter != NULL)
+ {
+ ExplainPropertyUInteger("Bits", NULL, nbits, es);
+ ExplainPropertyInteger("Hash Functions", NULL, nhashfns, es);
+ ExplainPropertyUInteger("Memory Usage", "kB",
+ BYTES_TO_KILOBYTES(bytes), es);
+ ExplainPropertyFloat("Checked", NULL,
+ (double) hashstate->bloomFilterChecked, 0, es);
+ ExplainPropertyFloat("Rejected", NULL,
+ (double) hashstate->bloomFilterRejected, 0, es);
+ }
+ ExplainCloseGroup("Bloom Filter", "Bloom Filter", true, es);
+ }
+ }
}
/*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 1eb6b9f1f40..3c32a61dddd 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -159,6 +159,8 @@ CreateExecutorState(void)
estate->es_auxmodifytables = NIL;
+ estate->es_bloom_producers = NIL;
+
estate->es_per_tuple_exprcontext = NULL;
estate->es_sourceText = NULL;
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 83d6478bc2b..e71a47b6205 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -456,6 +456,9 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
scanstate->bitmapqualorig =
ExecInitQual(node->bitmapqualorig, (PlanState *) scanstate);
+ ExecInitBloomFilters((PlanState *) scanstate,
+ scanstate->ss.ss_ScanTupleSlot);
+
scanstate->ss.ss_currentRelation = currentRelation;
/*
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 8825bb6fa23..37224324bce 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -35,6 +35,7 @@
#include "executor/instrument.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
+#include "lib/bloomfilter.h"
#include "miscadmin.h"
#include "port/pg_bitutils.h"
#include "utils/lsyscache.h"
@@ -184,6 +185,18 @@ MultiExecPrivateHash(HashState *node)
uint32 hashvalue = DatumGetUInt32(hashdatum);
int bucketNumber;
+ /*
+ * Add the tuple to the pushed-down bloom filter (if any). Do
+ * it here (rather than in ExecHashTableInsert) so that each
+ * tuple is added exactly once, even if it later gets shuffled
+ * between batches by ExecHashIncreaseNumBatches. The filter
+ * would still produce the same matches, but it costs CPU.
+ */
+ if (node->bloom_filter != NULL)
+ bloom_add_element(node->bloom_filter,
+ (unsigned char *) &hashvalue,
+ sizeof(hashvalue));
+
bucketNumber = ExecHashGetSkewBucket(hashtable, hashvalue);
if (bucketNumber != INVALID_SKEW_BUCKET_NO)
{
@@ -665,6 +678,53 @@ ExecHashTableCreate(HashState *state)
MemoryContextSwitchTo(oldcxt);
}
+ /*
+ * If we managed to push down a bloom filter to the outer side of the
+ * hash join, allocate it with the hash table.
+ *
+ * Whether we build the filter is decided by try_push_bloom_filter at
+ * plan time. If there's no recipient node, or when the GUC is set to
+ * off, state->want_bloom_filter is false.
+ *
+ * XXX We don't do this for parallel hash joins, to keep the PoC simple.
+ * The filter would need to live in shared memory, and the workers would
+ * need to coordinate to build it. But it's doable.
+ *
+ * The filter lives in the HashState, in the hashCtx memory context.
+ * That means it gets destroyed along with the hashtable, and it follows
+ * the same lifecycle (during rescans, etc.).
+ *
+ * The size of the filter is bounded by both the estimated inner row
+ * count and a fixed fraction of work_mem. bloom_create() will round
+ * down to the next power-of-two bitset and enforces a 1MB minimum.
+ *
+ * XXX This may need more thought. If we limit bloom_work_mem too much,
+ * the false positive rate will get too bad, and we won't filter enough
+ * tuples for the filter to pay for itself. The adaptive behavior will
+ * eventually skip the filter, but we could just not build it at all?
+ * Or do we want to take the chance, sometimes?
+ */
+ if (state->want_bloom_filter)
+ {
+ MemoryContext oldctx;
+ int bloom_work_mem;
+
+ /* only serial hashjoins for now, init only once */
+ Assert(hashtable->parallel_state == NULL);
+ Assert(state->bloom_filter == NULL);
+
+ state->bloomFilterChecked = 0;
+ state->bloomFilterRejected = 0;
+
+ /* Cap bloom filter at ~1/8 of work_mem, but not less than 1MB. */
+ bloom_work_mem = Max(1024, work_mem / 8);
+
+ oldctx = MemoryContextSwitchTo(hashtable->hashCxt);
+ state->bloom_filter = bloom_create((int64) Max(rows, 1.0),
+ bloom_work_mem, 0);
+ MemoryContextSwitchTo(oldctx);
+ }
+
return hashtable;
}
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 0b365d5b475..8fa7af4cfef 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -169,7 +169,9 @@
#include "executor/instrument.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
+#include "lib/bloomfilter.h"
#include "miscadmin.h"
+#include "port/pg_bitutils.h"
#include "utils/lsyscache.h"
#include "utils/sharedtuplestore.h"
#include "utils/tuplestore.h"
@@ -834,6 +836,7 @@ HashJoinState *
ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
{
HashJoinState *hjstate;
+ HashState *hashState;
Plan *outerNode;
Hash *hashNode;
TupleDesc outerDesc,
@@ -875,11 +878,37 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
outerNode = outerPlan(node);
hashNode = (Hash *) innerPlan(node);
+ /*
+ * Register ourselves as a bloom-filter producer in the EState before
+ * recursing into the outer subtree, so the scan node (we pushed the
+ * filter to) can find us. We do this only if we actually managed to
+ * push down the filter to a scan node.
+ */
+ if (node->bloom_consumer_count > 0)
+ ExecRegisterBloomFilterProducer(hjstate);
+
outerPlanState(hjstate) = ExecInitNode(outerNode, estate, eflags);
outerDesc = ExecGetResultType(outerPlanState(hjstate));
innerPlanState(hjstate) = ExecInitNode((Plan *) hashNode, estate, eflags);
innerDesc = ExecGetResultType(innerPlanState(hjstate));
+ /*
+ * Tell the Hash child to actually build the bloom filter, and the
+ * ID assigned to the filter.
+ *
+ * XXX Seems a bit ugly to manipulate the inner plan state like this.
+ * Surely there's a better way. OTOH the two nodes are pretty tightly
+ * coupled already, so maybe it's fine.
+ *
+ * XXX Also, this assumes the hash table is not built by ExecInitNode(),
+ * which is true for now. But maybe we will relax that in the future
+ * (e.g. so that the scan can push the filter to storage / to remote FDW
+ * node / ...)?
+ */
+ hashState = castNode(HashState, innerPlanState(hjstate));
+ hashState->want_bloom_filter = (node->bloom_consumer_count > 0);
+ hashState->bloom_filter_id = node->bloom_filter_id;
+
/*
* Initialize result slot, type and projection.
*/
@@ -1080,11 +1109,15 @@ ExecEndHashJoin(HashJoinState *node)
/*
* Free hash table
+ *
+ * Clear the bloom_filter pointer. It lives in hashCxt, so it gets freed by
+ * the ExecHashTableDestroy call.
*/
if (node->hj_HashTable)
{
ExecHashTableDestroy(node->hj_HashTable);
node->hj_HashTable = NULL;
+ hashNode->bloom_filter = NULL;
}
/*
@@ -1737,6 +1770,12 @@ ExecReScanHashJoin(HashJoinState *node)
node->hj_HashTable = NULL;
node->hj_JoinState = HJ_BUILD_HASHTABLE;
+ /*
+ * Clear the bloom_filter pointer. It lives in hashCxt, so it gets
+ * freed by the ExecHashTableDestroy call.
+ */
+ hashNode->bloom_filter = NULL;
+
/*
* if chgParam of subnode is not null then plan will be re-scanned
* by first ExecProcNode.
@@ -1975,3 +2014,419 @@ ExecHashJoinInitializeWorker(HashJoinState *state,
ExecSetExecProcNode(&state->js.ps, ExecParallelHashJoin);
}
+
+/*
+ * BLOOM FILTER PUSHDOWN
+ *
+ * The pushdown decision is done in try_push_bloom_filter, when constructing
+ * the plan from the selected paths (see createplan.c). It decides which scan
+ * node should receive the bloom filter (if any), and what expressions it
+ * should use to calculate the hash value.
+ *
+ * Then at execution time:
+ *
+ * - ExecInitHashJoin registers itself in EState.es_bloom_producers
+ * before recursing into child plans, so by the time a recipient's
+ * ExecInit runs, the producer is already discoverable by plan_node_id.
+ * This registration only happens when there's at least one consumer.
+ * It also sets want_bloom_filter for the Hash node.
+ *
+ * - ExecHashTableCreate (in nodeHash.c) builds the actual bloom_filter
+ * when HashState.want_bloom_filter is set (so no work happens when
+ * nobody will probe).
+ *
+ * - Nodes with non-NIL plan->bloom_filters (and supporting bloom
+ * filters) call ExecInitBloomFilters() during its own ExecInit,
+ * which looks up the producer node (in the EState), compiles
+ * ExprStates for the hash expressions, etc. The filter state
+ * (BloomFilterState) gets added to ps->bloom_filters (a node may
+ * have multiple bloom filters from different hash joins).
+ *
+ * - The per-tuple loop of the scan node calls ExecBloomFilters() (much
+ * like ExecQual) to test the tuple against every attached filter,
+ * dropping it on the first filter that excludes it. For scan nodes
+ * this call happens in ExecScanExtended.
+ *
+ * The scan nodes reach the bloom filter via the HashJoinState pointer
+ * added to EState.es_bloom_producers, so that the rescans etc. (filter
+ * freed + recreated when the hash table is destroyed and rebuilt) are
+ * transparent to the consumer. The bloom filter gets reallocated after
+ * a rescan, so the pointer to it may change.
+ *
+ * XXX It's possible the bloom filter gets pushed down to a node that
+ * fails to initialize/use it. It'll be added to the bloom_filters list,
+ * but if the node does not call ExecInitBloomFilters, the filter will
+ * be unused.
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * Lookup the HashJoinState for a producer by plan_node_id in the
+ * EState's es_bloom_producers list. Returns NULL if no matching
+ * producer has registered yet (which can happen for filters attached to
+ * recipients in trees where the producer hasn't ExecInit'd yet -- in
+ * normal execution we always register first).
+ */
+static HashJoinState *
+LookupBloomFilterProducer(EState *estate, int bloom_filter_id)
+{
+ ListCell *lc;
+
+ foreach(lc, estate->es_bloom_producers)
+ {
+ HashJoinState *hjstate = (HashJoinState *) lfirst(lc);
+ HashJoin *plan = (HashJoin *) hjstate->js.ps.plan;
+
+ if (plan->bloom_filter_id == bloom_filter_id)
+ return hjstate;
+ }
+ return NULL;
+}
+
+/*
+ * ExecBloomFilterHash
+ * Calculate the hash value for a tuple in the recipient node.
+ *
+ * Uses the per-key ExprStates initialized by ExecInitBloomFilters. Mirrors the
+ * scheme used by the Hash node, so that it matches what was inserted into the
+ * hashtable (and filter).
+ *
+ * Returns false if a strict key is NULL: such a tuple can't match anything in
+ * the bloom filter, but we still must let it pass through to the upstream join
+ * so the join (rather than us) decides what to do with it (e.g. emit
+ * NULL-extended for an outer join).
+ *
+ * XXX I'm not sure about this strict/NULL business.
+ */
+static inline bool
+ExecBloomFilterHash(BloomFilterState *bfs, ExprContext *econtext,
+ uint32 *hashvalue)
+{
+ bool isnull;
+ uint32 hash;
+
+ hash = DatumGetUInt32(ExecEvalExpr(bfs->keys, econtext, &isnull));
+
+ if (isnull)
+ return 0; /* XXX correct? do we care about NULL values?*/
+
+ *hashvalue = hash;
+ return true;
+}
+
+/*
+ * ADAPTIVE BEHAVIOR
+ *
+ * If the bloom filter lets through most (or all) tuples, it becomes somewhat
+ * useless - we're just wasting CPU cycles, getting nothing in return. We could
+ * simply stop using such filter. But we've already paid quite a bit to build
+ * it, and maybe the data set is not uniform and we'll get into a part where
+ * fewer tuples pass.
+ *
+ * So we're evaluating the match rate for windows of 1000 probes. If more than
+ * 90% match, we start sampling 1% of the probes (i.e. 99% it treated as a
+ * match without looking at the filter). And if the match rate drops below 80%,
+ * we stop the sampling and all probes go to the filter.
+ *
+ * XXX These are empirical values, picked based on experiments. "Perfect"
+ * values depend on hardware, number of keys, data types, ... and maybe even
+ * on how many hash joins / pushed-down filters there are, and how deep (the
+ * deeper the bigger the benefit).
+ *
+ * XXX Maybe we should sample more probes, or maybe the window should be a bit
+ * smaller? With 1% and 1000 probes per window, it'll take 100k probes to
+ * enable the filter again. That seems like a lot.
+ *
+ * XXX We should probably track the number of times we "disabled" the filter,
+ * and what fraction of entries were "let through" during sampling periods.
+ *
+ * XXX There's an intentional gap between low/high thresholds, to add a bit
+ * of hysteresis into the behavior, so it does not flap all the time.
+ */
+#define BLOOM_ADAPTIVE_WINDOW_SIZE 1000
+#define BLOOM_ADAPTIVE_HIGH_MATCH_PERCENT 90
+#define BLOOM_ADAPTIVE_LOW_MATCH_PERCENT 80
+#define BLOOM_ADAPTIVE_SAMPLE_RATE 100
+
+/*
+ * ExecBloomFilterShouldProbe
+ * Decide if the next tuple should probe the bloom filter.
+ *
+ * Returns true if the next value should actually probe the bloom filter
+ * We sample 1/100 (1/BLOOM_ADAPTIVE_SAMPLE_RATE) probes, i.e. 1%.
+ */
+static inline bool
+ExecBloomFilterShouldProbe(BloomFilterState *bfs)
+{
+ if (!bfs->adaptiveSampling)
+ return true;
+
+ bfs->adaptiveSampleCounter++;
+
+ if (bfs->adaptiveSampleCounter >= BLOOM_ADAPTIVE_SAMPLE_RATE)
+ {
+ bfs->adaptiveSampleCounter = 0;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * ExecBloomFilterUpdateAdaptiveState
+ * Update the adaptive state for sampling the probes.
+ *
+ * Adjust the adaptive behavior every 1000 probes. If too many probes match,
+ * stop using the filter (and just sample 1% of probes instead). If we were
+ * sampling, and the fraction of matches drops enough, stop the sampling.
+ */
+static inline void
+ExecBloomFilterUpdateAdaptiveState(BloomFilterState *bfs, bool match)
+{
+ bfs->adaptiveWindowProbes++;
+ if (match)
+ bfs->adaptiveWindowMatches++;
+
+ /* have we done enough probes in this window? */
+ if (bfs->adaptiveWindowProbes >= BLOOM_ADAPTIVE_WINDOW_SIZE)
+ {
+ uint64 match_percent;
+
+ /* fraction of matches */
+ match_percent = (bfs->adaptiveWindowMatches * 100) /
+ bfs->adaptiveWindowProbes;
+
+ if (!bfs->adaptiveSampling &&
+ match_percent > BLOOM_ADAPTIVE_HIGH_MATCH_PERCENT)
+ {
+ /* Too many matches - start sampling. */
+ bfs->adaptiveSampling = true;
+ bfs->adaptiveSampleCounter = 0;
+ }
+ else if (bfs->adaptiveSampling &&
+ match_percent < BLOOM_ADAPTIVE_LOW_MATCH_PERCENT)
+ {
+ /* Stop sampling if the match fraction got low enough. */
+ bfs->adaptiveSampling = false;
+ bfs->adaptiveSampleCounter = 0;
+ }
+
+ /* in any case, start a new window of probes */
+ bfs->adaptiveWindowProbes = 0;
+ bfs->adaptiveWindowMatches = 0;
+ }
+}
+
+/*
+ * ExecBloomFilters
+ * Probe bloom filters for the current slot.
+ *
+ * Test the slot in the expression context (set by the scan node) against
+ * every bloom filter attached to the node. Returns true if the tuple matches
+ * all filters (some of which may have been skipped, because the hash table
+ * isn't built yet); false if at least one filter conclusively excludes it.
+ *
+ * 'filters' is a list of BloomFilterState for each filter, pushed to the
+ * node (stored in planstate->bloom_filters). It may be NIL, which means
+ * are no filters, and the function simply returns NULL.
+ *
+ * The caller is responsible for having set econtext->ecxt_scantuple to
+ * 'slot' first. We do not reset the per-tuple context here (it's up to the
+ * scan node).
+ *
+ * Designed to be called like ExecQual from the recipient's per-tuple
+ * loop. See ExecScanExtended for the scan-node integration point.
+ *
+ * XXX We're pushing filters to scan nodes, which set the scan slot. And
+ * setrefs.c is currently wired to do fix_scan_bloom_filters, called from
+ * set_plan_refs. If we decide to push filters to other nodes (e.g. joins),
+ * this may need some rework.
+ */
+bool
+ExecBloomFilters(List *filters, ExprContext *econtext)
+{
+ ListCell *lc;
+
+ /* bail out if no filters */
+ if (filters == NIL)
+ return true;
+
+ foreach(lc, filters)
+ {
+ BloomFilterState *bfs = (BloomFilterState *) lfirst(lc);
+ HashJoinState *producer = bfs->producer;
+ HashState *hashNode;
+ bloom_filter *bf;
+ uint32 hashvalue;
+
+ /* Producer should always exist (resolved at init time). */
+ Assert(producer != NULL);
+
+ /*
+ * The hashtable (and the bloom filter) is built lazily the first
+ * time it needs to do a lookup. Until then, assume everything
+ * matches everything through. Once the filter is in place, start
+ * probing it.
+ *
+ * XXX It should only take a couple tuples (maybe just a single one)
+ * from the scan node before the filter is available.
+ */
+ hashNode = castNode(HashState, innerPlanState(&producer->js.ps));
+ bf = hashNode->bloom_filter;
+ if (bf == NULL)
+ continue;
+
+ /*
+ * When recent bloom probes mostly pass through, probe only a sample of
+ * values to avoid spending work on an ineffective filter. Sampled
+ * probes keep updating the recent match fraction, so filtering resumes
+ * for every value once the filter becomes selective again.
+ */
+ if (!ExecBloomFilterShouldProbe(bfs))
+ continue;
+
+ /* NULL strict key: tuple cannot be in the filter, pass through. */
+ if (!ExecBloomFilterHash(bfs, econtext, &hashvalue))
+ continue;
+
+ /*
+ * XXX It's a bit silly the counters are in two places. We should
+ * keep just the hashNode counters, and get rid of bfs counters.
+ */
+ bfs->checked++;
+ hashNode->bloomFilterChecked++;
+
+ /* If not matching, we're done - reject the tuple. */
+ if (bloom_lacks_element(bf,
+ (unsigned char *) &hashvalue,
+ sizeof(hashvalue)))
+ {
+ bfs->rejected++;
+ hashNode->bloomFilterRejected++;
+ ExecBloomFilterUpdateAdaptiveState(bfs, false);
+ return false;
+ }
+
+ ExecBloomFilterUpdateAdaptiveState(bfs, true);
+ }
+
+ return true;
+}
+
+/*
+ * ExecInitBloomFilters
+ * Initialize state for pushed-down bloom filters.
+ *
+ * Called by nodes that want to act as a recipient of pushed-down filters,
+ * after the node's projection / scan-tuple slot are set up, just like
+ * for regular quals.
+ *
+ * Walks the plan's bloom_filters list and produces a list of BloomFilterState
+ * nodes, stored in planstate->bloom_filters. The producer HashJoinState node
+ * is resolved here, once, via EState.es_bloom_producers; so that no lookup is
+ * needed at probe time (the bloom_filter pointer may change on rescan, but
+ * that's not what we store).
+ *
+ * 'output_slot' is the slot whose values the filter expressions will be
+ * evaluated against (i.e. the same slot the surrounding qual evaluates
+ * against, post-setrefs).
+ *
+ * XXX For now this has to be the scan slot. See the comment about setrefs
+ * a bit earlier. Could be relaxed later, if we support to pushdown to
+ * other node types.
+ *
+ * XXX The filter states are initialized in es_query_cxt, but maybe that's
+ * too long-lived. The states live only as long as the recipient node.
+ */
+void
+ExecInitBloomFilters(PlanState *planstate, TupleTableSlot *output_slot)
+{
+ Plan *plan = planstate->plan;
+ EState *estate = planstate->state;
+ List *result = NIL;
+ ListCell *lc;
+ MemoryContext oldctx;
+
+ /* bail out if there are no pushed-down filters */
+ if (plan->bloom_filters == NIL)
+ return;
+
+ oldctx = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ foreach(lc, plan->bloom_filters)
+ {
+ BloomFilter *bf = lfirst_node(BloomFilter, lc);
+ BloomFilterState *bfs;
+ int nkeys;
+
+ nkeys = list_length(bf->filter_exprs);
+ Assert(nkeys > 0);
+ Assert(nkeys == list_length(bf->hashops));
+ Assert(nkeys == list_length(bf->hashcollations));
+
+ bfs = makeNode(BloomFilterState);
+
+ /* XXX some of this is redundant */
+ bfs->filter = bf;
+ bfs->producer_id = bf->producer_id;
+ bfs->producer = LookupBloomFilterProducer(estate, bf->producer_id);
+
+ /* initialize the expression state for the hashvalue calculation */
+ {
+ Oid *outer_hashfuncid = palloc_array(Oid, nkeys);
+ Oid *inner_hashfuncid = palloc_array(Oid, nkeys);
+ bool *hash_strict = palloc_array(bool, nkeys);
+ ListCell *lc2;
+
+ /*
+ * Determine the hash function for each side of the join for the given
+ * join operator, and detect whether the join operator is strict.
+ */
+ foreach(lc2, bf->hashops)
+ {
+ Oid hashop = lfirst_oid(lc2);
+ int i = foreach_current_index(lc2);
+
+ if (!get_op_hash_functions(hashop,
+ &outer_hashfuncid[i],
+ &inner_hashfuncid[i]))
+ elog(ERROR,
+ "could not find hash function for hash operator %u",
+ hashop);
+ hash_strict[i] = op_strict(hashop);
+ }
+
+ /* state for the hash value calculation */
+ bfs->keys = ExecBuildHash32Expr(output_slot->tts_tupleDescriptor,
+ output_slot->tts_ops,
+ outer_hashfuncid,
+ bf->hashcollations,
+ bf->filter_exprs,
+ hash_strict,
+ planstate,
+ 0);
+ }
+
+ result = lappend(result, bfs);
+ }
+
+ planstate->bloom_filters = result;
+
+ MemoryContextSwitchTo(oldctx);
+}
+
+/*
+ * ExecRegisterBloomFilterProducer
+ * Register the pushed downn bloom filter.
+ *
+ * Called by ExecInitHashJoin (before recursing into the outer subtree)
+ * to register this HashJoinState as a producer for the pushed-down filter.
+ * Recipients in the outer subtree will look us up here by plan_node_id.
+ */
+void
+ExecRegisterBloomFilterProducer(HashJoinState *hjstate)
+{
+ EState *estate = hjstate->js.ps.state;
+
+ estate->es_bloom_producers = lappend(estate->es_bloom_producers, hjstate);
+}
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index d52012e8a69..bd7e5966017 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -600,6 +600,9 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
indexstate->recheckqual =
ExecInitQual(node->recheckqual, (PlanState *) indexstate);
+ ExecInitBloomFilters((PlanState *) indexstate,
+ indexstate->ss.ss_ScanTupleSlot);
+
/*
* If we are just doing EXPLAIN (ie, aren't going to run the plan), stop
* here. This allows an index-advisor plugin to EXPLAIN a plan containing
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 39f6691ee35..ef56522fbc5 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -970,6 +970,9 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
indexstate->indexorderbyorig =
ExecInitExprList(node->indexorderbyorig, (PlanState *) indexstate);
+ ExecInitBloomFilters((PlanState *) indexstate,
+ indexstate->ss.ss_ScanTupleSlot);
+
/*
* If we are just doing EXPLAIN (ie, aren't going to run the plan), stop
* here. This allows an index-advisor plugin to EXPLAIN a plan containing
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index f3d273e1c5e..738502433b4 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -147,6 +147,9 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags)
scanstate->repeatable =
ExecInitExpr(tsc->repeatable, (PlanState *) scanstate);
+ ExecInitBloomFilters((PlanState *) scanstate,
+ scanstate->ss.ss_ScanTupleSlot);
+
/*
* If we don't have a REPEATABLE clause, select a random seed. We want to
* do this just once, since the seed shouldn't change over rescans.
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 5bcb0a861d7..4d3d7ba10a9 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -268,6 +268,9 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
scanstate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+ ExecInitBloomFilters((PlanState *) scanstate,
+ scanstate->ss.ss_ScanTupleSlot);
+
/*
* When EvalPlanQual() is not in use, assign ExecProcNode for this node
* based on the presence of qual and projection. Each ExecSeqScan*()
diff --git a/src/backend/executor/nodeTidrangescan.c b/src/backend/executor/nodeTidrangescan.c
index b387ed6c308..b00a3736b99 100644
--- a/src/backend/executor/nodeTidrangescan.c
+++ b/src/backend/executor/nodeTidrangescan.c
@@ -434,6 +434,9 @@ ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags)
tidrangestate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, (PlanState *) tidrangestate);
+ ExecInitBloomFilters((PlanState *) tidrangestate,
+ tidrangestate->ss.ss_ScanTupleSlot);
+
TidExprListCreate(tidrangestate);
/*
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 6641df10999..f7ba78a63fc 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -551,6 +551,9 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
tidstate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate);
+ ExecInitBloomFilters((PlanState *) tidstate,
+ tidstate->ss.ss_ScanTupleSlot);
+
TidExprListCreate(tidstate);
/*
diff --git a/src/backend/lib/bloomfilter.c b/src/backend/lib/bloomfilter.c
index 73b3768a172..fbcf788e271 100644
--- a/src/backend/lib/bloomfilter.c
+++ b/src/backend/lib/bloomfilter.c
@@ -192,6 +192,25 @@ bloom_prop_bits_set(bloom_filter *filter)
return bits_set / (double) filter->m;
}
+/*
+ * Total bitset size, in bits. Useful for EXPLAIN instrumentation:
+ * divide by 8 to get the bitset's memory footprint in bytes.
+ */
+uint64
+bloom_total_bits(bloom_filter *filter)
+{
+ return filter->m;
+}
+
+/*
+ * Number of hash functions in use.
+ */
+int
+bloom_hash_funcs(bloom_filter *filter)
+{
+ return filter->k_hash_funcs;
+}
+
/*
* Which element in the sequence of powers of two is less than or equal to
* target_bitset_bits?
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1c575e56ff6..c3072a29ccc 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -156,6 +156,7 @@ bool enable_material = true;
bool enable_memoize = true;
bool enable_mergejoin = true;
bool enable_hashjoin = true;
+bool enable_hashjoin_bloom = true;
bool enable_gathermerge = true;
bool enable_partitionwise_join = false;
bool enable_partitionwise_aggregate = false;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index de6a183da79..7ecb551aae6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4689,6 +4689,298 @@ create_mergejoin_plan(PlannerInfo *root,
return join_plan;
}
+/*
+ * BLOOM FILTER PUSHDOWN
+ *
+ * When creating a hash join plan, consider building a bloom filter and
+ * pushing it down to the outer subtree. For now we only push filters to
+ * scan nodes containing all the join keys. When we find such scan node,
+ * we append the BloomFilter ID to the node's bloom_filters list, and
+ * increment the bloom_consumer_count for the hashjoin.
+ *
+ * As setrefs hashn't run yet, the join keys are still the raw Vars.
+ * So it's safe to compare var->varno against the scanrelid, and copy
+ * the keys verbatim onto the recipient. setrefs will rewrite the Vars
+ * later as usual, just like for the recipient's qual.
+ *
+ * XXX In most cases there'll be only a single consumer node. To get
+ * multiple consumers, we'd need either joins on the same keys, or
+ * ability to produce filters for subsets of the join keys (for cases
+ * where the join is more complex, and does not map to a single scan
+ * node directly). Seems like a possible future improvement.
+ *
+ * XXX Actually, we could have multiple consumer nodes for partitioned
+ * tables, where each partition gets a separate scan. Which seems like
+ * something we should support.
+ *
+ * XXX For simplicity, all outer join keys have to be bare Vars (from
+ * the same RTE). We could relax this later, and allow joins on more
+ * complex expressions. Not sure if that'll erase some of the benefits,
+ * which relies on filter probes being much cheaper hashtable probes.
+ * It also doesn't seem like a very common case.
+ *
+ * XXX The recipient node must be one of a small set of scan nodes. We
+ * could relax this, and allow pushing to other nodes (e.g. joins or
+ * aggregates). Future improvement.
+ *
+ * XXX We don't currently push the same HashJoin to multiple recipients,
+ * but multiple HashJoins may attach a filter to the same scan node.
+ * --------------------------------------------------------------------------
+ */
+
+/*
+ * bloom_join_side_preserved
+ * Decide if we can push filter to inner/outer side of a join.
+ *
+ * Outer joins that emit unmatched outer tuples (LEFT/ANTI/FULL) are
+ * skipped: dropping outer tuples there would be incorrect.
+ */
+static bool
+bloom_join_side_preserved(JoinType jointype, bool to_outer)
+{
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ return true;
+ case JOIN_LEFT:
+ case JOIN_SEMI:
+ case JOIN_ANTI:
+ return to_outer;
+ case JOIN_RIGHT:
+ case JOIN_RIGHT_SEMI:
+ case JOIN_RIGHT_ANTI:
+ return !to_outer;
+ case JOIN_FULL:
+ return false;
+ default:
+ return false;
+ }
+}
+
+/*
+ * find_bloom_filter_recipient
+ * Try to find a scan node to push filter to.
+ *
+ * We support pushing filter to a subset of scan nodes (could be extended
+ * later). We support pushing filters through intermediate nodes (joins,
+ * sorts, ...). See bloom_join_side_preserved for joins.
+ *
+ * XXX We could push filters through more nodes - e.g. aggregates if the
+ * hash keys match GROUP BY keys (are a subset of).
+ *
+ * XXX We could do pushdown to parallel parts of a query. But we'd need
+ * a different way to communicate if a filter is built etc. (the worker
+ * won't have access to the hashjoin state).
+ */
+static Plan *
+find_bloom_filter_recipient(Plan *plan, Index target_relid)
+{
+ /* XXX shouldn't really happen, I think */
+ if (plan == NULL)
+ return NULL;
+
+ /* XXX no pushdown to parallel parts of a query */
+ if (plan->parallel_aware)
+ return NULL;
+
+ switch (nodeTag(plan))
+ {
+ case T_SeqScan:
+ case T_SampleScan:
+ case T_IndexScan:
+ case T_IndexOnlyScan:
+ case T_BitmapHeapScan:
+ case T_TidScan:
+ case T_TidRangeScan:
+ {
+ Scan *scan = (Scan *) plan;
+
+ if (scan->scanrelid == target_relid)
+ return plan;
+ return NULL;
+ }
+ case T_Sort:
+ case T_IncrementalSort:
+ case T_Material:
+ case T_Memoize:
+ case T_Unique:
+ case T_Limit:
+ return find_bloom_filter_recipient(outerPlan(plan), target_relid);
+ case T_HashJoin:
+ case T_NestLoop:
+ case T_MergeJoin:
+ {
+ JoinType jt = ((Join *) plan)->jointype;
+ Plan *res;
+
+ /*
+ * We can only push to one node, but we don't know on which
+ * side of the join it is (e.g. for inner joins it can be on
+ * both sides). So make sure to check both, if compatible
+ * with the join type (per bloom_join_side_preserved).
+ */
+ if (bloom_join_side_preserved(jt, true))
+ {
+ res = find_bloom_filter_recipient(outerPlan(plan),
+ target_relid);
+ if (res != NULL)
+ return res;
+ }
+ if (bloom_join_side_preserved(jt, false))
+ {
+ res = find_bloom_filter_recipient(innerPlan(plan),
+ target_relid);
+ if (res != NULL)
+ return res;
+ }
+ return NULL;
+ }
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * try_push_bloom_filter
+ * Attempt to pushdown a bloom filter for the current hashjoin.
+ *
+ * The filter pushdown happens during plan creation, i.e. after the plan was
+ * already selected. That is not entirely optimal, and it has a couple of
+ * annoying consequences.
+ *
+ * The main disadvantage is that injecting the filter to a scan node may
+ * significantly alter the number of tuples produced by that scan node. If a
+ * filter eliminates 99% of the rows, the scan produces 1/100 of the rows it
+ * was planned with. It would not affect the scan itself, but if there are
+ * other nodes (between the scan and the join), maybe we'd have planned them
+ * differently if we knew about the lower cardinality?
+ *
+ * Similarly, it's confusing in the explain. That is, we'll get "rows=N"
+ * with the planner cardinality (before the filter was pushed down), but
+ * then in EXPLAIN ANALYZE it'll get much lower values. It'd be easy to
+ * confuse with inaccurate estimates.
+ *
+ * It'd be better to know about the filter earlier, when constructing the scan
+ * path. But that's not quite feasible with our bottom-up planner. When planing
+ * the scan, we don't know which of the joins above it will be hashjoins, or
+ * if it can pushdown the filter. We'd have to speculate, or maybe build more
+ * paths with/without expectation of the bloom filter pushdown. But that seems
+ * not great, as it'd add overhead for everyone.
+ */
+static void
+try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan)
+{
+ List *hashkeys = hj->hashkeys;
+ List *hashops = hj->hashoperators;
+ List *hashcolls = hj->hashcollations;
+ ListCell *lc;
+ Index target_relid = 0;
+ Plan *recipient;
+ BloomFilter *bf;
+
+ /* bail out if feature disabled. */
+ if (!enable_hashjoin_bloom)
+ return;
+
+ /* XXX shouldn't really happen, I think */
+ if (hashkeys == NIL)
+ return;
+
+ Assert(list_length(hashkeys) == list_length(hashops));
+ Assert(list_length(hashkeys) == list_length(hashcolls));
+
+ /*
+ * Pushdown is unsafe for join types that emit unmatched outer tuples
+ * (LEFT/ANTI/FULL): we'd risk dropping outer tuples the join would
+ * otherwise have emitted (possibly NULL-extended).
+ */
+ switch (hj->join.jointype)
+ {
+ case JOIN_INNER:
+ case JOIN_RIGHT:
+ case JOIN_SEMI:
+ case JOIN_RIGHT_SEMI:
+ case JOIN_RIGHT_ANTI:
+ /* these join types are OK */
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * All hashkeys must be bare Vars referencing the same base RTE.
+ *
+ * XXX We could be a bit less strict, and check that at least some of the
+ * hashkeys are bare Vars, not all of them. So with joins on multiple
+ * expressions we'd have better chance to push a filter down. Doesn't
+ * seem worth it, at least for now.
+ */
+ foreach(lc, hashkeys)
+ {
+ Node *k = (Node *) lfirst(lc);
+ Var *var;
+
+ /* not a plain Var */
+ if (!IsA(k, Var))
+ return;
+
+ var = (Var *) k;
+
+ /*
+ * Reject outer references, whole-row or system columns, and
+ * special varnos (not sure we can get them here, though).
+ */
+ if ((var->varlevelsup != 0) ||
+ (var->varattno <= 0) ||
+ IS_SPECIAL_VARNO(var->varno))
+ return;
+
+ /* make sure all the vars are for the same relid */
+ if (target_relid == 0)
+ target_relid = var->varno;
+ else if (var->varno != target_relid)
+ return;
+ }
+
+ /* should have found at least one var */
+ Assert(target_relid != 0);
+
+ /*
+ * See if we can find the scan node for target_relid. It certainly is
+ * in the plan somewhere, but it may not be able to pushdown the filter
+ * to it (because of a join or so).
+ */
+ recipient = find_bloom_filter_recipient(outer_plan, target_relid);
+ if (recipient == NULL)
+ return;
+
+ /*
+ * If we found a recipient, assign the filter an ID. We'll use it to
+ * register the filter in ExecRegisterBloomFilterProducer, and then
+ * for lookups in LookupBloomFilterProducer during execution.
+ *
+ * XXX We can't use plan_node_id, as it's not assigned yet, that only
+ * happens in set_plan_refs. Also, if we ever allow multiple filters
+ * per hashtable (e.g. for different subsets of keys), it's not work.
+ */
+ hj->bloom_filter_id = ++root->glob->lastBloomFilterId;
+
+ bf = makeNode(BloomFilter);
+ bf->filter_exprs = (List *) copyObject(hashkeys);
+ bf->hashops = list_copy(hashops);
+ bf->hashcollations = list_copy(hashcolls);
+ bf->producer_id = hj->bloom_filter_id;
+
+ recipient->bloom_filters = lappend(recipient->bloom_filters, bf);
+
+ /*
+ * XXX We've manged to push the filter to the scan node, but maybe
+ * we should wait with updating bloom_consumer_count when it actually
+ * initializes the filters in ExecInit()?
+ */
+ hj->bloom_consumer_count++;
+}
+
static HashJoin *
create_hashjoin_plan(PlannerInfo *root,
HashPath *best_path)
@@ -4859,6 +5151,13 @@ create_hashjoin_plan(PlannerInfo *root,
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
+ /*
+ * Try to push the bloom filter for the hashtable down to nodes in the outer
+ * subtree. If a suitable scan node exists, add the filter to bloom_filters,
+ * and bump our bloom_consumer_count.
+ */
+ try_push_bloom_filter(root, join_plan, outer_plan);
+
return join_plan;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 846bd7c1fbe..e7ca3472d7c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -389,6 +389,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
glob->lastPHId = 0;
glob->lastRowMarkId = 0;
glob->lastPlanNodeId = 0;
+ glob->lastBloomFilterId = 0;
glob->transientPlan = false;
glob->dependsOnRole = false;
glob->partition_directory = NULL;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index ff0e875f2a2..0059acfccbe 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -161,6 +161,7 @@ static Node *fix_scan_expr(PlannerInfo *root, Node *node,
int rtoffset, double num_exec);
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
+static void fix_scan_bloom_filters(PlannerInfo *root, Plan *plan, int rtoffset);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
static void set_param_references(PlannerInfo *root, Plan *plan);
@@ -665,6 +666,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->scan.plan.qual =
fix_scan_list(root, splan->scan.plan.qual,
rtoffset, NUM_EXEC_QUAL(plan));
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_SampleScan:
@@ -681,6 +685,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->tablesample = (TableSampleClause *)
fix_scan_expr(root, (Node *) splan->tablesample,
rtoffset, 1);
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_IndexScan:
@@ -706,6 +713,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->indexorderbyorig =
fix_scan_list(root, splan->indexorderbyorig,
rtoffset, NUM_EXEC_QUAL(plan));
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_IndexOnlyScan:
@@ -744,6 +754,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->bitmapqualorig =
fix_scan_list(root, splan->bitmapqualorig,
rtoffset, NUM_EXEC_QUAL(plan));
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_TidScan:
@@ -760,6 +773,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->tidquals =
fix_scan_list(root, splan->tidquals,
rtoffset, 1);
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_TidRangeScan:
@@ -776,6 +792,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->tidrangequals =
fix_scan_list(root, splan->tidrangequals,
rtoffset, 1);
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_SubqueryScan:
@@ -1426,6 +1445,30 @@ set_indexonlyscan_references(PlannerInfo *root,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) plan));
+ /*
+ * Bloom filter pushdown: any BloomFilter recipient lists also need
+ * their key expressions rewritten to reference the index tuple, since
+ * that's what the recipient scan returns and what ExecBloomFilters
+ * will evaluate against.
+ */
+ if (plan->scan.plan.bloom_filters != NIL)
+ {
+ ListCell *lc2;
+
+ foreach(lc2, plan->scan.plan.bloom_filters)
+ {
+ BloomFilter *bf = lfirst_node(BloomFilter, lc2);
+
+ bf->filter_exprs = (List *)
+ fix_upper_expr(root,
+ (Node *) bf->filter_exprs,
+ index_itlist,
+ INDEX_VAR,
+ rtoffset,
+ NRM_EQUAL,
+ NUM_EXEC_QUAL((Plan *) plan));
+ }
+ }
/* indexqual is already transformed to reference index columns */
plan->indexqual = fix_scan_list(root, plan->indexqual,
rtoffset, 1);
@@ -2398,6 +2441,26 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
return expression_tree_walker(node, fix_scan_expr_walker, context);
}
+/*
+ * Fix references in hashkey expressions of bloom filters pushed down.
+ */
+static void
+fix_scan_bloom_filters(PlannerInfo *root, Plan *plan, int rtoffset)
+{
+ ListCell *lc;
+
+ if (plan->bloom_filters == NIL)
+ return;
+
+ foreach(lc, plan->bloom_filters)
+ {
+ BloomFilter *bf = lfirst_node(BloomFilter, lc);
+
+ bf->filter_exprs = fix_scan_list(root, bf->filter_exprs, rtoffset,
+ NUM_EXEC_QUAL(plan));
+ }
+}
+
/*
* set_join_references
* Modify the target list and quals of a join node to reference its
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3c1e6b31bf8..c9dcb294d4d 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -919,6 +919,13 @@
boot_val => 'true',
},
+{ name => 'enable_hashjoin_bloom', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
+ short_desc => 'Enables hash join bloom filter pushdown to the outer side.',
+ flags => 'GUC_EXPLAIN',
+ variable => 'enable_hashjoin_bloom',
+ boot_val => 'true',
+},
+
{ name => 'enable_incremental_sort', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
short_desc => 'Enables the planner\'s use of incremental sort steps.',
flags => 'GUC_EXPLAIN',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 86f2e16eba0..34f98b42ff6 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -448,6 +448,7 @@
#enable_distinct_reordering = on
#enable_self_join_elimination = on
#enable_eager_aggregate = on
+#enable_hashjoin_bloom = on
# - Planner Cost Constants -
diff --git a/src/include/executor/execScan.h b/src/include/executor/execScan.h
index 18b03235c3c..8cfe9b03c3e 100644
--- a/src/include/executor/execScan.h
+++ b/src/include/executor/execScan.h
@@ -170,10 +170,11 @@ ExecScanExtended(ScanState *node,
/* interrupt checks are in ExecScanFetch */
/*
- * If we have neither a qual to check nor a projection to do, just skip
- * all the overhead and return the raw scan tuple.
+ * If we have neither a qual to check nor a projection to do nor any
+ * pushed-down bloom filter to probe, just skip all the overhead and
+ * return the raw scan tuple.
*/
- if (!qual && !projInfo)
+ if (!qual && !projInfo && node->ps.bloom_filters == NIL)
{
ResetExprContext(econtext);
return ExecScanFetch(node, epqstate, accessMtd, recheckMtd);
@@ -214,6 +215,21 @@ ExecScanExtended(ScanState *node,
*/
econtext->ecxt_scantuple = slot;
+ /*
+ * If runtime bloom filters have been pushed down to this scan,
+ * check them first. A rejected tuple is dropped silently (no
+ * "Rows Removed by Filter" instrumentation -- the per-filter
+ * counters in BloomFilterState already capture this).
+ *
+ * XXX Maybe we should include this in "Rows Removed by Filter"?
+ */
+ if (node->ps.bloom_filters != NIL &&
+ !ExecBloomFilters(node->ps.bloom_filters, econtext))
+ {
+ ResetExprContext(econtext);
+ continue;
+ }
+
/*
* check that the current tuple satisfies the qual-clause
*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1798e6027d4..12b0b2faedf 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -514,6 +514,18 @@ ExecProject(ProjectionInfo *projInfo)
}
#endif
+/*
+ * Bloom filter pushdown (see nodeHashjoin.c). Declared here so that
+ * scan nodes that act as recipients don't need to pull in hashjoin
+ * internals just to call these helpers from their ExecInit.
+ *
+ * XXX There's probably a better place for this. It should live in
+ * the executor somewhere, not in nodeHashjoin.c?
+ */
+extern bool ExecBloomFilters(List *filters, ExprContext *econtext);
+extern void ExecInitBloomFilters(PlanState *planstate,
+ TupleTableSlot *output_slot);
+
/*
* ExecQual - evaluate a qual prepared with ExecInitQual (possibly via
* ExecPrepareQual). Returns true if qual is satisfied, else false.
diff --git a/src/include/executor/nodeHashjoin.h b/src/include/executor/nodeHashjoin.h
index aebd39be8b5..08efefae209 100644
--- a/src/include/executor/nodeHashjoin.h
+++ b/src/include/executor/nodeHashjoin.h
@@ -31,4 +31,13 @@ extern void ExecHashJoinInitializeWorker(HashJoinState *state,
extern void ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue,
BufFile **fileptr, HashJoinTable hashtable);
+/*
+ * Bloom filter pushdown producer-side helper (see nodeHashjoin.c).
+ *
+ * ExecBloomFilters and ExecInitBloomFilters live in executor.h so that
+ * scan nodes can call them from ExecInit without having to pull in
+ * hashjoin internals.
+ */
+extern void ExecRegisterBloomFilterProducer(HashJoinState *hjstate);
+
#endif /* NODEHASHJOIN_H */
diff --git a/src/include/lib/bloomfilter.h b/src/include/lib/bloomfilter.h
index 860ee9bdc72..6b0026c8c45 100644
--- a/src/include/lib/bloomfilter.h
+++ b/src/include/lib/bloomfilter.h
@@ -23,5 +23,7 @@ extern void bloom_add_element(bloom_filter *filter, unsigned char *elem,
extern bool bloom_lacks_element(bloom_filter *filter, unsigned char *elem,
size_t len);
extern double bloom_prop_bits_set(bloom_filter *filter);
+extern uint64 bloom_total_bits(bloom_filter *filter);
+extern int bloom_hash_funcs(bloom_filter *filter);
#endif /* BLOOMFILTER_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e64fd8c7ea3..aad721f3421 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -763,6 +763,14 @@ typedef struct EState
List *es_auxmodifytables; /* List of secondary ModifyTableStates */
+ /*
+ * List of nodes producing pushed-down bloom filters. Each list element
+ * is a HashJoinState with at least one filter pushed down to a scan node.
+ * Scans look up their producer in this list (by plan_node_id) lazily on
+ * first probe (and cache the result).
+ */
+ List *es_bloom_producers;
+
/*
* this ExprContext is for per-output-tuple operations, such as constraint
* checks and index-value computations. It will be reset for each output
@@ -1287,8 +1295,58 @@ typedef struct PlanState
bool outeropsset;
bool inneropsset;
bool resultopsset;
+
+ /*
+ * Bloom filters (BloomFilterState) pushed to this node (NIL if no
+ * filters were pushed down to this node). Right now only some scan
+ * nodes expect filters, but the list is in PlanState so that we can
+ * expand the feature to more nodes in the future.
+ */
+ List *bloom_filters;
} PlanState;
+/*
+ * BloomFilterState
+ *
+ * State for executing (evaluating) a BloomFilter, pushed to a scan node.
+ *
+ * The producer is the HashJoinState this bloom filter is for. It's resolved
+ * at executor-init time using EState.es_bloom_producers (the producer always
+ * runs ExecInit before its recipients).
+ *
+ * 'checked' and 'rejected' are per-recipient counters reported by EXPLAIN
+ * ANALYZE. It's a bit redundant with the fields we have in the producer
+ * (see HashState). But if we ever end up with multiple consumers per filter,
+ * it'd be useful.
+ *
+ * The adaptive fields track the match fraction in recently checked probes.
+ * When most checks are passing through, the executor starts probing less
+ * frequently (and sampling probes instead), until the fraction of matches
+ * drops below some threshold.
+ */
+typedef struct BloomFilterState
+{
+ NodeTag type;
+
+ /* producer HJ node and the filter */
+ int producer_id;
+ struct HashJoinState *producer;
+ BloomFilter *filter;
+
+ int nkeys; /* number of hash keys */
+ ExprState *keys; /* ExprState for the hash calculation */
+
+ /* probe counters */
+ uint64 checked;
+ uint64 rejected;
+
+ /* adaptive probing */
+ uint64 adaptiveWindowProbes;
+ uint64 adaptiveWindowMatches;
+ uint64 adaptiveSampleCounter;
+ bool adaptiveSampling;
+} BloomFilterState;
+
/* ----------------
* these are defined to avoid confusion problems with "left"
* and "right" and "inner" and "outer". The convention is that
@@ -2703,6 +2761,39 @@ typedef struct HashState
Tuplestorestate *null_tuple_store; /* where to put null-keyed tuples */
bool keep_null_tuples; /* do we need to save such tuples? */
+ /*
+ * True iff at we managed to push down the bloom filter to at least one
+ * node on the HashJoin's outer side. It tells the Hash node to also build
+ * a bloom filter next to the hash table.
+ */
+ bool want_bloom_filter;
+
+ /*
+ * Mirror of the parent HashJoin's bloom_filter_id, copied here at
+ * ExecInitHashJoin time so EXPLAIN's show_hash_info can label the
+ * filter without traversing back up the plan-state tree (which is
+ * not easy, as there's no parent in PlanState).
+ */
+ int bloom_filter_id;
+
+ /*
+ * Bloom filter on hash values during the build phase. Probed by recipient
+ * nodes (typically scans in the outer subtree). NULL when pushdown is
+ * disabled or no recipient was identified, and (transiently) before
+ * ExecHashTableCreate runs (on the first outer tuple).
+ *
+ * Allocated in hashCxt, just like the hashtable itself. Reset on rescans.
+ */
+ struct bloom_filter *bloom_filter;
+
+ /*
+ * Counters with total per-filter instrumentation. Separate from the
+ * per-recipient counters in BloomFilterState. Redundant, but will be
+ * needed if we end up allowing multiple recipients.
+ */
+ uint64 bloomFilterChecked;
+ uint64 bloomFilterRejected;
+
/*
* In a parallelized hash join, the leader retains a pointer to the
* shared-memory stats area in its shared_info field, and then copies the
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c6815b7..69f9ad2d5e3 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -244,6 +244,9 @@ typedef struct PlannerGlobal
/* highest plan node ID assigned */
int lastPlanNodeId;
+ /* highest bloom-filter-producer ID assigned (see plannodes.h) */
+ int lastBloomFilterId;
+
/* redo plan when TransactionXmin changes? */
bool transientPlan;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 14a1dfed2b9..4e35d77cc49 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -254,8 +254,40 @@ typedef struct Plan
*/
Bitmapset *extParam;
Bitmapset *allParam;
+
+ /*
+ * List of BloomFilter nodes (or NIL). When non-empty, this plan node is
+ * a recipient of one or more runtime bloom filters pushed down at plan
+ * time by some HashJoin ancestor; see nodeHashjoin.c. Living on Plan
+ * (rather than on a specific subtype like Scan) allows pushdown to any
+ * node type that's prepared to call ExecBloomFilters on its output.
+ */
+ List *bloom_filters;
} Plan;
+/*
+ * BloomFilter -
+ * One pushed-down bloom filter, attached to a recipient Plan node.
+ *
+ * 'filter_exprs', 'hashops' and 'hashcollations' are parallel lists, one
+ * entry per join key: the expression to hash, its hash operator (OID),
+ * and its input collation (OID).
+ *
+ * 'producer_id' is the bloom_filter_id of the producing HashJoin (resolved at
+ * executor init time via EState.es_bloom_producers).
+ * ----------------
+ */
+typedef struct BloomFilter
+{
+ pg_node_attr(no_query_jumble)
+
+ NodeTag type;
+ List *filter_exprs;
+ List *hashops;
+ List *hashcollations;
+ int producer_id;
+} BloomFilter;
+
/* ----------------
* these are defined to avoid confusion problems with "left"
* and "right" and "inner" and "outer". The convention is that
@@ -1073,6 +1105,25 @@ typedef struct HashJoin
* perform lookups in the hashtable over the inner plan.
*/
List *hashkeys;
+
+ /*
+ * Number of plan nodes that consume this HashJoin's bloom filter via
+ * pushdown. Set at plan time by the bloom filter pushdown pass.
+ *
+ * At execution time, the HashJoin builds the bloom filter only when this
+ * is non-zero (and the enable_hashjoin_bloom GUC is on).
+ */
+ int bloom_consumer_count;
+
+ /*
+ * Identifier used by recipient nodes to find this producer at execution
+ * time, via EState.es_bloom_producers. Assigned during create_hashjoin_plan
+ * from PlannerGlobal.lastBloomFilterId. Each BloomFilter on a recipient stores
+ * a copy in its producer_id field for convenience.
+ *
+ * Zero when this HashJoin has no consumers.
+ */
+ int bloom_filter_id;
} HashJoin;
/* ----------------
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index f2fd5d31507..7339979c008 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -62,6 +62,7 @@ extern PGDLLIMPORT bool enable_material;
extern PGDLLIMPORT bool enable_memoize;
extern PGDLLIMPORT bool enable_mergejoin;
extern PGDLLIMPORT bool enable_hashjoin;
+extern PGDLLIMPORT bool enable_hashjoin_bloom;
extern PGDLLIMPORT bool enable_gathermerge;
extern PGDLLIMPORT bool enable_partitionwise_join;
extern PGDLLIMPORT bool enable_partitionwise_aggregate;
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 89e051ee824..a08d2556981 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1539,9 +1539,11 @@ group by t1.a,t1.b,t1.c,t1.d,t2.x,t2.y,t2.z;
-> Hash Join
Hash Cond: ((t2.x = t1.a) AND (t2.y = t1.b))
-> Seq Scan on t2
+ Bloom Filter 1: keys=(x, y)
-> Hash
+ Bloom Filter 1
-> Seq Scan on t1
-(7 rows)
+(9 rows)
-- Test case where t1 can be optimized but not t2
explain (costs off) select t1.*,t2.x,t2.z
@@ -1554,9 +1556,11 @@ group by t1.a,t1.b,t1.c,t1.d,t2.x,t2.z;
-> Hash Join
Hash Cond: ((t2.x = t1.a) AND (t2.y = t1.b))
-> Seq Scan on t2
+ Bloom Filter 1: keys=(x, y)
-> Hash
+ Bloom Filter 1
-> Seq Scan on t1
-(7 rows)
+(9 rows)
-- Cannot optimize when PK is deferrable
explain (costs off) select * from t3 group by a,b,c;
diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out
index 091ae48a92b..bbdb9526471 100644
--- a/src/test/regress/expected/eager_aggregate.out
+++ b/src/test/regress/expected/eager_aggregate.out
@@ -34,14 +34,16 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(18 rows)
+(20 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -80,8 +82,10 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
+ Bloom Filter 1
-> Partial GroupAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
@@ -90,7 +94,7 @@ GROUP BY t1.a ORDER BY t1.a;
Sort Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.c, t2.b
-(21 rows)
+(23 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -134,8 +138,10 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 2: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg((t2.c + t3.c)))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2.b, PARTIAL avg((t2.c + t3.c))
Group Key: t2.b
@@ -144,11 +150,13 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t3.a = t2.a)
-> Seq Scan on public.eager_agg_t3 t3
Output: t3.a, t3.b, t3.c
+ Bloom Filter 1: keys=(t3.a)
-> Hash
Output: t2.c, t2.b, t2.a
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.c, t2.b, t2.a
-(25 rows)
+(29 rows)
SELECT t1.a, avg(t2.c + t3.c)
FROM eager_agg_t1 t1
@@ -189,8 +197,10 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 2: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg((t2.c + t3.c)))
+ Bloom Filter 2
-> Partial GroupAggregate
Output: t2.b, PARTIAL avg((t2.c + t3.c))
Group Key: t2.b
@@ -202,11 +212,13 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t3.a = t2.a)
-> Seq Scan on public.eager_agg_t3 t3
Output: t3.a, t3.b, t3.c
+ Bloom Filter 1: keys=(t3.a)
-> Hash
Output: t2.c, t2.b, t2.a
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.c, t2.b, t2.a
-(28 rows)
+(32 rows)
SELECT t1.a, avg(t2.c + t3.c)
FROM eager_agg_t1 t1
@@ -249,14 +261,16 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(18 rows)
+(20 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -295,11 +309,13 @@ GROUP BY t2.b ORDER BY t2.b;
Hash Cond: (t2.b = t1.b)
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
+ Bloom Filter 1: keys=(t2.b)
-> Hash
Output: t1.b
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.b
-(15 rows)
+(17 rows)
SELECT t2.b, avg(t2.c)
FROM eager_agg_t1 t1
@@ -400,14 +416,16 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(18 rows)
+(20 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -444,9 +462,11 @@ GROUP BY t1.a ORDER BY t1.a;
-> Hash Join
Hash Cond: (t2.b = t1.b)
-> Seq Scan on eager_agg_t2 t2
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on eager_agg_t1 t1
-(9 rows)
+(11 rows)
EXPLAIN (COSTS OFF)
SELECT t1.a, avg(t2.c) FILTER (WHERE random() > 0.5)
@@ -462,9 +482,11 @@ GROUP BY t1.a ORDER BY t1.a;
-> Hash Join
Hash Cond: (t2.b = t1.b)
-> Seq Scan on eager_agg_t2 t2
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on eager_agg_t1 t1
-(9 rows)
+(11 rows)
-- Eager aggregation must not push a partial aggregate onto the inner side of a
-- SEMI or ANTI join
@@ -599,8 +621,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
+ Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -614,8 +638,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
+ Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -629,14 +655,16 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
+ Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x, t1_2.y
-(49 rows)
+(55 rows)
SELECT t1.x, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -681,8 +709,10 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
+ Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -696,8 +726,10 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
+ Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -711,14 +743,16 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
+ Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.y, t1_2.x
-(49 rows)
+(55 rows)
SELECT t2.y, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -765,8 +799,10 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.x, t2.y
+ Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.x)), (PARTIAL count(*)), (PARTIAL avg(t1.x))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.x), PARTIAL count(*), PARTIAL avg(t1.x)
Group Key: t1.x
@@ -777,8 +813,10 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.x, t2_1.y
+ Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.x)), (PARTIAL count(*)), (PARTIAL avg(t1_1.x))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.x), PARTIAL count(*), PARTIAL avg(t1_1.x)
Group Key: t1_1.x
@@ -789,14 +827,16 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.x, t2_2.y
+ Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.x)), (PARTIAL count(*)), (PARTIAL avg(t1_2.x))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.x), PARTIAL count(*), PARTIAL avg(t1_2.x)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x
-(44 rows)
+(50 rows)
SELECT t2.x, sum(t1.x), count(*)
FROM eager_agg_tab1 t1
@@ -838,8 +878,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab1_p1 t1
Output: t1.x
+ Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y)))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y))
Group Key: t2.x
@@ -848,8 +890,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab1_p1 t2
Output: t2.y, t2.x
+ Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_tab1_p1 t3
Output: t3.y, t3.x
-> Finalize HashAggregate
@@ -860,8 +904,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab1_p2 t1_1
Output: t1_1.x
+ Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y)))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y))
Group Key: t2_1.x
@@ -870,8 +916,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab1_p2 t2_1
Output: t2_1.y, t2_1.x
+ Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
+ Bloom Filter 3
-> Seq Scan on public.eager_agg_tab1_p2 t3_1
Output: t3_1.y, t3_1.x
-> Finalize HashAggregate
@@ -882,8 +930,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x
+ Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y)))
+ Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y))
Group Key: t2_2.x
@@ -892,11 +942,13 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab1_p3 t2_2
Output: t2_2.y, t2_2.x
+ Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
+ Bloom Filter 5
-> Seq Scan on public.eager_agg_tab1_p3 t3_2
Output: t3_2.y, t3_2.x
-(70 rows)
+(82 rows)
SELECT t1.x, sum(t2.y + t3.y)
FROM eager_agg_tab1 t1
@@ -1066,8 +1118,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
+ Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -1081,8 +1135,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
+ Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -1096,14 +1152,16 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
+ Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x, t1_2.y
-(49 rows)
+(55 rows)
SELECT t1.x, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -1166,8 +1224,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
+ Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1181,8 +1241,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
+ Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1196,8 +1258,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
+ Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1211,8 +1275,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
+ Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1226,14 +1292,16 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
+ Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
+ Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(79 rows)
+(89 rows)
SELECT t1.x, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1294,8 +1362,10 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.y, t1.x
+ Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1306,8 +1376,10 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.y, t1_1.x
+ Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1318,8 +1390,10 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.y, t1_2.x
+ Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1330,8 +1404,10 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.y, t1_3.x
+ Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1342,14 +1418,16 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.y, t1_4.x
+ Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
+ Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(67 rows)
+(77 rows)
SELECT t1.y, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1411,8 +1489,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
+ Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*)
Group Key: t2.x
@@ -1421,8 +1501,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t2
Output: t2.y, t2.x
+ Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_tab_ml_p1 t3
Output: t3.y, t3.x
-> Finalize HashAggregate
@@ -1433,8 +1515,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
+ Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*)
Group Key: t2_1.x
@@ -1443,8 +1527,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1
Output: t2_1.y, t2_1.x
+ Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
+ Bloom Filter 3
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1
Output: t3_1.y, t3_1.x
-> Finalize HashAggregate
@@ -1455,8 +1541,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
+ Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*))
+ Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*)
Group Key: t2_2.x
@@ -1465,8 +1553,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2
Output: t2_2.y, t2_2.x
+ Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
+ Bloom Filter 5
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2
Output: t3_2.y, t3_2.x
-> Finalize HashAggregate
@@ -1477,8 +1567,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
+ Bloom Filter 8: keys=(t1_3.x)
-> Hash
Output: t2_3.x, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*))
+ Bloom Filter 8
-> Partial HashAggregate
Output: t2_3.x, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*)
Group Key: t2_3.x
@@ -1487,8 +1579,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_3.x = t3_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3
Output: t2_3.y, t2_3.x
+ Bloom Filter 7: keys=(t2_3.x)
-> Hash
Output: t3_3.y, t3_3.x
+ Bloom Filter 7
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3
Output: t3_3.y, t3_3.x
-> Finalize HashAggregate
@@ -1499,8 +1593,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
+ Bloom Filter 10: keys=(t1_4.x)
-> Hash
Output: t2_4.x, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*))
+ Bloom Filter 10
-> Partial HashAggregate
Output: t2_4.x, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*)
Group Key: t2_4.x
@@ -1509,11 +1605,13 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_4.x = t3_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
+ Bloom Filter 9: keys=(t2_4.x)
-> Hash
Output: t3_4.y, t3_4.x
+ Bloom Filter 9
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4
Output: t3_4.y, t3_4.x
-(114 rows)
+(134 rows)
SELECT t1.x, sum(t2.y + t3.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1575,8 +1673,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
+ Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.y, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.y, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*)
Group Key: t2.x, t3.y, t3.x
@@ -1585,8 +1685,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t2
Output: t2.y, t2.x
+ Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_tab_ml_p1 t3
Output: t3.y, t3.x
-> Hash Join
@@ -1594,8 +1696,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
+ Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.y, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.y, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*)
Group Key: t2_1.x, t3_1.y, t3_1.x
@@ -1604,8 +1708,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1
Output: t2_1.y, t2_1.x
+ Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
+ Bloom Filter 3
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1
Output: t3_1.y, t3_1.x
-> Hash Join
@@ -1613,8 +1719,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
+ Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.y, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*))
+ Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.y, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*)
Group Key: t2_2.x, t3_2.y, t3_2.x
@@ -1623,8 +1731,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2
Output: t2_2.y, t2_2.x
+ Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
+ Bloom Filter 5
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2
Output: t3_2.y, t3_2.x
-> Hash Join
@@ -1632,8 +1742,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
+ Bloom Filter 8: keys=(t1_3.x)
-> Hash
Output: t2_3.x, t3_3.y, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*))
+ Bloom Filter 8
-> Partial HashAggregate
Output: t2_3.x, t3_3.y, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*)
Group Key: t2_3.x, t3_3.y, t3_3.x
@@ -1642,8 +1754,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_3.x = t3_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3
Output: t2_3.y, t2_3.x
+ Bloom Filter 7: keys=(t2_3.x)
-> Hash
Output: t3_3.y, t3_3.x
+ Bloom Filter 7
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3
Output: t3_3.y, t3_3.x
-> Hash Join
@@ -1651,8 +1765,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
+ Bloom Filter 10: keys=(t1_4.x)
-> Hash
Output: t2_4.x, t3_4.y, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*))
+ Bloom Filter 10
-> Partial HashAggregate
Output: t2_4.x, t3_4.y, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*)
Group Key: t2_4.x, t3_4.y, t3_4.x
@@ -1661,11 +1777,13 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_4.x = t3_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
+ Bloom Filter 9: keys=(t2_4.x)
-> Hash
Output: t3_4.y, t3_4.x
+ Bloom Filter 9
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4
Output: t3_4.y, t3_4.x
-(102 rows)
+(122 rows)
SELECT t3.y, sum(t2.y + t3.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1728,8 +1846,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
+ Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1743,8 +1863,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
+ Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1758,8 +1880,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
+ Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1773,8 +1897,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
+ Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1788,14 +1914,16 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
+ Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
+ Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(79 rows)
+(89 rows)
SELECT t1.x, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index ed946abed7f..4fccc7c4057 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1909,20 +1909,24 @@ select * from tenk1 a, tenk1 b
where exists(select * from tenk1 c
where b.twothousand = c.twothousand and b.fivethous <> c.fivethous)
and a.tenthous = b.tenthous and a.tenthous < 5000;
- QUERY PLAN
------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------
Hash Semi Join
Hash Cond: (b.twothousand = c.twothousand)
Join Filter: (b.fivethous <> c.fivethous)
-> Hash Join
Hash Cond: (b.tenthous = a.tenthous)
-> Seq Scan on tenk1 b
+ Bloom Filter 1: keys=(tenthous)
+ Bloom Filter 2: keys=(twothousand)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 a
Filter: (tenthous < 5000)
-> Hash
+ Bloom Filter 2
-> Seq Scan on tenk1 c
-(11 rows)
+(15 rows)
--
-- More complicated constructs
@@ -2378,9 +2382,11 @@ order by t1.unique1;
Hash Cond: ((t1.two = t2.two) AND (t1.unique1 = (SubPlan expr_1)))
-> Bitmap Heap Scan on tenk1 t1
Recheck Cond: (unique1 < 10)
+ Bloom Filter 1: keys=(two, unique1)
-> Bitmap Index Scan on tenk1_unique1
Index Cond: (unique1 < 10)
-> Hash
+ Bloom Filter 1
-> Bitmap Heap Scan on tenk1 t2
Recheck Cond: (unique1 < 10)
-> Bitmap Index Scan on tenk1_unique1
@@ -2392,7 +2398,7 @@ order by t1.unique1;
-> Limit
-> Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 IS NOT NULL) AND (unique1 = t2.unique1))
-(20 rows)
+(22 rows)
-- Ensure we get the expected result
select t1.unique1,t2.unique1 from tenk1 t1
@@ -2598,12 +2604,14 @@ select * from int4_tbl t1
Join Filter: (t2.f1 > 0)
Filter: (t3.f1 IS NULL)
-> Seq Scan on int4_tbl t2
+ Bloom Filter 1: keys=(f1)
-> Materialize
-> Seq Scan on int4_tbl t3
-> Seq Scan on tenk1 t4
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl t1
-(13 rows)
+(15 rows)
explain (costs off)
select * from int4_tbl t1
@@ -2622,13 +2630,15 @@ select * from int4_tbl t1
Join Filter: (t2.f1 > 0)
Filter: (t2.f1 <> COALESCE(t3.f1, '-1'::integer))
-> Seq Scan on int4_tbl t2
+ Bloom Filter 1: keys=(f1)
-> Materialize
-> Seq Scan on int4_tbl t3
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl t1
-> Materialize
-> Seq Scan on tenk1 t4
-(14 rows)
+(16 rows)
explain (costs off)
select * from onek t1
@@ -3122,10 +3132,12 @@ select count(*) from tenk1 a, tenk1 b
-> Hash Join
Hash Cond: (a.hundred = b.thousand)
-> Index Only Scan using tenk1_hundred on tenk1 a
+ Bloom Filter 1: keys=(hundred)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 b
Filter: ((fivethous % 10) < 10)
-(7 rows)
+(9 rows)
select count(*) from tenk1 a, tenk1 b
where a.hundred = b.thousand and (b.fivethous % 10) < 10;
@@ -3168,11 +3180,13 @@ ORDER BY 1;
Hash Cond: (b.f1 = c.f1)
Filter: (COALESCE(c.f1, 0) = 0)
-> Seq Scan on tt3 b
+ Bloom Filter 1: keys=(f1)
-> Hash
-> Seq Scan on tt3 c
-> Hash
+ Bloom Filter 1
-> Seq Scan on tt4 a
-(13 rows)
+(15 rows)
SELECT a.f1
FROM tt4 a
@@ -3210,10 +3224,12 @@ where t1.filt = 5;
Hash Join
Hash Cond: (t2.val = t1.val)
-> Seq Scan on skewedtable t2
+ Bloom Filter 1: keys=(val)
-> Hash
+ Bloom Filter 1
-> Seq Scan on skewedtable t1
Filter: (filt = 5)
-(6 rows)
+(8 rows)
drop table skewedtable;
--
@@ -3227,9 +3243,11 @@ where unique1 in (select unique2 from tenk1 b);
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(5 rows)
+(7 rows)
-- sadly, this is not an antijoin
explain (costs off)
@@ -3251,9 +3269,11 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2);
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(5 rows)
+(7 rows)
explain (costs off)
select a.* from tenk1 a
@@ -3290,11 +3310,13 @@ where (hundred, thousand) in (select twothousand, twothousand from onek);
Hash Cond: (tenk1.hundred = onek.twothousand)
-> Seq Scan on tenk1
Filter: (hundred = thousand)
+ Bloom Filter 1: keys=(hundred)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: onek.twothousand
-> Seq Scan on onek
-(8 rows)
+(10 rows)
reset enable_memoize;
--
@@ -3311,17 +3333,19 @@ where t2.a is null;
Hash Right Anti Join
Hash Cond: (t2.b = t1.unique1)
-> Seq Scan on tbl_anti t2
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 t1
-(5 rows)
+(7 rows)
-- this is an antijoin, as t2.a is non-null for any matching row
explain (costs off)
select * from tenk1 t1 left join
(tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b
where t2.a is null;
- QUERY PLAN
--------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Right Anti Join
Hash Cond: (t2.b = t1.unique1)
-> Merge Left Join
@@ -3329,20 +3353,22 @@ where t2.a is null;
-> Sort
Sort Key: t2.c
-> Seq Scan on tbl_anti t2
+ Bloom Filter 1: keys=(b)
-> Sort
Sort Key: t3.c
-> Seq Scan on tbl_anti t3
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 t1
-(12 rows)
+(14 rows)
-- this is not an antijoin, as t3.a can be nulled by t2/t3 join
explain (costs off)
select * from tenk1 t1 left join
(tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b
where t3.a is null;
- QUERY PLAN
--------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Right Join
Hash Cond: (t2.b = t1.unique1)
Filter: (t3.a IS NULL)
@@ -3351,12 +3377,14 @@ where t3.a is null;
-> Sort
Sort Key: t2.c
-> Seq Scan on tbl_anti t2
+ Bloom Filter 1: keys=(b)
-> Sort
Sort Key: t3.c
-> Seq Scan on tbl_anti t3
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 t1
-(13 rows)
+(15 rows)
rollback;
--
@@ -3370,9 +3398,11 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2 group by b.uniqu
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(5 rows)
+(7 rows)
--
-- regression test for proper handling of outer joins within antijoins
@@ -3989,11 +4019,13 @@ where q1 = thousand or q2 = thousand;
-> Seq Scan on q2
-> Bitmap Heap Scan on tenk1
Recheck Cond: ((q1.q1 = thousand) OR (q2.q2 = thousand))
+ Bloom Filter 1: keys=(twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = ANY (ARRAY[q1.q1, q2.q2]))
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl
-(12 rows)
+(14 rows)
explain (costs off)
select * from
@@ -4010,11 +4042,13 @@ where thousand = (q1 + q2);
-> Seq Scan on q2
-> Bitmap Heap Scan on tenk1
Recheck Cond: (thousand = (q1.q1 + q2.q2))
+ Bloom Filter 1: keys=(twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = (q1.q1 + q2.q2))
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl
-(12 rows)
+(14 rows)
--
-- test ability to generate a suitable plan for a star-schema query
@@ -4120,8 +4154,10 @@ where t1.unique1 < i4.f1;
Hash Cond: (t2.ten = t1.tenthous)
-> Seq Scan on public.tenk1 t2
Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4
+ Bloom Filter 1: keys=(t2.ten)
-> Hash
Output: t1.tenthous, t1.unique1
+ Bloom Filter 1
-> Nested Loop
Output: t1.tenthous, t1.unique1
-> Subquery Scan on ss0
@@ -4137,7 +4173,7 @@ where t1.unique1 < i4.f1;
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
Filter: (i8.q1 = ((64)::information_schema.cardinal_number)::integer)
-(33 rows)
+(35 rows)
select ss1.d1 from
tenk1 as t1
@@ -5234,6 +5270,7 @@ order by i0.f1, x;
Output: i1.f1, i2.q1, i2.q2, '123'::bigint
-> Seq Scan on public.int4_tbl i1
Output: i1.f1
+ Bloom Filter 1: keys=(i1.f1)
-> Materialize
Output: i2.q1, i2.q2
-> Seq Scan on public.int8_tbl i2
@@ -5241,9 +5278,10 @@ order by i0.f1, x;
Filter: (123 = i2.q2)
-> Hash
Output: i0.f1
+ Bloom Filter 1
-> Seq Scan on public.int4_tbl i0
Output: i0.f1
-(19 rows)
+(21 rows)
select * from
int4_tbl i0 left join
@@ -5297,8 +5335,10 @@ select t1.* from
Hash Cond: (i8.q1 = i8b2.q1)
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
+ Bloom Filter 1: keys=(i8.q1)
-> Hash
Output: i8b2.q1, (NULL::integer)
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, NULL::integer
-> Hash
@@ -5309,7 +5349,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(30 rows)
+(32 rows)
select t1.* from
text_tbl t1
@@ -5360,10 +5400,12 @@ select t1.* from
Output: i8b2.q1, NULL::integer
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, i8b2.q2
+ Bloom Filter 1: keys=(i8b2.q1)
-> Materialize
-> Seq Scan on public.int4_tbl i4b2
-> Hash
Output: i8.q1, i8.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Hash
@@ -5374,7 +5416,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(34 rows)
+(36 rows)
select t1.* from
text_tbl t1
@@ -5427,12 +5469,16 @@ select t1.* from
Hash Cond: (i8b2.q1 = i4b2.f1)
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, i8b2.q2
+ Bloom Filter 1: keys=(i8b2.q1)
+ Bloom Filter 2: keys=(i8b2.q1)
-> Hash
Output: i4b2.f1
+ Bloom Filter 1
-> Seq Scan on public.int4_tbl i4b2
Output: i4b2.f1
-> Hash
Output: i8.q1, i8.q2
+ Bloom Filter 2
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Hash
@@ -5443,7 +5489,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(37 rows)
+(41 rows)
select t1.* from
text_tbl t1
@@ -5794,15 +5840,17 @@ where ss1.c2 = 0;
Filter: (i43.f1 = 0)
-> Seq Scan on public.int4_tbl i41
Output: i41.f1
+ Bloom Filter 1: keys=(i41.f1)
-> Hash
Output: i42.f1
+ Bloom Filter 1
-> Seq Scan on public.int4_tbl i42
Output: i42.f1
-> Limit
Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42))
-> Seq Scan on public.text_tbl
Output: i41.f1, i8.q1, i8.q2, i42.f1, i43.f1, (42)
-(25 rows)
+(27 rows)
select ss2.* from
int4_tbl i41
@@ -5934,12 +5982,14 @@ select a.unique1, b.unique2
Hash Cond: (b.unique2 = a.unique1)
-> Seq Scan on onek b
Filter: (ANY ((unique2 = (SubPlan any_1).col1) AND ((random() > '0'::double precision) = (SubPlan any_1).col2)))
+ Bloom Filter 1: keys=(unique2)
SubPlan any_1
-> Seq Scan on int8_tbl c
Filter: (q1 < b.unique1)
-> Hash
+ Bloom Filter 1
-> Index Only Scan using onek_unique1 on onek a
-(9 rows)
+(11 rows)
select a.unique1, b.unique2
from onek a left join onek b on a.unique1 = b.unique2
@@ -6092,14 +6142,16 @@ explain (costs off)
select id from a where id in (
select b.id from b left join c on b.id = c.id
);
- QUERY PLAN
-----------------------------
+ QUERY PLAN
+-----------------------------------
Hash Join
Hash Cond: (a.id = b.id)
-> Seq Scan on a
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on b
-(5 rows)
+(7 rows)
-- check optimization with oddly-nested outer joins
explain (costs off)
@@ -6522,16 +6574,18 @@ explain (costs off)
select c.id, ss.a from c
left join (select d.a from onerow, d left join b on d.a = b.id) ss
on c.id = ss.a;
- QUERY PLAN
---------------------------------
+ QUERY PLAN
+----------------------------------------
Hash Right Join
Hash Cond: (d.a = c.id)
-> Nested Loop
-> Seq Scan on onerow
-> Seq Scan on d
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on c
-(7 rows)
+(9 rows)
-- check the case when the placeholder relates to an outer join and its
-- inner in the press field but actually uses only the outer side of the join
@@ -6883,23 +6937,27 @@ where exists (select 1 from t t4
Hash Cond: (t6.b = t4.b)
-> Seq Scan on pg_temp.t t6
Output: t6.a, t6.b
+ Bloom Filter 2: keys=(t6.b)
-> Hash
Output: t4.b, t5.b, t5.a
+ Bloom Filter 2
-> Hash Join
Output: t4.b, t5.b, t5.a
Inner Unique: true
Hash Cond: (t5.b = t4.b)
-> Seq Scan on pg_temp.t t5
Output: t5.a, t5.b
+ Bloom Filter 1: keys=(t5.b)
-> Hash
Output: t4.b, t4.a
+ Bloom Filter 1
-> Index Scan using t_a_key on pg_temp.t t4
Output: t4.b, t4.a
Index Cond: (t4.a = 1)
-> Index Only Scan using t_a_key on pg_temp.t t3
Output: t3.a
Index Cond: (t3.a = t5.a)
-(32 rows)
+(36 rows)
select t1.a from t t1
left join t t2 on t1.a = t2.a
@@ -9085,13 +9143,15 @@ select * from
Output: b.q1, COALESCE(b.q2, '42'::bigint)
-> Seq Scan on public.int8_tbl d
Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)
+ Bloom Filter 1: keys=(d.q1)
-> Hash
Output: c.q1, c.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Result
Output: (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))
-(24 rows)
+(26 rows)
-- another case requiring nested PlaceHolderVars
explain (verbose, costs off)
@@ -9150,25 +9210,29 @@ select c.*,a.*,ss1.q1,ss2.q1,ss3.* from
Join Filter: (b.q1 < b2.f1)
-> Seq Scan on public.int8_tbl b
Output: b.q1, b.q2
+ Bloom Filter 1: keys=(b.q1)
-> Materialize
Output: b2.f1
-> Seq Scan on public.int4_tbl b2
Output: b2.f1
-> Hash
Output: a.q1, a.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl a
Output: a.q1, a.q2
-> Seq Scan on public.int8_tbl d
Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
+ Bloom Filter 2: keys=(d.q1)
-> Hash
Output: c.q1, c.q2
+ Bloom Filter 2
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Materialize
Output: i.f1
-> Seq Scan on public.int4_tbl i
Output: i.f1
-(34 rows)
+(38 rows)
-- check processing of postponed quals (bug #9041)
explain (verbose, costs off)
@@ -9475,8 +9539,10 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral
Hash Cond: (t3.b = t2.a)
-> Seq Scan on public.join_ut1 t3
Output: t3.a, t3.b, t3.c
+ Bloom Filter 1: keys=(t3.b)
-> Hash
Output: t2.a
+ Bloom Filter 1
-> Append
-> Seq Scan on public.join_pt1p1p1 t2_1
Output: t2_1.a
@@ -9484,7 +9550,7 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral
-> Seq Scan on public.join_pt1p2 t2_2
Output: t2_2.a
Filter: (t1.a = t2_2.a)
-(21 rows)
+(23 rows)
select t1.b, ss.phv from join_ut1 t1 left join lateral
(select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv
@@ -9518,12 +9584,14 @@ select * from fkest f1
Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10))
-> Seq Scan on fkest f2
Filter: (x100 = 2)
+ Bloom Filter 1: keys=(x, x10b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on fkest f1
Filter: (x100 = 2)
-> Index Scan using fkest_x_x10_x100_idx on fkest f3
Index Cond: (x = f1.x)
-(10 rows)
+(12 rows)
alter table fkest add constraint fk
foreign key (x, x10b, x100) references fkest (x, x10, x100);
@@ -9539,13 +9607,15 @@ select * from fkest f1
-> Hash Join
Hash Cond: (f3.x = f2.x)
-> Seq Scan on fkest f3
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on fkest f2
Filter: (x100 = 2)
-> Hash
-> Seq Scan on fkest f1
Filter: (x100 = 2)
-(11 rows)
+(13 rows)
rollback;
--
@@ -9598,19 +9668,21 @@ analyze j3;
-- ensure join is properly marked as unique
explain (verbose, costs off)
select * from j1 inner join j2 on j1.id = j2.id;
- QUERY PLAN
------------------------------------
+ QUERY PLAN
+--------------------------------------
Hash Join
Output: j1.id, j2.id
Inner Unique: true
Hash Cond: (j1.id = j2.id)
-> Seq Scan on public.j1
Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
-> Hash
Output: j2.id
+ Bloom Filter 1
-> Seq Scan on public.j2
Output: j2.id
-(10 rows)
+(12 rows)
-- ensure join is not unique when not an equi-join
explain (verbose, costs off)
@@ -9631,19 +9703,21 @@ 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
Inner Unique: true
Hash Cond: (j3.id = j1.id)
-> Seq Scan on public.j3
Output: j3.id
+ Bloom Filter 1: keys=(j3.id)
-> Hash
Output: j1.id
+ Bloom Filter 1
-> Seq Scan on public.j1
Output: j1.id
-(10 rows)
+(12 rows)
-- ensure left join is marked as unique
explain (verbose, costs off)
@@ -9714,19 +9788,21 @@ select * from j1 cross join j2;
-- ensure a natural join is marked as unique
explain (verbose, costs off)
select * from j1 natural join j2;
- QUERY PLAN
------------------------------------
+ QUERY PLAN
+--------------------------------------
Hash Join
Output: j1.id
Inner Unique: true
Hash Cond: (j1.id = j2.id)
-> Seq Scan on public.j1
Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
-> Hash
Output: j2.id
+ Bloom Filter 1
-> Seq Scan on public.j2
Output: j2.id
-(10 rows)
+(12 rows)
-- ensure a distinct clause allows the inner to become unique
explain (verbose, costs off)
diff --git a/src/test/regress/expected/join_hash.out b/src/test/regress/expected/join_hash.out
index 75009e29720..0a8ade8b961 100644
--- a/src/test/regress/expected/join_hash.out
+++ b/src/test/regress/expected/join_hash.out
@@ -90,15 +90,17 @@ set local work_mem = '4MB';
set local hash_mem_multiplier = 1.0;
explain (costs off)
select count(*) from simple r join simple s using (id);
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+-----------------------------------------
Aggregate
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on simple s
-(6 rows)
+(8 rows)
select count(*) from simple r join simple s using (id);
count
@@ -203,15 +205,17 @@ set local work_mem = '128kB';
set local hash_mem_multiplier = 1.0;
explain (costs off)
select count(*) from simple r join simple s using (id);
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+-----------------------------------------
Aggregate
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on simple s
-(6 rows)
+(8 rows)
select count(*) from simple r join simple s using (id);
count
@@ -330,9 +334,11 @@ explain (costs off)
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on bigger_than_it_looks s
-(6 rows)
+(8 rows)
select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);
count
@@ -445,9 +451,11 @@ explain (costs off)
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on extremely_skewed s
-(6 rows)
+(8 rows)
select count(*) from simple r join extremely_skewed s using (id);
count
@@ -1149,9 +1157,11 @@ lateral (select t1.fivethous, i4.f1 from tenk1 t1 join int4_tbl i4
-> Hash Join
Hash Cond: (t1.fivethous = (i4.f1 + i8.q2))
-> Seq Scan on tenk1 t1
+ Bloom Filter 1: keys=(fivethous)
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl i4
-(9 rows)
+(11 rows)
select i8.q2, ss.* from
int8_tbl i8,
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index 9cb1d87066a..c5aa11cd249 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -323,15 +323,17 @@ USING source AS s
ON t.tid = s.sid
WHEN MATCHED THEN
UPDATE SET balance = 0;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+------------------------------------------
Merge on target t
-> Hash Join
Hash Cond: (s.sid = t.tid)
-> Seq Scan on source s
+ Bloom Filter 1: keys=(sid)
-> Hash
+ Bloom Filter 1
-> Seq Scan on target t
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF)
MERGE INTO target t
@@ -339,15 +341,17 @@ USING source AS s
ON t.tid = s.sid
WHEN MATCHED THEN
DELETE;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+------------------------------------------
Merge on target t
-> Hash Join
Hash Cond: (s.sid = t.tid)
-> Seq Scan on source s
+ Bloom Filter 1: keys=(sid)
-> Hash
+ Bloom Filter 1
-> Seq Scan on target t
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF)
MERGE INTO target t
@@ -1831,8 +1835,10 @@ WHEN MATCHED AND t.c > s.cnt THEN
Join Filter: (t.b < (SubPlan expr_1))
-> Seq Scan on public.tgt t
Output: t.ctid, t.a, t.b
+ Bloom Filter 1: keys=(t.a)
-> Hash
Output: s.a, s.b, s.c, s.d, s.ctid
+ Bloom Filter 1
-> Seq Scan on public.src s
Output: s.a, s.b, s.c, s.d, s.ctid
SubPlan expr_1
@@ -1856,7 +1862,7 @@ WHEN MATCHED AND t.c > s.cnt THEN
-> Seq Scan on public.ref r_1
Output: r_1.ab, r_1.cd
Filter: ((r_1.ab = (s.a + s.b)) AND (r_1.cd = (s.c - s.d)))
-(32 rows)
+(34 rows)
DROP TABLE src, tgt, ref;
-- Subqueries
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index c3261bff209..b52528870ef 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -582,10 +582,12 @@ WHERE my_int_eq(a.unique2, 42);
Hash Join
Hash Cond: (b.unique1 = a.unique1)
-> Seq Scan on tenk1 b
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 a
Filter: my_int_eq(unique2, 42)
-(6 rows)
+(8 rows)
-- With support function that knows it's int4eq, we get a different plan
CREATE FUNCTION test_support_func(internal)
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index c30304b99c7..be56036461b 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -460,23 +460,29 @@ SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> HashAggregate
Group Key: t1_1.x
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
+ Bloom Filter 2: keys=(x)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> HashAggregate
Group Key: t1_2.x
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
+ Bloom Filter 3: keys=(y)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(24 rows)
+(30 rows)
SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3;
x | sum | count
@@ -533,23 +539,29 @@ SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> HashAggregate
Group Key: t2_1.y
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
+ Bloom Filter 2: keys=(x)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> HashAggregate
Group Key: t2_2.y
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
+ Bloom Filter 3: keys=(y)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(24 rows)
+(30 rows)
-- When GROUP BY clause does not match; partial aggregation is performed for each partition.
-- Also test GroupAggregate paths by disabling hash aggregates.
@@ -572,7 +584,9 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> Partial GroupAggregate
Group Key: t1_1.y
@@ -581,7 +595,9 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
+ Bloom Filter 2: keys=(x)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> Partial GroupAggregate
Group Key: t1_2.y
@@ -590,9 +606,11 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
+ Bloom Filter 3: keys=(y)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(34 rows)
+(40 rows)
SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3;
y | sum | count
@@ -638,9 +656,11 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP B
-> Hash Right Join
Hash Cond: (b_2.y = a_2.x)
-> Seq Scan on pagg_tab2_p3 b_2
+ Bloom Filter 1: keys=(y)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab1_p3 a_2
-(26 rows)
+(28 rows)
SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
y | sum
@@ -667,14 +687,18 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
-> Hash Right Join
Hash Cond: (a.x = b.y)
-> Seq Scan on pagg_tab1_p1 a
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 b
-> HashAggregate
Group Key: b_1.y
-> Hash Right Join
Hash Cond: (a_1.x = b_1.y)
-> Seq Scan on pagg_tab1_p2 a_1
+ Bloom Filter 2: keys=(x)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 b_1
-> HashAggregate
Group Key: b_2.y
@@ -683,7 +707,7 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
-> Seq Scan on pagg_tab2_p3 b_2
-> Hash
-> Seq Scan on pagg_tab1_p3 a_2
-(24 rows)
+(28 rows)
SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
y | sum
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 38643d41fd7..1906b3641a3 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -36,22 +36,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -77,22 +83,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a =
-> Hash Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = b)
-> Hash Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = b)
-> Hash Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_p3 t2_3
Filter: (a = b)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a = t2.b ORDER BY t1.a, t2.b;
a | c | b | c
@@ -202,13 +214,17 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE
-> Hash Right Join
Hash Cond: (t1_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = 0)
-> Hash Right Join
Hash Cond: (t1_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Left Join
@@ -216,7 +232,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE
Filter: (a = 0)
-> Index Scan using iprt1_p3_a on prt1_p3 t1_3
Index Cond: (a = t2_3.b)
-(20 rows)
+(24 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -283,10 +299,12 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a <
Hash Cond: (t2.b = t1.a)
-> Seq Scan on prt2_p2 t2
Filter: (b > 250)
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p2 t1
Filter: ((a < 450) AND (b = 0))
-(9 rows)
+(11 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -382,14 +400,18 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
Hash Cond: (t1_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = 0)
-> Hash Semi Join
Hash Cond: (t1_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Semi Join
@@ -399,7 +421,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
-> Materialize
-> Seq Scan on prt2_p3 t2_3
Filter: (a = 0)
-(24 rows)
+(28 rows)
SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -515,19 +537,25 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
-> Hash Join
Hash Cond: (t2_1.a = t3_1.b)
-> Seq Scan on prt1_p1 t2_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t3_1
-> Hash Join
Hash Cond: (t2_2.a = t3_2.b)
-> Seq Scan on prt1_p2 t2_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t3_2
-> Hash Join
Hash Cond: (t2_3.a = t3_3.b)
-> Seq Scan on prt1_p3 t2_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_p3 t3_3
-(26 rows)
+(32 rows)
SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
@@ -728,29 +756,41 @@ SELECT * FROM prt1 t1 JOIN prt1 t2 ON t1.a = t2.a WHERE t1.a IN (SELECT a FROM p
-> Hash Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t2_1
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p1 t3_1
-> Hash Semi Join
Hash Cond: (t1_2.a = t3_2.a)
-> Hash Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 3: keys=(a)
+ Bloom Filter 4: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p2 t2_2
-> Hash
+ Bloom Filter 4
-> Seq Scan on prt1_p2 t3_2
-> Hash Semi Join
Hash Cond: (t1_3.a = t3_3.a)
-> Hash Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
+ Bloom Filter 5: keys=(a)
+ Bloom Filter 6: keys=(a)
-> Hash
+ Bloom Filter 5
-> Seq Scan on prt1_p3 t2_3
-> Hash
+ Bloom Filter 6
-> Seq Scan on prt1_p3 t3_3
-(28 rows)
+(40 rows)
--
-- partitioned by expression
@@ -821,7 +861,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3_1
@@ -831,7 +873,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2
@@ -841,12 +885,14 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3
Index Cond: (((a + b) / 2) = t2_3.b)
-(33 rows)
+(39 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c | ?column? | c
@@ -871,7 +917,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
@@ -881,7 +929,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
@@ -891,10 +941,12 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(33 rows)
+(39 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
a | c | b | c | ?column? | c
@@ -924,7 +976,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_1.a = ((t3_1.a + t3_1.b) / 2))
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_e_p1 t3_1
Filter: (c = 0)
-> Index Scan using iprt2_p1_b on prt2_p1 t2_1
@@ -933,7 +987,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_2.a = ((t3_2.a + t3_2.b) / 2))
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_e_p2 t3_2
Filter: (c = 0)
-> Index Scan using iprt2_p2_b on prt2_p2 t2_2
@@ -942,12 +998,14 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_3.a = ((t3_3.a + t3_3.b) / 2))
-> Seq Scan on prt1_p3 t1_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_e_p3 t3_3
Filter: (c = 0)
-> Index Scan using iprt2_p3_b on prt2_p3 t2_3
Index Cond: (b = t1_3.a)
-(30 rows)
+(36 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
a | c | b | c | ?column? | c
@@ -1205,7 +1263,9 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_6.b = ((t1_9.a + t1_9.b) / 2))
-> Seq Scan on prt2_p1 t1_6
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_e_p1 t1_9
Filter: (c = 0)
-> Index Scan using iprt1_p1_a on prt1_p1 t1_3
@@ -1218,7 +1278,9 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_7.b = ((t1_10.a + t1_10.b) / 2))
-> Seq Scan on prt2_p2 t1_7
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_e_p2 t1_10
Filter: (c = 0)
-> Index Scan using iprt1_p2_a on prt1_p2 t1_4
@@ -1231,13 +1293,15 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2))
-> Seq Scan on prt2_p3 t1_8
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_e_p3 t1_11
Filter: (c = 0)
-> Index Scan using iprt1_p3_a on prt1_p3 t1_5
Index Cond: (a = t1_8.b)
Filter: (b = 0)
-(41 rows)
+(47 rows)
SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -1567,29 +1631,41 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, pl
-> Hash Join
Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on plt1_p1 t1_1
+ Bloom Filter 1: keys=(b, c)
+ Bloom Filter 2: keys=(c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt2_p1 t2_1
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_e_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on plt1_p2 t1_2
+ Bloom Filter 3: keys=(b, c)
+ Bloom Filter 4: keys=(c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt2_p2 t2_2
-> Hash
+ Bloom Filter 4
-> Seq Scan on plt1_e_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
-> Seq Scan on plt1_p3 t1_3
+ Bloom Filter 5: keys=(b, c)
+ Bloom Filter 6: keys=(c)
-> Hash
+ Bloom Filter 5
-> Seq Scan on plt2_p3 t2_3
-> Hash
+ Bloom Filter 6
-> Seq Scan on plt1_e_p3 t3_3
-(32 rows)
+(44 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
avg | avg | avg | c | c | c
@@ -1637,23 +1713,29 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1
-> Hash Join
Hash Cond: (t3_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t3_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
-> Hash Join
Hash Cond: (t3_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t3_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
-> Hash Join
Hash Cond: (t3_3.a = t2_3.b)
-> Seq Scan on prt1_p3 t3_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_p3 t2_3
-> Hash
-> Result
Replaces: Scan on prt1
One-Time Filter: false
-(22 rows)
+(28 rows)
EXPLAIN (COSTS OFF)
SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
@@ -1715,29 +1797,41 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, ph
-> Hash Join
Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on pht1_p1 t1_1
+ Bloom Filter 1: keys=(b, c)
+ Bloom Filter 2: keys=(c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pht2_p1 t2_1
-> Hash
+ Bloom Filter 2
-> Seq Scan on pht1_e_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on pht1_p2 t1_2
+ Bloom Filter 3: keys=(b, c)
+ Bloom Filter 4: keys=(c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pht2_p2 t2_2
-> Hash
+ Bloom Filter 4
-> Seq Scan on pht1_e_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
-> Seq Scan on pht1_p3 t1_3
+ Bloom Filter 5: keys=(b, c)
+ Bloom Filter 6: keys=(c)
-> Hash
+ Bloom Filter 5
-> Seq Scan on pht2_p3 t2_3
-> Hash
+ Bloom Filter 6
-> Seq Scan on pht1_e_p3 t3_3
-(32 rows)
+(44 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
avg | avg | avg | c | c | c
@@ -1767,22 +1861,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
-- test default partition behavior for list
ALTER TABLE plt1 DETACH PARTITION plt1_p3;
@@ -1803,22 +1903,28 @@ SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c
-> Hash Join
Hash Cond: (t2_1.c = t1_1.c)
-> Seq Scan on plt2_p1 t2_1
+ Bloom Filter 1: keys=(c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_p1 t1_1
Filter: ((a % 25) = 0)
-> Hash Join
Hash Cond: (t2_2.c = t1_2.c)
-> Seq Scan on plt2_p2 t2_2
+ Bloom Filter 2: keys=(c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_p2 t1_2
Filter: ((a % 25) = 0)
-> Hash Join
Hash Cond: (t2_3.c = t1_3.c)
-> Seq Scan on plt2_p3 t2_3
+ Bloom Filter 3: keys=(c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_p3 t1_3
Filter: ((a % 25) = 0)
-(23 rows)
+(29 rows)
--
-- multiple levels of partitioning
@@ -1854,7 +1960,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_l_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_l_p1 t1_1
Filter: (b = 0)
-> Hash Join
@@ -1876,7 +1984,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Hash
-> Seq Scan on prt1_l_p3_p1 t1_5
Filter: (b = 0)
-(28 rows)
+(30 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2355,19 +2463,25 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a
-> Hash Join
Hash Cond: (t1_1.a = t3_1.b)
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.a = t3_2.b)
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.a = t3_3.b)
-> Seq Scan on prt1_p3 t1_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_p3 t3_3
-(23 rows)
+(29 rows)
-- partitionwise join can not be applied if there are no equi-join conditions
-- between partition keys
@@ -2639,22 +2753,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2680,22 +2800,28 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2721,22 +2847,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2766,22 +2898,28 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2848,22 +2986,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2889,22 +3033,28 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2930,22 +3080,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3001,22 +3157,28 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -3102,24 +3264,32 @@ SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2
-> Hash Right Join
Hash Cond: (t2_2.a = t1_2.b)
-> Seq Scan on prt1_adv_p2 t2_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Hash Join
Hash Cond: (t3_2.a = t1_2.b)
-> Seq Scan on prt1_adv_p2 t3_2
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_adv_p2 t1_2
Filter: (a = 0)
-> Hash Right Join
Hash Cond: (t2_3.a = t1_3.b)
-> Seq Scan on prt1_adv_p3 t2_3
+ Bloom Filter 4: keys=(a)
-> Hash
+ Bloom Filter 4
-> Hash Join
Hash Cond: (t3_3.a = t1_3.b)
-> Seq Scan on prt1_adv_p3 t3_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_adv_p3 t1_3
Filter: (a = 0)
-(31 rows)
+(39 rows)
SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a;
b | c | a | c | a | c
@@ -3289,16 +3459,20 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_2.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_1.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_1
Filter: (b = 0)
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3390,24 +3564,32 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2
-> Hash Right Join
Hash Cond: (t3_1.a = t1_1.a)
-> Seq Scan on prt3_adv_p1 t3_1
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Hash Right Join
Hash Cond: (t2_2.b = t1_1.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p2 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t3_2.a = t1_2.a)
-> Seq Scan on prt3_adv_p2 t3_2
+ Bloom Filter 4: keys=(a)
-> Hash
+ Bloom Filter 4
-> Hash Right Join
Hash Cond: (t2_1.b = t1_2.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p1 t1_2
Filter: (b = 0)
-(23 rows)
+(31 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a;
a | c | b | c | a | c
@@ -3449,16 +3631,20 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: ((a < 300) AND (b = 0))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: ((a < 300) AND (b = 0))
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3488,16 +3674,20 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3538,22 +3728,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3575,22 +3771,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3612,22 +3814,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3651,22 +3859,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3731,22 +3945,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3768,22 +3988,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3805,22 +4031,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3870,22 +4102,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4098,22 +4336,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4135,22 +4379,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4172,22 +4422,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4212,22 +4468,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4303,22 +4565,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4394,22 +4662,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4431,19 +4705,25 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-> Nested Loop Left Join
@@ -4451,7 +4731,7 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Seq Scan on plt1_adv_extra t1_4
Filter: (b < 10)
-> Seq Scan on plt2_adv_extra t2_4
-(26 rows)
+(32 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4525,31 +4805,43 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
-> Hash Right Join
Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
-> Seq Scan on plt1_adv_p1 t3_1
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
-> Seq Scan on plt1_adv_p2 t3_2
+ Bloom Filter 4: keys=(a, c)
-> Hash
+ Bloom Filter 4
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c))
-> Seq Scan on plt1_adv_p3 t3_3
+ Bloom Filter 6: keys=(a, c)
-> Hash
+ Bloom Filter 6
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 5: keys=(a, c)
-> Hash
+ Bloom Filter 5
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-> Nested Loop Left Join
@@ -4560,7 +4852,7 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
Filter: (b < 10)
-> Seq Scan on plt2_adv_extra t2_4
-> Seq Scan on plt1_adv_extra t3_4
-(41 rows)
+(53 rows)
SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c | a | c
@@ -4596,16 +4888,20 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4687,24 +4983,32 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
-> Hash Right Join
Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
-> Seq Scan on plt3_adv_p1 t3_1
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
-> Seq Scan on plt3_adv_p2 t3_2
+ Bloom Filter 4: keys=(a, c)
-> Hash
+ Bloom Filter 4
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-(23 rows)
+(31 rows)
SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c | a | c
@@ -4733,16 +5037,20 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1_null t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4767,10 +5075,12 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p2 t2
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p2 t1
Filter: (b < 10)
-(8 rows)
+(10 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4809,16 +5119,20 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p3 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p3 t1_1
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p4 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p4 t1_2
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4837,10 +5151,12 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p4 t2
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p4 t1
Filter: ((c IS NULL) AND (b < 10))
-(8 rows)
+(10 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4862,16 +5178,20 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p3 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p3 t1_1
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p4 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p4 t1_2
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4890,10 +5210,12 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p4 t2
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p4 t1
Filter: ((c IS NULL) AND (b < 10))
-(8 rows)
+(10 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5015,12 +5337,16 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b))
-> Seq Scan on alpha_neg_p1 t1_1
Filter: ((b >= 125) AND (b < 225))
+ Bloom Filter 1: keys=(a, b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on beta_neg_p1 t2_1
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-> Seq Scan on beta_neg_p2 t2_2
+ Bloom Filter 2: keys=(a, b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on alpha_neg_p2 t1_2
Filter: ((b >= 125) AND (b < 225))
-> Hash Join
@@ -5037,7 +5363,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Filter: ((b >= 125) AND (b < 225))
-> Seq Scan on alpha_pos_p3 t1_6
Filter: ((b >= 125) AND (b < 225))
-(29 rows)
+(33 rows)
SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
a | b | c | a | b | c
@@ -5150,14 +5476,18 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on alpha_neg_p1 t1_1
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ Bloom Filter 1: keys=(a, b, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on beta_neg_p1 t2_1
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-> Hash Join
Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on alpha_neg_p2 t1_2
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ Bloom Filter 2: keys=(a, b, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on beta_neg_p2 t2_2
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-> Nested Loop
@@ -5172,7 +5502,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
-> Seq Scan on beta_pos_p3 t2_4
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-(29 rows)
+(33 rows)
SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b;
a | b | c | a | b | c
@@ -5316,19 +5646,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM pht1 p1 JOIN pht1 p2 USING (c) LIMIT 1000;
-> Hash Join
Hash Cond: (p1_1.c = p2_1.c)
-> Seq Scan on pht1_p1 p1_1
+ Bloom Filter 1: keys=(c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pht1_p1 p2_1
-> Hash Join
Hash Cond: (p1_2.c = p2_2.c)
-> Seq Scan on pht1_p2 p1_2
+ Bloom Filter 2: keys=(c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pht1_p2 p2_2
-> Hash Join
Hash Cond: (p1_3.c = p2_3.c)
-> Seq Scan on pht1_p3 p1_3
+ Bloom Filter 3: keys=(c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pht1_p3 p2_3
-(17 rows)
+(23 rows)
RESET enable_mergejoin;
SET max_parallel_workers_per_gather = 1;
diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out
index feae77cb840..079f6422fdc 100644
--- a/src/test/regress/expected/predicate.out
+++ b/src/test/regress/expected/predicate.out
@@ -748,14 +748,16 @@ SELECT id FROM dist_tab WHERE row_nn IS DISTINCT FROM ROW(1, 5)::dist_row_t;
SET enable_nestloop TO off;
EXPLAIN (COSTS OFF)
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
- QUERY PLAN
---------------------------------------
+ QUERY PLAN
+---------------------------------------
Hash Join
Hash Cond: (t1.val_nn = t2.val_nn)
-> Seq Scan on dist_tab t1
+ Bloom Filter 1: keys=(val_nn)
-> Hash
+ Bloom Filter 1
-> Seq Scan on dist_tab t2
-(5 rows)
+(7 rows)
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
id | val_nn | val_null | row_nn | id | val_nn | val_null | row_nn
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index f6cc1a1029c..cb03966e79b 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -618,10 +618,12 @@ EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y
Hash Join
Hash Cond: (x.a = y.b)
-> Seq Scan on atest12 x
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on atest12 y
Filter: (abs(a) <<< 5)
-(6 rows)
+(8 rows)
-- clean up (regress_priv_user1's objects are all dropped later)
DROP FUNCTION leak2(integer, integer) CASCADE;
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index ca83d9fcc09..dc44871b682 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -719,8 +719,10 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
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
@@ -730,12 +732,14 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
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)
-> Hash
Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+ 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)
-(27 rows)
+(31 rows)
UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3;
@@ -793,12 +797,14 @@ 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)
-> Hash
Output: foo.f3, foo.f1, foo.f2, foo.f4, foo.ctid, foo.tableoid
+ Bloom Filter 1
-> Seq Scan on pg_temp.foo
Output: foo.f3, foo.f1, foo.f2, foo.f4, foo.ctid, foo.tableoid
Filter: (foo.f3 = 58)
-(12 rows)
+(14 rows)
UPDATE joinview SET f3 = f3 + 1, f4 = 7 WHERE f3 = 58
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3; -- should succeed
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 3a5e82c35bd..4d69d1cd84b 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1,6 +1,8 @@
--
-- Test of Row-level security feature
--
+-- disable bloom filter pushdown, to not interfere with calls to functions
+SET enable_hashjoin_bloom = off;
-- Clean up in case a prior regression run failed
-- Suppress NOTICE messages when users/groups don't exist
SET client_min_messages TO 'warning';
diff --git a/src/test/regress/expected/select_views.out b/src/test/regress/expected/select_views.out
index 1aeed8452bd..fe7be9891d5 100644
--- a/src/test/regress/expected/select_views.out
+++ b/src/test/regress/expected/select_views.out
@@ -2,6 +2,8 @@
-- SELECT_VIEWS
-- test the views defined in CREATE_VIEWS
--
+-- disable bloom filter pushdown, to not interfere with calls to functions
+SET enable_hashjoin_bloom = off;
SELECT * FROM street;
name | thepath | cname
------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 37070c1a896..11cf56fdba4 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3633,9 +3633,11 @@ SELECT * FROM sb_1 a, sb_2 b WHERE a.x = b.x AND a.y = b.y AND a.z = b.z;
Hash Join
Hash Cond: ((a.x = b.x) AND (a.y = b.y) AND (a.z = b.z))
-> Seq Scan on sb_1 a
+ Bloom Filter 1: keys=(x, y, z)
-> Hash
+ Bloom Filter 1
-> Seq Scan on sb_2 b
-(5 rows)
+(7 rows)
-- Check that the Hash Join bucket size estimator detects equal clauses correctly.
SET enable_nestloop = 'off';
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index a3778c23c34..5c7d49050db 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -406,9 +406,11 @@ select * from int4_tbl o where exists
Hash Semi Join
Hash Cond: (o.f1 = i.f1)
-> Seq Scan on int4_tbl o
+ Bloom Filter 1: keys=(f1)
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl i
-(5 rows)
+(7 rows)
explain (costs off)
select * from int4_tbl o where not exists
@@ -783,14 +785,16 @@ order by t1.a, t2.a;
Hash Cond: (t2.a = (t3.b + 1))
-> Seq Scan on public.semijoin_unique_tbl t2
Output: t2.a, t2.b
+ Bloom Filter 1: keys=(t2.a)
-> Hash
Output: t3.a, t3.b
+ Bloom Filter 1
-> HashAggregate
Output: t3.a, t3.b
Group Key: (t3.a + 1), (t3.b + 1)
-> Seq Scan on public.semijoin_unique_tbl t3
Output: t3.a, t3.b, (t3.a + 1), (t3.b + 1)
-(24 rows)
+(26 rows)
-- encourage use of parallel plans
set parallel_setup_cost=0;
@@ -2176,11 +2180,13 @@ order by t1.ten;
Hash Cond: (t2.fivethous = t1.unique1)
-> Seq Scan on public.tenk1 t2
Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4
+ Bloom Filter 1: keys=(t2.fivethous)
-> Hash
Output: t1.ten, t1.unique1
+ Bloom Filter 1
-> Seq Scan on public.tenk1 t1
Output: t1.ten, t1.unique1
-(15 rows)
+(17 rows)
select t1.ten, sum(x) from
tenk1 t1 left join lateral (
@@ -2275,15 +2281,19 @@ order by 1, 2;
Hash Cond: (t2.q2 = t3.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q2)
+ Bloom Filter 2: keys=(t2.q2)
-> Hash
Output: t3.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t3
Output: t3.q2
-> Hash
Output: t1.q1, t1.q2
+ Bloom Filter 2
-> Seq Scan on public.int8_tbl t1
Output: t1.q1, t1.q2
-(19 rows)
+(23 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2326,14 +2336,16 @@ order by 1, 2;
Output: t2.q2, ((t2.q1 + 1))
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q2)
-> Seq Scan on public.int8_tbl t3
Output: t3.q2, (t2.q1 + 1)
Filter: (t2.q2 = t3.q2)
-> Hash
Output: t1.q1, t1.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q1, t1.q2
-(17 rows)
+(19 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2378,15 +2390,19 @@ order by 1, 2;
Hash Cond: (t2.q2 = t3.q1)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q2)
+ Bloom Filter 2: keys=(t2.q1)
-> Hash
Output: t3.q1
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t3
Output: t3.q1
-> Hash
Output: t1.q1
+ Bloom Filter 2
-> Seq Scan on public.int8_tbl t1
Output: t1.q1
-(19 rows)
+(23 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2439,14 +2455,16 @@ order by 1, 2;
Output: t2.q1, (t2.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q1)
-> Seq Scan on public.int8_tbl t3
Output: t3.q1, t2.q2
Filter: (t2.q2 = t3.q1)
-> Hash
Output: t1.q1
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q1
-(17 rows)
+(19 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2510,19 +2528,21 @@ order by 1, 2, 3;
Hash Cond: (t2.q1 = t3.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q2)
-> Hash
Output: t3.q2, (COALESCE(t3.q1, t3.q1))
-> Seq Scan on public.int8_tbl t3
Output: t3.q2, COALESCE(t3.q1, t3.q1)
-> Hash
Output: t4.q1, t4.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t4
Output: t4.q1, t4.q2
-> Hash
Output: t1.q2
-> Seq Scan on public.int8_tbl t1
Output: t1.q2
-(26 rows)
+(28 rows)
select ss2.* from
int8_tbl t1 left join
@@ -2590,11 +2610,13 @@ order by 1, 2, 3;
Output: t3.q2, COALESCE(t3.q1, t3.q1)
-> Seq Scan on public.int8_tbl t4
Output: t4.q1, t4.q2, (COALESCE(t3.q1, t3.q1))
+ Bloom Filter 1: keys=(t4.q1)
-> Hash
Output: t1.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q2
-(24 rows)
+(26 rows)
select ss2.* from
int8_tbl t1 left join
@@ -2895,11 +2917,13 @@ select * from tenk1 A where hundred in (select hundred from tenk2 B where B.odd
Hash Join
Hash Cond: ((a.odd = b.odd) AND (a.hundred = b.hundred))
-> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(odd, hundred)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: b.odd, b.hundred
-> Seq Scan on tenk2 b
-(7 rows)
+(9 rows)
explain (costs off)
select * from tenk1 A where exists
@@ -2964,11 +2988,13 @@ ON B.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = b.odd);
-> Hash Join
Hash Cond: ((b.odd = c.odd) AND (b.hundred = c.hundred))
-> Seq Scan on tenk2 b
+ Bloom Filter 1: keys=(odd, hundred)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: c.odd, c.hundred
-> Seq Scan on tenk2 c
-(10 rows)
+(12 rows)
-- we can pull up the sublink into the inner JoinExpr.
explain (costs off)
@@ -2983,13 +3009,15 @@ WHERE a.thousand < 750;
Hash Cond: (a.hundred = c.hundred)
-> Seq Scan on tenk1 a
Filter: (thousand < 750)
+ Bloom Filter 1: keys=(hundred)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: c.odd, c.hundred
-> Seq Scan on tenk2 c
-> Hash
-> Seq Scan on tenk2 b
-(12 rows)
+(14 rows)
-- we can pull up the aggregate sublink into RHS of a left join.
explain (costs off)
@@ -3126,9 +3154,11 @@ WHERE a.ten IN (VALUES (1), (2));
Hash Cond: (a.ten = c.ten)
-> Seq Scan on onek a
Filter: (ten = ANY ('{1,2}'::integer[]))
+ Bloom Filter 1: keys=(ten)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 c
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF)
SELECT c.unique1,c.ten FROM tenk1 c JOIN onek a USING (ten)
@@ -3139,9 +3169,11 @@ WHERE c.ten IN (VALUES (1), (2));
Hash Cond: (c.ten = a.ten)
-> Seq Scan on tenk1 c
Filter: (ten = ANY ('{1,2}'::integer[]))
+ Bloom Filter 1: keys=(ten)
-> Hash
+ Bloom Filter 1
-> Seq Scan on onek a
-(6 rows)
+(8 rows)
-- Constant expressions are simplified
EXPLAIN (COSTS OFF)
@@ -3311,9 +3343,11 @@ SELECT * FROM onek t1, lateral (SELECT * FROM onek t2 WHERE t2.ten IN (values (t
-> Hash Semi Join
Hash Cond: (t2.ten = "*VALUES*".column1)
-> Seq Scan on onek t2
+ Bloom Filter 1: keys=(ten)
-> Hash
+ Bloom Filter 1
-> Values Scan on "*VALUES*"
-(7 rows)
+(9 rows)
-- VtA causes the whole expression to be evaluated as a constant
EXPLAIN (COSTS OFF)
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 132b56a5864..a796e431415 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -163,6 +163,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_group_by_reordering | on
enable_hashagg | on
enable_hashjoin | on
+ enable_hashjoin_bloom | on
enable_incremental_sort | on
enable_indexonlyscan | on
enable_indexscan | on
@@ -180,7 +181,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(25 rows)
+(26 rows)
-- There are always wait event descriptions for various types. InjectionPoint
-- may be present or absent, depending on history since last postmaster start.
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 7b00c742776..7d4af80faf6 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -587,11 +587,13 @@ MERGE INTO rw_view1 t
Hash Cond: (base_tbl.a = generate_series.generate_series)
-> Bitmap Heap Scan on base_tbl
Recheck Cond: (a > 0)
+ Bloom Filter 1: keys=(a)
-> Bitmap Index Scan on base_tbl_pkey
Index Cond: (a > 0)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series
-(9 rows)
+(11 rows)
EXPLAIN (costs off)
MERGE INTO rw_view1 t
@@ -621,11 +623,13 @@ MERGE INTO rw_view1 t
Hash Cond: (base_tbl.a = generate_series.generate_series)
-> Bitmap Heap Scan on base_tbl
Recheck Cond: (a > 0)
+ Bloom Filter 1: keys=(a)
-> Bitmap Index Scan on base_tbl_pkey
Index Cond: (a > 0)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series
-(9 rows)
+(11 rows)
-- it's still updatable if we add a DO ALSO rule
CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
@@ -2777,12 +2781,14 @@ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
-> Hash Join
Hash Cond: (b.a = r.a)
-> Seq Scan on base_tbl b
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on ref_tbl r
SubPlan exists_1
-> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
Index Cond: (a = b.a)
-(9 rows)
+(11 rows)
DROP TABLE base_tbl, ref_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 90d9f953b81..5c86619f023 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -5474,10 +5474,12 @@ LIMIT 1;
-> Hash Join
Hash Cond: (t1.unique1 = t2.tenthous)
-> Index Only Scan using tenk1_unique1 on tenk1 t1
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 t2
Filter: (two = 1)
-(9 rows)
+(11 rows)
-- Ensure we get a cheap total plan. This time use UNBOUNDED FOLLOWING, which
-- needs to read all join rows to output the first WindowAgg row.
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 77ded01b046..db8c77721e7 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -686,9 +686,11 @@ select count(*) from tenk1 a
-> Hash Semi Join
Hash Cond: (a.unique1 = x.unique1)
-> Index Only Scan using tenk1_unique1 on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> CTE Scan on x
-(8 rows)
+(10 rows)
explain (costs off)
with x as materialized (insert into tenk1 default values returning unique1)
@@ -3246,8 +3248,10 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
Hash Cond: (m.k = o.k)
-> Seq Scan on public.m
Output: m.ctid, m.k
+ Bloom Filter 1: keys=(m.k)
-> Hash
Output: o.k, o.v, o.*
+ Bloom Filter 1
-> Subquery Scan on o
Output: o.k, o.v, o.*
-> Result
@@ -3258,7 +3262,7 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
-> CTE Scan on cte_basic
Output: (cte_basic.b || ' merge update'::text)
Filter: (cte_basic.a = m.k)
-(21 rows)
+(23 rows)
-- InitPlan
WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b)
@@ -3295,13 +3299,15 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
Hash Cond: (m.k = o.k)
-> Seq Scan on public.m
Output: m.ctid, m.k
+ Bloom Filter 1: keys=(m.k)
-> Hash
Output: o.k, o.v, o.*
+ Bloom Filter 1
-> Subquery Scan on o
Output: o.k, o.v, o.*
-> Result
Output: 1, 'merge source InitPlan'::text
-(21 rows)
+(23 rows)
-- MERGE source comes from CTE:
WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b)
@@ -3339,11 +3345,13 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text
Hash Cond: (m.k = merge_source_cte.a)
-> Seq Scan on public.m
Output: m.ctid, m.k
+ Bloom Filter 1: keys=(m.k)
-> Hash
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
+ Bloom Filter 1
-> CTE Scan on merge_source_cte
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
-(20 rows)
+(22 rows)
DROP TABLE m;
-- check that run to completion happens in proper ordering
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index 6b3566271df..390e79b04c3 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -2,6 +2,9 @@
-- Test of Row-level security feature
--
+-- disable bloom filter pushdown, to not interfere with calls to functions
+SET enable_hashjoin_bloom = off;
+
-- Clean up in case a prior regression run failed
-- Suppress NOTICE messages when users/groups don't exist
diff --git a/src/test/regress/sql/select_views.sql b/src/test/regress/sql/select_views.sql
index e742f136990..09f96b0b1ae 100644
--- a/src/test/regress/sql/select_views.sql
+++ b/src/test/regress/sql/select_views.sql
@@ -3,6 +3,9 @@
-- test the views defined in CREATE_VIEWS
--
+-- disable bloom filter pushdown, to not interfere with calls to functions
+SET enable_hashjoin_bloom = off;
+
SELECT * FROM street;
SELECT name, #thepath FROM iexit ORDER BY name COLLATE "C", 2;
--
2.50.1 (Apple Git-155)
From 6d4a45b3ff5bb25f8a14a1c2c3e34a92994a25a3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <[email protected]>
Date: Sat, 20 Jun 2026 20:20:08 +0000
Subject: [PATCH v4 2/4] PoC: Bloom filter pushdown during path construction
We can't decide which filters to push down during plan creation, it's
too late. At that point we've already created paths with expected
cardinalities, calculated the costs, etc.
If we inject new filters to the scan nodes, the row counts won't match
and it'll be confusing (effectively impossible to decide if a difference
is a misestimate or just do to a pushed-down filter).
It also means we can't consider the effect during the "bottom-up" phase,
e.g. when picking algorithms for the other joins, etc. We would likely
improve the current plan, but it prevents us from picking an alternative
cheaper plan.
To address this, add "expected filters" as a feature of a path, and
propage them during joins etc. This is similar concept to path keys,
tracking "interesting" orderings for a path.
At the scan level, figure out a list of "interesting" filters. This is
done by inspecting the joins the scanned relation participates in, and
picking a limited number of sufficiently selective filters, and then it
generates paths expecting different combinations of filters.
The number of combinations grows with 2^n, so with 3 filters we get 9
paths (8 new ones), with 4 it's 16, etc. And this is for each scan node.
For a query with many joins, it can grow pretty quick.
So we need to be conservative, in order to prevent an explosion of the
number of paths. The patch simply picks a limited number of the most
selective filters (e.g. 3 filters, each eliminating at least 30%
tuples). We could refine how this is decided, of course. For example,
CustomScan could/should have a say in the costing, somehow.
At the join level, we need to consider if a join is matching a filter
expected by the input paths or not.
* A join "matches" a filter of an input path, if it's the join from
which the filter was derived.
* A join can "satisfy" a filter expected by the outer innput path, if it
matches it, and if it's a hash join. A join can never satisfy filters
expected by the inner input.
* A join has to satisfy all filters matching the inner/outer path.
This implies that:
* NestLoop/MergeJoin can never satisfy any filters. The places creating
these joins have to ignore all paths with such filters. But these
joins can still propagate all other filters, in case one of the later
joins happens to be a hashjoin satisfying them.
* HashJois can satisfy filters expected by outer input path, or
propagate filters not matching the join.
Notes:
* We could also consider the "remaining row count" after a filter, and
only consider filters if it's above some limit. E.g. it there's less
than 1000 tuples, there's little point in pushing down additional
filters. This would help with reducing the impact of path explosion.
* When figuring out interesting filters, we need to be careful about
outer joins. Outer joins are not a good filter source, because we
can't eliminate any rows even if the join clause is very selective.
* We still do the pushdown when creating the plan, but it's very
mechanical - all the decisions have been made earlier. It's not
possible (allowed) to mismatch - we can't pushdown a filter not
expected by a scan, and the scan can't expect a filter that has not
been pushdown by the plan.
---
src/backend/commands/explain.c | 49 +-
src/backend/executor/nodeHashjoin.c | 19 +-
src/backend/optimizer/path/allpaths.c | 463 ++++++++++++++++
src/backend/optimizer/path/costsize.c | 17 +
src/backend/optimizer/path/equivclass.c | 179 ++++++
src/backend/optimizer/path/joinpath.c | 524 +++++++++++++++---
src/backend/optimizer/plan/createplan.c | 257 ++++-----
src/backend/optimizer/util/pathnode.c | 158 ++++++
src/backend/utils/misc/guc_parameters.dat | 20 +
src/backend/utils/misc/postgresql.conf.sample | 2 +
src/include/nodes/pathnodes.h | 66 +++
src/include/optimizer/cost.h | 2 +
src/include/optimizer/pathnode.h | 5 +
src/include/optimizer/paths.h | 6 +
src/test/regress/expected/eager_aggregate.out | 178 ++----
src/test/regress/expected/graph_table.out | 14 +-
src/test/regress/expected/join.out | 425 +++++++-------
src/test/regress/expected/join_hash.out | 16 +-
src/test/regress/expected/merge.out | 106 ++--
src/test/regress/expected/misc_functions.out | 14 +-
.../regress/expected/partition_aggregate.out | 34 +-
src/test/regress/expected/partition_join.out | 452 ++-------------
src/test/regress/expected/predicate.out | 8 +-
src/test/regress/expected/returning.out | 42 +-
src/test/regress/expected/stats_ext.out | 23 +-
src/test/regress/expected/subselect.out | 83 +--
src/test/regress/expected/updatable_views.out | 19 +-
src/test/regress/expected/window.out | 15 +-
src/test/regress/expected/with.out | 112 ++--
29 files changed, 2004 insertions(+), 1304 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8893e36c8aa..0ba1319d79f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2589,19 +2589,34 @@ show_upper_qual(List *qual, const char *qlabel,
/*
* show_bloom_filter_info
- * Show info about every bloom filter pushed down to a scan node.
+ * Show info about every Bloom filter pushed down to a scan node.
*
* In TEXT format each filter is rendered on a single line, e.g.
*
- * Bloom Filter N: (a, b) producer=3 checked=99999 rejected=99990
+ * Bloom Filter N: keys=(a, b) checked=99999 rejected=99990
*
- * The checked/rejected fields are omitted outside of ANALYZE). In
- * structured formats we emit a group per filter with the same fields
- * broken out as properties.
+ * The checked/rejected fields are omitted outside of ANALYZE). In structured
+ * formats we emit a group per filter with the same fields broken out as
+ * properties.
*
- * Called from the per-recipient cases in ExplainNode; the recipient is
- * expected to be a scan node in the current PoC, but nothing here
- * depends on that.
+ * Called from the per-recipient cases in ExplainNode; the recipient is expected
+ * to be a scan node, but nothing here depends on that. We may choose to push
+ * filters to other nodes in the plan.
+ *
+ * This only prints information about the keys, and the checked/rejected counts.
+ * Detailed information about the filter itself (number of bits, number of hash
+ * functions, ...) is available on the producer side.
+ *
+ * XXX It might be a good idea to show the expected filter selectivity too,
+ * not just the one actually observed during execution. That'd make it easier
+ * to reason about the decisions and review the plan changes.
+ *
+ * XXX If we choose other filter types, we'd need some general way to show the
+ * information. I don't know if we should have a "generic" information provided
+ * by all the filters, or if we would need a way to print custom information.
+ * Chances are we'd have a limited number of supported filter types, in which
+ * case we can have a show_ function for each type. Only if users could inject
+ * arbitrary filters, that'd be an issue. But that seems unlikely.
*/
static void
show_bloom_filter_info(PlanState *planstate, List *ancestors,
@@ -3599,9 +3614,21 @@ show_hash_info(HashState *hashstate, ExplainState *es)
}
/*
- * Show infromation about the bloom filter produced by this Hash node
- * (if any). For plain EXPLAIN, the filter is not initialized / built,
- * but we still show available plan-time metadata (at least the ID).
+ * Show infromation about the Bloom filter produced by this Hash node (if
+ * any). For plain EXPLAIN, the filter is not initialized / built, but we
+ * still show available plan-time metadata (at least the ID).
+ *
+ * Once the filter is built, we show the various filter parameters (number
+ * of bits, hash functions, ...). Note that even with EXPLAIN ANALYZE the
+ * filter may not be built, due to the hashjoin delaying the Hash build
+ * until on the first outer tuple.
+ *
+ * XXX We don't show the keys, because those are always the join keys.
+ *
+ * XXX I think it makes sense to show the checked/rejected counters both
+ * here and for the consumer. If/when we allow multiple consumers for the
+ * filter, then this would show the "summary" while the scan node would
+ * show just counters for that one consumer.
*/
if (hashstate->bloom_filter_id > 0)
{
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 8fa7af4cfef..e3467f14739 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -2019,15 +2019,16 @@ ExecHashJoinInitializeWorker(HashJoinState *state,
* BLOOM FILTER PUSHDOWN
*
* The pushdown decision is done in try_push_bloom_filter, when constructing
- * the plan from the selected paths (see createplan.c). It decides which scan
- * node should receive the bloom filter (if any), and what expressions it
- * should use to calculate the hash value.
+ * the plan from the selected paths. The input paths track filters "expected"
+ * by scan nodes included in that path (if any). The planner then ensures all
+ * expected filters are either satisfied (by matching joins) or propagated up.
+ * In a valid plan all expected filters are satisfied.
*
* Then at execution time:
*
* - ExecInitHashJoin registers itself in EState.es_bloom_producers
* before recursing into child plans, so by the time a recipient's
- * ExecInit runs, the producer is already discoverable by plan_node_id.
+ * ExecInit runs, the producer is already discoverable by filter ID.
* This registration only happens when there's at least one consumer.
* It also sets want_bloom_filter for the Hash node.
*
@@ -2035,25 +2036,25 @@ ExecHashJoinInitializeWorker(HashJoinState *state,
* when HashState.want_bloom_filter is set (so no work happens when
* nobody will probe).
*
- * - Nodes with non-NIL plan->bloom_filters (and supporting bloom
+ * - Nodes with non-NIL plan->bloom_filters (and supporting Bloom
* filters) call ExecInitBloomFilters() during its own ExecInit,
* which looks up the producer node (in the EState), compiles
* ExprStates for the hash expressions, etc. The filter state
* (BloomFilterState) gets added to ps->bloom_filters (a node may
- * have multiple bloom filters from different hash joins).
+ * have multiple Bloom filters from different hash joins).
*
* - The per-tuple loop of the scan node calls ExecBloomFilters() (much
* like ExecQual) to test the tuple against every attached filter,
* dropping it on the first filter that excludes it. For scan nodes
* this call happens in ExecScanExtended.
*
- * The scan nodes reach the bloom filter via the HashJoinState pointer
+ * The scan nodes reach the Bloom filter via the HashJoinState pointer
* added to EState.es_bloom_producers, so that the rescans etc. (filter
* freed + recreated when the hash table is destroyed and rebuilt) are
- * transparent to the consumer. The bloom filter gets reallocated after
+ * transparent to the consumer. The Bloom filter gets reallocated after
* a rescan, so the pointer to it may change.
*
- * XXX It's possible the bloom filter gets pushed down to a node that
+ * XXX It's possible the Bloom filter gets pushed down to a node that
* fails to initialize/use it. It'll be added to the bloom_filters list,
* but if the node does not call ExecInitBloomFilters, the filter will
* be unused.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c134594a21a..8829c7c3108 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -107,6 +107,9 @@ static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
+static List *find_interesting_bloom_filters(PlannerInfo *root,
+ RelOptInfo *rel);
+static void generate_expected_filter_paths(PlannerInfo *root, RelOptInfo *rel);
static void set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -603,6 +606,16 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
!bms_equal(rel->relids, root->all_query_rels))
generate_useful_gather_paths(root, rel, false);
+ /*
+ * For plain base relations, consider generating additional scan paths
+ * that anticipate a Bloom filter being pushed down from a hash join above
+ * (see find_interesting_bloom_filters). These paths have reduced row
+ * estimates and are consumed by join path generation.
+ */
+ if (rel->reloptkind == RELOPT_BASEREL &&
+ rte->rtekind == RTE_RELATION)
+ generate_expected_filter_paths(root, rel);
+
/* Now find the cheapest of the paths for this rel */
set_cheapest(rel);
@@ -884,6 +897,456 @@ create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
}
+/*
+ * bloom_em_matches_anybarevar
+ * ec_matches_callback used by find_interesting_bloom_filters: accept any
+ * EquivalenceClass member that is a bare Var of the target relation. The
+ * Bloom filter pushdown logic (createplan.c) only supports recipients whose
+ * join keys are bare Vars, so we mirror that requirement here.
+ */
+static bool
+bloom_em_matches_anybarevar(PlannerInfo *root, RelOptInfo *rel,
+ EquivalenceClass *ec, EquivalenceMember *em,
+ void *arg)
+{
+ Var *var;
+
+ /* We're looking only for bare Var expressions. */
+ if (!IsA(em->em_expr, Var))
+ return false;
+
+ /*
+ * Is the Var referencing a normal (non-system) attribute in the relation
+ * we're processing (generating scans for)?
+ *
+ * FIXME Can we have (varlevelsup != 0) for baserels? I don't think we can
+ * have outer referecenses in that place.
+ */
+ var = (Var *) em->em_expr;
+ if (var->varno != rel->relid ||
+ var->varattno <= 0 ||
+ var->varlevelsup != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * bloom_filter_recipient_reachable
+ * Check that a Bloom filter owned by owner_relid and built from
+ * build_relids could actually be pushed to the owner's scan.
+ *
+ * A pushed-down filter removes owner tuples that have no match on the build
+ * side, so it can only be applied where the join that realizes it drops such
+ * unmatched owner (probe) tuples. If an outer join null-extends the owner
+ * before it can be joined to the build side, the filter would change the
+ * result and is therefore unusable: at plan time find_bloom_filter_recipient
+ * would refuse to descend into that side and find no recipient (see also
+ * bloom_join_side_preserved, which is checking for this situation).
+ *
+ * Picking such a filter only to throw it away later wastes planner effort,
+ * and we might also ignore some other filters because of that. It's better
+ * to eliminate it right away.
+ *
+ * This is primarily an optimization - we don't want to generate paths that
+ * would ultimately be useless, and possibly not generating paths for other
+ * filters. The correctness is still guaranteed by the propagation logic in
+ * compute_join_expected_filters(), which rejects cases that would carry a
+ * filter across a non-preserved join side. That guarantees we don't pick a
+ * plan with such filters, only to find about the issue in createplan.c.
+ */
+static bool
+bloom_filter_recipient_reachable(PlannerInfo *root, Index owner_relid,
+ Relids build_relids)
+{
+ ListCell *lc;
+
+ foreach(lc, root->join_info_list)
+ {
+ SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
+
+ switch (sjinfo->jointype)
+ {
+ case JOIN_LEFT:
+ case JOIN_ANTI:
+
+ /*
+ * The syntactic RHS is null-extended. If the owner is on it
+ * but the build side is reached from the preserved LHS, the
+ * owner must cross this outer join on its nullable side.
+ */
+ if (bms_is_member(owner_relid, sjinfo->syn_righthand) &&
+ !bms_is_subset(build_relids, sjinfo->syn_righthand))
+ return false;
+ break;
+ case JOIN_FULL:
+
+ /*
+ * Both sides are null-extended, so the filter is unusable
+ * whenever the owner and the build side sit on opposite sides
+ * of the join.
+ */
+ if (bms_is_member(owner_relid, sjinfo->syn_lefthand) &&
+ !bms_is_subset(build_relids, sjinfo->syn_lefthand))
+ return false;
+ if (bms_is_member(owner_relid, sjinfo->syn_righthand) &&
+ !bms_is_subset(build_relids, sjinfo->syn_righthand))
+ return false;
+ break;
+ default:
+ /* INNER and SEMI joins never null-extend the owner. */
+ break;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * find_interesting_bloom_filters
+ * Identify Bloom filters that a hash join above this scan could push down,
+ * and that are selective enough to be worth costing for.
+ *
+ * We look for hashjoinable equality join clauses where this rel's side is a
+ * bare Var, derived both from EquivalenceClasses and from non-EC joininfo
+ * clauses. Clauses are grouped by the relids on the other ("build") side of
+ * the join; each group becomes a candidate filter whose expected surviving
+ * fraction is estimated as the semijoin selectivity of those clauses.
+ *
+ * A candidate is "interesting" only if it is expected to eliminate at least
+ * bloom_filter_pushdown_threshold of the rel's tuples. We keep at most
+ * bloom_filter_pushdown_max of the most selective candidates, and return them
+ * as a list of ExpectedFilter nodes.
+ *
+ * XXX This needs to be careful to not interfere with the general selectivity
+ * estimation, performed by clauselist_selectivity(). We'll estimate the filter
+ * selectivity using a made-up sjinfo with JOIN_INNER, which may not match
+ * the actual join. The selectivities must not leak - this is why this function
+ * does not collect the RestrictInfos but only the clauses. If we used the
+ * RestrictInfos, the clauselist_selectivity would cache the incorrect result
+ * in them, and it'd affect the planning in weird ways.
+ *
+ * FIXME Maybe there's a better way to calculate the filter selectivity?
+ */
+static List *
+find_interesting_bloom_filters(PlannerInfo *root, RelOptInfo *rel)
+{
+ List *candidates;
+ List *group_relids = NIL; /* parallel: Relids per group */
+ List *group_clauses = NIL; /* parallel: List of clauses */
+ List *result = NIL;
+ ListCell *lc;
+
+ if (!enable_hashjoin_bloom)
+ return NIL;
+
+ if (bloom_filter_pushdown_max <= 0)
+ return NIL;
+
+ if (rel->reloptkind != RELOPT_BASEREL)
+ return NIL;
+
+ /* Collect candidate hashjoinable equality clauses for this rel. */
+ candidates = generate_implied_equalities_for_all_columns(root, rel,
+ bloom_em_matches_anybarevar,
+ NULL, NULL);
+
+ foreach(lc, rel->joininfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ /* EC-derived clauses are already covered above. */
+ if (rinfo->parent_ec != NULL)
+ continue;
+
+ candidates = lappend(candidates, rinfo->clause);
+ }
+
+ /* Group candidate clauses by their build-side relids. */
+ foreach(lc, candidates)
+ {
+ Node *clause = (Node *) lfirst(lc);
+ Node *left;
+ Node *right;
+ Node *ownerexpr;
+ Oid opno;
+ Relids clause_relids;
+ Relids build_relids;
+ int buildrel;
+ ListCell *lc2;
+ ListCell *lc3;
+ bool found;
+
+ /* strip RestrictInfo (see comment above) */
+ if (IsA(clause, RestrictInfo))
+ clause = (Node *) ((RestrictInfo *) clause)->clause;
+
+ /*
+ * Only care about (Expr op Expr) clauses. We know one side has to be
+ * a bare Var node, from the "owner" side (which is the scan node).
+ * The other side can be arbitrary expression on the other relation.
+ */
+ if (!is_opclause(clause) ||
+ list_length(((OpExpr *) clause)->args) != 2)
+ continue;
+
+ opno = ((OpExpr *) clause)->opno;
+ left = get_leftop(clause);
+ right = get_rightop(clause);
+
+ /* Identify which side is a bare Var of this rel (the owner side). */
+ /* XXX replace this with a macro shared with bloom_em_matches_anybarevar */
+ if (IsA(left, Var) && ((Var *) left)->varno == rel->relid &&
+ ((Var *) left)->varattno > 0 && ((Var *) left)->varlevelsup == 0)
+ ownerexpr = left;
+ else if (IsA(right, Var) && ((Var *) right)->varno == rel->relid &&
+ ((Var *) right)->varattno > 0 &&
+ ((Var *) right)->varlevelsup == 0)
+ ownerexpr = right;
+ else
+ continue;
+
+ /* Operator must be hashjoinable on the owner's input type. */
+ if (!op_hashjoinable(opno, exprType(ownerexpr)))
+ continue;
+
+ /*
+ * The build side must be a single base relation; that's what the
+ * recipient lookup and our selectivity estimate can handle.
+ *
+ * XXX I don't think this restriction is necessary. We can allow the
+ * build side to be a join. I don't see why that would be a problem.
+ */
+ clause_relids = pull_varnos(root, (Node *) clause);
+ build_relids = bms_difference(clause_relids, rel->relids);
+ if (!bms_get_singleton_member(build_relids, &buildrel) ||
+ buildrel >= root->simple_rel_array_size ||
+ root->simple_rel_array[buildrel] == NULL ||
+ root->simple_rel_array[buildrel]->reloptkind != RELOPT_BASEREL)
+ {
+ bms_free(build_relids);
+ continue;
+ }
+
+ /* Add to an existing group, or start a new one. */
+ /* XXX Maybe we sould have a HTAB with the relids as a key? But the
+ * lists should not be that long, I think. */
+ found = false;
+ forboth(lc2, group_relids, lc3, group_clauses)
+ {
+ Relids grelids = (Relids) lfirst(lc2);
+
+ if (bms_equal(grelids, build_relids))
+ {
+ lfirst(lc3) = lappend((List *) lfirst(lc3), clause);
+ found = true;
+
+ /* added to an existing group, don't keep the relids around */
+ bms_free(build_relids);
+
+ break;
+ }
+ }
+
+ if (!found) /* new group */
+ {
+ group_relids = lappend(group_relids, build_relids);
+ group_clauses = lappend(group_clauses, list_make1(clause));
+ }
+ }
+
+ /*
+ * We have collected all potentially intresting filters. Evaluate selectivity
+ * of each group and keep only the most interesting filters. Filters have to
+ * eliminate at least bloom_filter_pushdown_threshold tuples, and we keep
+ * only bloom_filter_pushdown_max most selective ones.
+ */
+ {
+ ListCell *lcr = list_head(group_relids);
+ ListCell *lcc = list_head(group_clauses);
+
+ while (lcr != NULL && lcc != NULL)
+ {
+ Relids build_relids = (Relids) lfirst(lcr);
+ List *clauses = (List *) lfirst(lcc);
+ SpecialJoinInfo sjinfo;
+ Selectivity sel;
+
+ init_dummy_sjinfo(&sjinfo, rel->relids, build_relids);
+ sjinfo.jointype = JOIN_SEMI;
+
+ sel = clauselist_selectivity(root, clauses, 0, JOIN_SEMI, &sjinfo);
+
+ if ((sel <= 1.0 - bloom_filter_pushdown_threshold) &&
+ (sel > 0.0) && /* XXX seems unnecessary */
+ bloom_filter_recipient_reachable(root, rel->relid, build_relids))
+ {
+ ExpectedFilter *f = makeNode(ExpectedFilter);
+
+ f->owner_relid = rel->relid;
+ f->build_relids = build_relids;
+ f->clauses = clauses;
+ f->selectivity = sel;
+ result = lappend(result, f);
+ }
+
+ lcr = lnext(group_relids, lcr);
+ lcc = lnext(group_clauses, lcc);
+ }
+ }
+
+ /*
+ * We only connsider a limited number of interesting filters, to prevent
+ * path explosion. If we found too many, keep only the most selective ones
+ * (with smallest surviving fraction of tuples), to bound the number of
+ * generated paths.
+ *
+ * XXX This also aligns with good join orders - those tend to perform the
+ * most selective joins first. So we get to build the filters soon, even
+ * if the hashjoin optimization is not disabled.
+ */
+ while (list_length(result) > bloom_filter_pushdown_max)
+ {
+ ExpectedFilter *worst = NULL;
+ ListCell *lcw;
+
+ foreach(lcw, result)
+ {
+ ExpectedFilter *f = (ExpectedFilter *) lfirst(lcw);
+
+ if (worst == NULL || f->selectivity > worst->selectivity)
+ worst = f;
+ }
+ result = list_delete_ptr(result, worst);
+ }
+
+ return result;
+}
+
+/*
+ * generate_expected_filter_paths
+ * Generate additional scan paths that anticipate one or more pushed-down
+ * Bloom filters.
+ *
+ * For each non-empty subset of the interesting filters, we clone every eligible
+ * existing scan path, reducing its row estimate by the combined selectivity and
+ * attaching the corresponding ExpectedFilter nodes.
+ *
+ * These paths are kept alongside the regular paths (add_path keeps paths with
+ * differing expected_filters) and are consumed by join path generation;
+ * set_cheapest never selects them.
+ *
+ * XXX We must not clone paths that already have expected filters.
+ *
+ * XXX The cloning is a rather dirty way to copy paths. It does not readjust the
+ * cost in a reasonable way. For example custom scans could do something smart
+ * with the filters, so it should have a chance to deal with that. A cleaner
+ * solution might be to actually pass the filters to the various "create"
+ * function, like create_seqscan_path/... For CustomScan nodes we can probably
+ * do most of this in the set_rel_pathlist_hook, somewhere. Maybe that needs
+ * some helper methods, though. And maybe it will need to pass some of the info
+ * through the callbacks? Not sure, someone has to try that.
+ *
+ * XXX This may need some major changes to work with custom scans. Right now we
+ * only consider filters exactly matching the hash keys, so if the hashjoin is
+ * on (t1.a = t2.a AND t1.b = t2.b), then the filter will be on (a,b). But a
+ * custom scan may prefer "split" filters on each column independently. We'd
+ * need a way for the custom scan to indicate that, and we'd need to apply this
+ * only to the "matching" scan paths (and not to any other scan paths). But
+ * we only look at the paths after selecting the "interesting" filters, so we'd
+ * need to rethink that - we'd need to make the "interesting" filters specific
+ * to a path, or something like that.
+ */
+static void
+generate_expected_filter_paths(PlannerInfo *root, RelOptInfo *rel)
+{
+ List *filters;
+ List *basepaths = NIL;
+ int nfilters;
+ uint32 combo;
+ ListCell *lc;
+
+ filters = find_interesting_bloom_filters(root, rel);
+ if (filters == NIL)
+ return;
+
+ nfilters = list_length(filters);
+
+ /*
+ * Snapshot the existing unparameterized, non-partial scan paths of a
+ * supported type. We must snapshot before calling add_path(), which
+ * mutates rel->pathlist.
+ */
+ foreach(lc, rel->pathlist)
+ {
+ Path *path = (Path *) lfirst(lc);
+
+ /* XXX Is parameterization really a problem? Always? */
+ if (path->param_info != NULL || path->expected_filters != NIL)
+ continue;
+
+ switch (nodeTag(path))
+ {
+ case T_Path:
+ case T_IndexPath:
+ case T_BitmapHeapPath:
+ case T_TidPath:
+ case T_TidRangePath:
+ basepaths = lappend(basepaths, path);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (basepaths == NIL)
+ return;
+
+ /*
+ * Generate all combinations of the interesting filters. We do that by
+ * iterating 1 to (2^n-1), which generates all bitmask in between. Those
+ * are the subsets.
+ *
+ * XXX This is a good demonstration why we need to keep the number of
+ * filters low
+ *
+ * XXX Maybe we should also stop adding filters once the other filters
+ * already eliminate enought tuples. Say, we know F1 alone eliminates 99%
+ * tuples. Does it make sense to also consider [F1,F2]? Probably not. We
+ * could track "maximum" sets, and reject combinations containing one
+ * of those. We'd need to generate sets of increasing size, the iteration
+ * does not do that. But that's not hard.
+ */
+ for (combo = 1; combo < ((uint32) 1 << nfilters); combo++)
+ {
+ List *subset = NIL;
+ int i = 0;
+ ListCell *lcf;
+
+ foreach(lcf, filters)
+ {
+ if (combo & ((uint32) 1 << i))
+ subset = lappend(subset, lfirst(lcf));
+ i++;
+ }
+
+ /*
+ * All filtered paths for this combo share the same expected_filters
+ * list. That's safe: the list is never modified, and add_path() only
+ * ever frees the Path node itself, not its expected_filters.
+ */
+ foreach(lc, basepaths)
+ {
+ Path *base = (Path *) lfirst(lc);
+ Path *newpath;
+
+ newpath = create_filtered_scan_path(root, base, subset);
+ if (newpath != NULL)
+ add_path(rel, newpath);
+ }
+ }
+}
+
/*
* set_tablesample_rel_size
* Set size estimates for a sampled relation
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index c3072a29ccc..8740889094f 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -166,6 +166,23 @@ bool enable_partition_pruning = true;
bool enable_presorted_aggregate = true;
bool enable_async_append = true;
+/*
+ * Minimum fraction of outer tuples a pushed-down hash-join Bloom filter must
+ * be expected to eliminate for the planner to treat it as "interesting" and
+ * generate filter-aware scan paths. A value of 0.3 means a filter is only
+ * considered if it is expected to discard at least 30% of the scanned tuples.
+ */
+double bloom_filter_pushdown_threshold = 0.3;
+
+/*
+ * Upper bound on the number of distinct interesting Bloom filters considered
+ * for a single scan relation. This bounds the number of additional paths
+ * generated per scan (the planner enumerates non-empty subsets of the
+ * interesting filters, i.e. up to 2^bloom_filter_pushdown_max - 1 extra
+ * paths per base scan path).
+ */
+int bloom_filter_pushdown_max = 3;
+
typedef struct
{
PlannerInfo *root;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index e3697df51a2..6722b74f401 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -75,6 +75,15 @@ static RestrictInfo *create_join_clause(PlannerInfo *root,
EquivalenceMember *leftem,
EquivalenceMember *rightem,
EquivalenceClass *parent_ec);
+static int generate_implied_equalities_for_column_ec(PlannerInfo *root,
+ RelOptInfo *rel,
+ EquivalenceClass *cur_ec,
+ ec_matches_callback_type callback,
+ void *callback_arg,
+ Relids prohibited_rels,
+ Relids parent_relids,
+ bool is_child_rel,
+ List **result);
static bool reconsider_outer_join_clause(PlannerInfo *root,
OuterJoinClauseInfo *ojcinfo,
bool outer_on_left);
@@ -3211,6 +3220,107 @@ eclass_member_iterator_next(EquivalenceMemberIterator *it)
return NULL;
}
+/*
+ * generate_implied_equalities_for_column_ec
+ * Workhorse for generate_implied_equalities_for_column() and
+ * generate_implied_equalities_for_all_columns(). Considers a single
+ * EquivalenceClass cur_ec: if it has a member matching the target column
+ * (as identified by the callback), generate EC-derived joinclauses
+ * equating that member to each other-relation member, appending them to
+ * *result. Returns the number of clauses generated.
+ */
+static int
+generate_implied_equalities_for_column_ec(PlannerInfo *root,
+ RelOptInfo *rel,
+ EquivalenceClass *cur_ec,
+ ec_matches_callback_type callback,
+ void *callback_arg,
+ Relids prohibited_rels,
+ Relids parent_relids,
+ bool is_child_rel,
+ List **result)
+{
+ EquivalenceMemberIterator it;
+ EquivalenceMember *cur_em;
+ ListCell *lc2;
+ int ngenerated = 0;
+
+ /*
+ * Won't generate joinclauses if const or single-member (the latter test
+ * covers the volatile case too)
+ */
+ if (cur_ec->ec_has_const || list_length(cur_ec->ec_members) <= 1)
+ return 0;
+
+ /*
+ * Scan members, looking for a match to the target column. Note that
+ * child EC members are considered, but only when they belong to the
+ * target relation. (Unlike regular members, the same expression could be
+ * a child member of more than one EC. Therefore, it's potentially
+ * order-dependent which EC a child relation's target column gets matched
+ * to. This is annoying but it only happens in corner cases, so for now we
+ * live with just reporting the first match. See also
+ * get_eclass_for_sort_expr.)
+ */
+ setup_eclass_member_iterator(&it, cur_ec, rel->relids);
+ while ((cur_em = eclass_member_iterator_next(&it)) != NULL)
+ {
+ if (bms_equal(cur_em->em_relids, rel->relids) &&
+ callback(root, rel, cur_ec, cur_em, callback_arg))
+ break;
+ }
+
+ if (!cur_em)
+ return 0;
+
+ /*
+ * Found our match. Scan the other EC members and attempt to generate
+ * joinclauses. Ignore children here.
+ */
+ foreach(lc2, cur_ec->ec_members)
+ {
+ EquivalenceMember *other_em = (EquivalenceMember *) lfirst(lc2);
+ Oid eq_op;
+ RestrictInfo *rinfo;
+
+ /* Child members should not exist in ec_members */
+ Assert(!other_em->em_is_child);
+
+ /* Make sure it'll be a join to a different rel */
+ if (other_em == cur_em ||
+ bms_overlap(other_em->em_relids, rel->relids))
+ continue;
+
+ /* Forget it if caller doesn't want joins to this rel */
+ if (bms_overlap(other_em->em_relids, prohibited_rels))
+ continue;
+
+ /*
+ * Also, if this is a child rel, avoid generating a useless join to its
+ * parent rel(s).
+ */
+ if (is_child_rel &&
+ bms_overlap(parent_relids, other_em->em_relids))
+ continue;
+
+ eq_op = select_equality_operator(cur_ec,
+ cur_em->em_datatype,
+ other_em->em_datatype);
+ if (!OidIsValid(eq_op))
+ continue;
+
+ /* set parent_ec to mark as redundant with other joinclauses */
+ rinfo = create_join_clause(root, cur_ec, eq_op,
+ cur_em, other_em,
+ cur_ec);
+
+ *result = lappend(*result, rinfo);
+ ngenerated++;
+ }
+
+ return ngenerated;
+}
+
/*
* generate_implied_equalities_for_column
* Create EC-derived joinclauses usable with a specific column.
@@ -3233,6 +3343,10 @@ eclass_member_iterator_next(EquivalenceMemberIterator *it)
*
* The caller can pass a Relids set of rels we aren't interested in joining
* to, so as to save the work of creating useless clauses.
+ *
+ * XXX This could reuse generate_implied_equalities_for_column_ec for the
+ * inner loop, similarly to generate_implied_equalities_for_all_columns, but I
+ * chose to not do that for now. Better keep this as is.
*/
List *
generate_implied_equalities_for_column(PlannerInfo *root,
@@ -3353,6 +3467,71 @@ generate_implied_equalities_for_column(PlannerInfo *root,
return result;
}
+/*
+ * generate_implied_equalities_for_all_columns
+ * Like generate_implied_equalities_for_column, but returns EC-derived
+ * joinclauses for *every* column of the relation, rather than stopping at
+ * the first column (EquivalenceClass) that yields any clauses.
+ *
+ * generate_implied_equalities_for_column() is designed for parameterized-path
+ * generation, where the goal is to find a single usable joinclause per column
+ * and there is no value in returning clauses for more than one column at a
+ * time. Some callers, however, are interested in joinclauses on all of the
+ * relation's columns simultaneously (for example, the Bloom filter pushdown
+ * logic, which may push down filters derived from several different columns at
+ * once). This variant therefore visits all of the relation's
+ * EquivalenceClasses and accumulates clauses from each.
+ *
+ * As with generate_implied_equalities_for_column(), the result for any single
+ * column is a redundant set of clauses equating that column to each of the
+ * other-relation values it is known to be equal to.
+ *
+ * XXX We don't really need the last two arguments, but we keep this as close
+ * to generate_implied_equalities_for_column as possible.
+ */
+List *
+generate_implied_equalities_for_all_columns(PlannerInfo *root,
+ RelOptInfo *rel,
+ ec_matches_callback_type callback,
+ void *callback_arg,
+ Relids prohibited_rels)
+{
+ List *result = NIL;
+ bool is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ Relids parent_relids;
+ int i;
+
+ /* Should be OK to rely on eclass_indexes */
+ Assert(root->ec_merging_done);
+
+ /* Indexes are available only on base or "other" member relations. */
+ Assert(IS_SIMPLE_REL(rel));
+
+ /* If it's a child rel, we'll need to know what its parent(s) are */
+ if (is_child_rel)
+ parent_relids = find_childrel_parents(root, rel);
+ else
+ parent_relids = NULL; /* not used, but keep compiler quiet */
+
+ i = -1;
+ while ((i = bms_next_member(rel->eclass_indexes, i)) >= 0)
+ {
+ EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
+
+ /* Sanity check eclass_indexes only contain ECs for rel */
+ Assert(is_child_rel || bms_is_subset(rel->relids, cur_ec->ec_relids));
+
+ (void) generate_implied_equalities_for_column_ec(root, rel, cur_ec,
+ callback, callback_arg,
+ prohibited_rels,
+ parent_relids,
+ is_child_rel,
+ &result);
+ }
+
+ return result;
+}
+
/*
* have_relevant_eclass_joinclause
* Detect whether there is an EquivalenceClass that could produce
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 713283a73aa..5e65fda1419 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -869,6 +869,271 @@ get_memoize_path(PlannerInfo *root, RelOptInfo *innerrel,
return NULL;
}
+/*
+ * bloom_join_side_preserved
+ * Can a pushed-down Bloom filter be applied below the given side of a
+ * join of this type without changing the join's result?
+ *
+ * A Bloom filter removes tuples from the scan it is pushed to. That is only
+ * safe on a side whose unmatched tuples the join would drop anyway: dropping
+ * a tuple early then matches the join's behaviour. On a null-extended
+ * (preserved-other-side) input it is unsafe, because removing a tuple there
+ * could suppress, or spuriously emit, null-extended rows.
+ *
+ * This is the path-time counterpart of find_bloom_filter_recipient() in
+ * createplan.c, which descends a plan tree toward the recipient scan: it may
+ * only descend into a join's outer (resp. inner) child when this function
+ * returns true for to_outer = true (resp. false). Keeping the two in sync is
+ * what guarantees that every filter we realize at path time has a reachable
+ * recipient at plan time.
+ */
+bool
+bloom_join_side_preserved(JoinType jointype, bool to_outer)
+{
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ return true;
+ case JOIN_LEFT:
+ case JOIN_SEMI:
+ case JOIN_ANTI:
+ return to_outer;
+ case JOIN_RIGHT:
+ case JOIN_RIGHT_SEMI:
+ case JOIN_RIGHT_ANTI:
+ return !to_outer;
+ case JOIN_FULL:
+ return false;
+ default:
+ return false;
+ }
+}
+
+/*
+ * jointype_realizes_bloom_filter
+ * Can a hash join of this type build and push a Bloom filter to its
+ * outer (probe) side?
+ *
+ * This must match the join-type check in try_push_bloom_filter(), so that a
+ * filter we cost for is actually realized at plan time. Only join types that
+ * drop unmatched outer (probe) tuples are safe, since the filter eliminates
+ * probe tuples lacking an inner match.
+ *
+ * Note JOIN_RIGHT qualifies: it preserves unmatched tuples of the inner (build)
+ * side, not the outer (probe) side, so dropping unmatched probe tuples is still
+ * correct.
+ */
+static bool
+jointype_realizes_bloom_filter(JoinType jointype)
+{
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ case JOIN_RIGHT:
+ case JOIN_SEMI:
+ case JOIN_RIGHT_SEMI:
+ case JOIN_RIGHT_ANTI:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * hashjoin_pushes_filter_to
+ * Would create_hashjoin_plan() be able to push a Bloom filter built from
+ * these hash clauses down to the scan of owner_relid?
+ *
+ * This mirrors try_push_bloom_filter()'s requirement that every hash key on
+ * the outer side be a bare Var of a single base relation. 'outer_relids' is
+ * the set of relids on the outer (probe) side of the join.
+ */
+static bool
+hashjoin_pushes_filter_to(List *hashclauses, Relids outer_relids,
+ Index owner_relid)
+{
+ ListCell *lc;
+
+ if (hashclauses == NIL)
+ return false;
+
+ foreach(lc, hashclauses)
+ {
+ RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+ Node *outerside;
+ Var *var;
+
+ if (!is_opclause(ri->clause) ||
+ list_length(((OpExpr *) ri->clause)->args) != 2)
+ return false;
+
+ /* Pick the side that belongs to the outer relation. */
+ if (bms_is_subset(ri->left_relids, outer_relids))
+ outerside = get_leftop(ri->clause);
+ else if (bms_is_subset(ri->right_relids, outer_relids))
+ outerside = get_rightop(ri->clause);
+ else
+ return false;
+
+ if (!IsA(outerside, Var))
+ return false;
+ var = (Var *) outerside;
+ if (var->varno != owner_relid ||
+ var->varattno <= 0 ||
+ var->varlevelsup != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * compute_join_expected_filters
+ * Determine the expected Bloom filters a prospective join path should
+ * carry, applying the propagation and contradiction rules.
+ *
+ * Each filter expected by an input path is either:
+ *
+ * - propagated upward (its build side is not yet fully joined in), or
+ *
+ * - realized by this join (a hash join that is the filter's source and can
+ * push the filter to its outer side): it is dropped from the result, since
+ * it has now been applied. When 'realized' is non-NULL, such filters are
+ * appended to *realized so the caller can record them on the join path and
+ * later propagate the pushdown decision to the plan, or
+ *
+ * - contradicted: the join is the filter's source but cannot push it (it is
+ * not a suitable hash join). In that case the input path cannot be used
+ * for this join, so *contradicted is set true and NIL is returned.
+ *
+ * 'is_hashjoin'/'hashclauses' describe the join method; hashclauses is only
+ * meaningful for hash joins.
+ */
+static List *
+compute_join_expected_filters(PlannerInfo *root,
+ Path *outer_path, Path *inner_path,
+ JoinType jointype, bool is_hashjoin,
+ List *hashclauses, bool *contradicted,
+ List **realized)
+{
+ List *result = NIL;
+ Relids outer_relids = outer_path->parent->relids;
+ Relids inner_relids = inner_path->parent->relids;
+ Relids join_relids;
+ int pass;
+
+ *contradicted = false;
+ if (realized != NULL)
+ *realized = NIL;
+
+ /* Fast path: neither input expects any filter. */
+ if (outer_path->expected_filters == NIL &&
+ inner_path->expected_filters == NIL)
+ return NIL;
+
+ join_relids = bms_union(outer_relids, inner_relids);
+
+ /* Examine the filters from both inputs. */
+ for (pass = 0; pass < 2; pass++)
+ {
+ List *filters = (pass == 0) ? outer_path->expected_filters
+ : inner_path->expected_filters;
+ ListCell *lc;
+
+ foreach(lc, filters)
+ {
+ ExpectedFilter *f = (ExpectedFilter *) lfirst(lc);
+ bool owner_in_outer;
+ Relids owner_relids;
+ Relids other_relids;
+
+ owner_in_outer = bms_is_member(f->owner_relid, outer_relids);
+ owner_relids = owner_in_outer ? outer_relids : inner_relids;
+ other_relids = owner_in_outer ? inner_relids : outer_relids;
+
+ if (bms_is_subset(f->build_relids, owner_relids))
+ {
+ /*
+ * Build side already sits with the owner; this shouldn't
+ * normally happen (such a filter would have been resolved at a
+ * lower join), but if it does, just propagate it unchanged.
+ *
+ * We still must be able to reach the owner's scan from above,
+ * so the owner has to be on a side this join preserves (see
+ * bloom_join_side_preserved); otherwise the filter could not
+ * be pushed to a recipient and this path must be rejected.
+ */
+ if (!bloom_join_side_preserved(jointype, owner_in_outer))
+ goto contradiction;
+ result = lappend(result, f);
+ }
+ else if (bms_is_subset(f->build_relids, join_relids))
+ {
+ /* This join is the source of the filter. */
+ if (is_hashjoin &&
+ owner_in_outer &&
+ bms_is_subset(f->build_relids, other_relids) &&
+ jointype_realizes_bloom_filter(jointype) &&
+ hashjoin_pushes_filter_to(hashclauses, outer_relids,
+ f->owner_relid))
+ {
+ /* Realized by this hash join; drop from propagation. */
+ if (realized != NULL)
+ *realized = lappend(*realized, f);
+ continue;
+ }
+ else
+ {
+ /* Cannot realize the filter here: reject this path. */
+ goto contradiction;
+ }
+ }
+ else
+ {
+ /*
+ * Build side not yet available; propagate. As above, the
+ * filter can only reach its recipient scan if the owner stays
+ * on a side this join preserves; if not, reject this path so
+ * we never realize a filter with no recipient at plan time.
+ */
+ if (!bloom_join_side_preserved(jointype, owner_in_outer))
+ goto contradiction;
+ result = lappend(result, f);
+ }
+ }
+ }
+
+ bms_free(join_relids);
+ return result;
+
+contradiction:
+ *contradicted = true;
+ if (realized != NULL)
+ {
+ list_free(*realized);
+ *realized = NIL;
+ }
+ bms_free(join_relids);
+ list_free(result);
+ return NIL;
+}
+
+/*
+ * set_join_path_expected_filters
+ * Attach the propagated expected filters to a freshly created join path
+ * and reduce its row estimate to reflect their combined selectivity.
+ */
+static void
+set_join_path_expected_filters(Path *path, List *filters)
+{
+ if (filters == NIL)
+ return;
+
+ path->expected_filters = filters;
+ path->rows = clamp_row_est(path->rows *
+ expected_filters_selectivity(filters));
+}
+
/*
* try_nestloop_path
* Consider a nestloop join path; if it appears useful, push it into
@@ -967,26 +1232,71 @@ try_nestloop_path(PlannerInfo *root,
nestloop_subtype | PGS_CONSIDER_NONPARTIAL,
outer_path, inner_path, extra);
- if (add_path_precheck(joinrel, workspace.disabled_nodes,
- workspace.startup_cost, workspace.total_cost,
- pathkeys, required_outer))
- {
- add_path(joinrel, (Path *)
- create_nestloop_path(root,
- joinrel,
- jointype,
- &workspace,
- extra,
- outer_path,
- inner_path,
- extra->restrictlist,
- pathkeys,
- required_outer));
- }
- else
+ /*
+ * Account for expected Bloom filters carried by the input paths. A
+ * nestloop never builds a Bloom filter, so if it is the source of any
+ * expected filter the path is contradicted and must be rejected;
+ * otherwise the filters propagate to the resulting path.
+ */
{
- /* Waste no memory when we reject a path here */
- bms_free(required_outer);
+ bool contradicted;
+ List *jfilters;
+
+ jfilters = compute_join_expected_filters(root, outer_path, inner_path,
+ jointype, false, NIL,
+ &contradicted, NULL);
+
+ /*
+ * Contradicted means the inner/outer paths expect this join to realize
+ * one of the expected filters, but a nestloop can't do that. So these
+ * input paths are incompatible with a nestloop.
+ */
+ if (contradicted)
+ {
+ bms_free(required_outer);
+ return;
+ }
+
+ /*
+ * If the path expects any filters, it's excluded from the cost pruning
+ * performed by add_path (so don't bother with add_path_precheck either).
+ * Once a path has all filters satisfied (or there were no filters), do
+ * the pruning as usual.
+ *
+ * XXX We don't want the "regular" paths without filters to get removed,
+ * because we need the option to pick from join algorithms. Paths with
+ * filters would likely win (simply because there are fewer rows), but
+ * they only work with hashjoins. However, maybe the hashjoin won't work
+ * for some reason (e.g. it wouldn't fit into work_mem).
+ *
+ * XXX Maybe it'd be cleaner to do this in add_path_precheck (i.e. make
+ * it return true for paths with expected filters).
+ */
+ if (jfilters != NIL ||
+ add_path_precheck(joinrel, workspace.disabled_nodes,
+ workspace.startup_cost, workspace.total_cost,
+ pathkeys, required_outer))
+ {
+ Path *nlpath;
+
+ nlpath = (Path *) create_nestloop_path(root,
+ joinrel,
+ jointype,
+ &workspace,
+ extra,
+ outer_path,
+ inner_path,
+ extra->restrictlist,
+ pathkeys,
+ required_outer);
+ set_join_path_expected_filters(nlpath, jfilters);
+ add_path(joinrel, nlpath);
+ }
+ else
+ {
+ /* Waste no memory when we reject a path here */
+ bms_free(required_outer);
+ }
}
}
@@ -1160,30 +1470,58 @@ try_mergejoin_path(PlannerInfo *root,
outer_presorted_keys,
extra);
- if (add_path_precheck(joinrel, workspace.disabled_nodes,
- workspace.startup_cost, workspace.total_cost,
- pathkeys, required_outer))
- {
- add_path(joinrel, (Path *)
- create_mergejoin_path(root,
- joinrel,
- jointype,
- &workspace,
- extra,
- outer_path,
- inner_path,
- extra->restrictlist,
- pathkeys,
- required_outer,
- mergeclauses,
- outersortkeys,
- innersortkeys,
- outer_presorted_keys));
- }
- else
+ /*
+ * Account for expected Bloom filters carried by the input paths. A
+ * mergejoin never builds a Bloom filter, so it contradicts (and cannot
+ * use) any input path for which it would be the filter's source.
+ * Filter-bearing paths bypass the precheck, since their reduced cost
+ * isn't comparable to ordinary paths.
+ *
+ * XXX see the comments in try_nestloop_path
+ */
{
- /* Waste no memory when we reject a path here */
- bms_free(required_outer);
+ bool contradicted;
+ List *jfilters;
+
+ jfilters = compute_join_expected_filters(root, outer_path, inner_path,
+ jointype, false, NIL,
+ &contradicted, NULL);
+ if (contradicted)
+ {
+ bms_free(required_outer);
+ return;
+ }
+
+ if (jfilters != NIL ||
+ add_path_precheck(joinrel, workspace.disabled_nodes,
+ workspace.startup_cost, workspace.total_cost,
+ pathkeys, required_outer))
+ {
+ Path *mjpath;
+
+ mjpath = (Path *) create_mergejoin_path(root,
+ joinrel,
+ jointype,
+ &workspace,
+ extra,
+ outer_path,
+ inner_path,
+ extra->restrictlist,
+ pathkeys,
+ required_outer,
+ mergeclauses,
+ outersortkeys,
+ innersortkeys,
+ outer_presorted_keys);
+ set_join_path_expected_filters(mjpath, jfilters);
+ add_path(joinrel, mjpath);
+ }
+ else
+ {
+ /* Waste no memory when we reject a path here */
+ bms_free(required_outer);
+ }
+
}
}
@@ -1314,27 +1652,62 @@ try_hashjoin_path(PlannerInfo *root,
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra, false);
- if (add_path_precheck(joinrel, workspace.disabled_nodes,
- workspace.startup_cost, workspace.total_cost,
- NIL, required_outer))
- {
- add_path(joinrel, (Path *)
- create_hashjoin_path(root,
- joinrel,
- jointype,
- &workspace,
- extra,
- outer_path,
- inner_path,
- false, /* parallel_hash */
- extra->restrictlist,
- required_outer,
- hashclauses));
- }
- else
+ /*
+ * Account for expected Bloom filters carried by the input paths. A hash
+ * join builds and pushes down a Bloom filter, so it realizes (and removes
+ * from propagation) any expected filter for which it is the source; other
+ * filters propagate upward. Filter-bearing paths bypass the precheck.
+ */
{
- /* Waste no memory when we reject a path here */
- bms_free(required_outer);
+ bool contradicted;
+ List *jfilters;
+ List *realized;
+
+ jfilters = compute_join_expected_filters(root, outer_path, inner_path,
+ jointype, true, hashclauses,
+ &contradicted, &realized);
+
+ /* XXX Can a hashjoin contradict a filter? Probably not. */
+ if (contradicted)
+ {
+ bms_free(required_outer);
+ return;
+ }
+
+ if (jfilters != NIL ||
+ add_path_precheck(joinrel, workspace.disabled_nodes,
+ workspace.startup_cost, workspace.total_cost,
+ NIL, required_outer))
+ {
+ Path *hjpath;
+
+ hjpath = (Path *) create_hashjoin_path(root,
+ joinrel,
+ jointype,
+ &workspace,
+ extra,
+ outer_path,
+ inner_path,
+ false, /* parallel_hash */
+ extra->restrictlist,
+ required_outer,
+ hashclauses);
+ set_join_path_expected_filters(hjpath, jfilters);
+
+ /*
+ * Record the filters this hash join realizes, so create_hashjoin_plan
+ * can push exactly those down (and no others) at plan-creation time.
+ */
+ ((HashPath *) hjpath)->realized_filters = realized;
+
+ add_path(joinrel, hjpath);
+ }
+ else
+ {
+ /* Waste no memory when we reject a path here */
+ bms_free(required_outer);
+ return;
+ }
}
}
@@ -2316,6 +2689,33 @@ hash_inner_and_outer(PlannerInfo *root,
}
}
+ /*
+ * Also consider outer paths that carry expected Bloom filters. These
+ * are deliberately excluded from cheapest_startup/total_path and from
+ * cheapest_parameterized_paths (see set_cheapest), so we must iterate
+ * the full outer pathlist to find them. A hash join is able to build
+ * and push down the filters, so these paths are useful here even when
+ * they would be contradicted at a non-hash join.
+ */
+ foreach(lc1, outerrel->pathlist)
+ {
+ Path *outerpath = (Path *) lfirst(lc1);
+
+ if (outerpath->expected_filters == NIL)
+ continue;
+
+ if (PATH_PARAM_BY_REL(outerpath, innerrel))
+ continue;
+
+ try_hashjoin_path(root,
+ joinrel,
+ outerpath,
+ cheapest_total_inner,
+ hashclauses,
+ jointype,
+ extra);
+ }
+
/*
* If the joinrel is parallel-safe, we may be able to consider a
* partial hash join.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 7ecb551aae6..51990b98419 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4692,78 +4692,54 @@ create_mergejoin_plan(PlannerInfo *root,
/*
* BLOOM FILTER PUSHDOWN
*
- * When creating a hash join plan, consider building a bloom filter and
- * pushing it down to the outer subtree. For now we only push filters to
- * scan nodes containing all the join keys. When we find such scan node,
- * we append the BloomFilter ID to the node's bloom_filters list, and
- * increment the bloom_consumer_count for the hashjoin.
+ * When a hash join is created as a path, we decide whether it should build a
+ * Bloom filter and push it down to a scan on its outer (probe) side. That
+ * decision - which filters are selective enough to be worth building, and
+ * which scan they can be pushed to - is made entirely while creating paths
+ * (see find_interesting_bloom_filters and compute_join_expected_filters in the
+ * optimizer); and the chosen filters are recorded on the HashPath as its
+ * realized_filters. Here we merely propagate that decision into the plan: we
+ * never reconsider whether a filter is worthwhile, and in particular we never
+ * push a filter that was not selected as interesting when creating paths.
*
- * As setrefs hashn't run yet, the join keys are still the raw Vars.
- * So it's safe to compare var->varno against the scanrelid, and copy
- * the keys verbatim onto the recipient. setrefs will rewrite the Vars
- * later as usual, just like for the recipient's qual.
+ * For each realized filter we locate the scan node for its owner relation in
+ * the outer subtree, append a BloomFilter (built from the hash keys belonging
+ * to that filter) to the scan's bloom_filters list, and increment the
+ * hashjoin's bloom_consumer_count.
*
- * XXX In most cases there'll be only a single consumer node. To get
- * multiple consumers, we'd need either joins on the same keys, or
- * ability to produce filters for subsets of the join keys (for cases
- * where the join is more complex, and does not map to a single scan
- * node directly). Seems like a possible future improvement.
+ * As setrefs hasn't run yet, the hash keys are still the raw Vars. So it's
+ * safe to compare var->varno against the scanrelid, and copy the keys verbatim
+ * onto the recipient. setrefs will rewrite the Vars later as usual, just like
+ * for the recipient's qual.
*
- * XXX Actually, we could have multiple consumer nodes for partitioned
- * tables, where each partition gets a separate scan. Which seems like
- * something we should support.
+ * XXX In most cases there'll be only a single consumer node. To get multiple
+ * consumers, we'd need either joins on the same keys, or ability to produce
+ * filters for subsets of the join keys (for cases where the join is more
+ * complex, and does not map to a single scan node directly). Seems like a
+ * possible future improvement.
*
- * XXX For simplicity, all outer join keys have to be bare Vars (from
- * the same RTE). We could relax this later, and allow joins on more
- * complex expressions. Not sure if that'll erase some of the benefits,
- * which relies on filter probes being much cheaper hashtable probes.
- * It also doesn't seem like a very common case.
+ * XXX Actually, we could have multiple consumer nodes for partitioned tables,
+ * where each partition gets a separate scan. Which seems like something we
+ * should support.
*
- * XXX The recipient node must be one of a small set of scan nodes. We
- * could relax this, and allow pushing to other nodes (e.g. joins or
- * aggregates). Future improvement.
+ * XXX The recipient node must be one of a small set of scan nodes. We could
+ * relax this, and allow pushing to other nodes (e.g. joins or aggregates).
+ * Future improvement.
*
- * XXX We don't currently push the same HashJoin to multiple recipients,
- * but multiple HashJoins may attach a filter to the same scan node.
+ * XXX We don't currently push the same HashJoin to multiple recipients, but
+ * multiple HashJoins may attach a filter to the same scan node.
* --------------------------------------------------------------------------
*/
-/*
- * bloom_join_side_preserved
- * Decide if we can push filter to inner/outer side of a join.
- *
- * Outer joins that emit unmatched outer tuples (LEFT/ANTI/FULL) are
- * skipped: dropping outer tuples there would be incorrect.
- */
-static bool
-bloom_join_side_preserved(JoinType jointype, bool to_outer)
-{
- switch (jointype)
- {
- case JOIN_INNER:
- return true;
- case JOIN_LEFT:
- case JOIN_SEMI:
- case JOIN_ANTI:
- return to_outer;
- case JOIN_RIGHT:
- case JOIN_RIGHT_SEMI:
- case JOIN_RIGHT_ANTI:
- return !to_outer;
- case JOIN_FULL:
- return false;
- default:
- return false;
- }
-}
-
/*
* find_bloom_filter_recipient
* Try to find a scan node to push filter to.
*
* We support pushing filter to a subset of scan nodes (could be extended
* later). We support pushing filters through intermediate nodes (joins,
- * sorts, ...). See bloom_join_side_preserved for joins.
+ * sorts, ...). See bloom_join_side_preserved (joinpath.c) for joins; the
+ * path-time propagation uses the same predicate, so any filter realized while
+ * costing paths is guaranteed a reachable recipient here.
*
* XXX We could push filters through more nodes - e.g. aggregates if the
* hash keys match GROUP BY keys (are a subset of).
@@ -4771,6 +4747,12 @@ bloom_join_side_preserved(JoinType jointype, bool to_outer)
* XXX We could do pushdown to parallel parts of a query. But we'd need
* a different way to communicate if a filter is built etc. (the worker
* won't have access to the hashjoin state).
+ *
+ * XXX Not sure this handles partitioned tables correctly. Those will be below
+ * Append node, and we don't push through those. But the scans will still expect
+ * the filter, I think. Even if we pushed through Append node, it probably won't
+ * work because we expect a single consumer. But we'll have one consumer per
+ * scan of a partition.
*/
static Plan *
find_bloom_filter_recipient(Plan *plan, Index target_relid)
@@ -4842,142 +4824,92 @@ find_bloom_filter_recipient(Plan *plan, Index target_relid)
/*
* try_push_bloom_filter
- * Attempt to pushdown a bloom filter for the current hashjoin.
+ * Push down the bloom filter the planner decided this hashjoin should
+ * build, recording it on the recipient scan node.
*
- * The filter pushdown happens during plan creation, i.e. after the plan was
- * already selected. That is not entirely optimal, and it has a couple of
- * annoying consequences.
+ * 'realized_filters' is the list of ExpectedFilter nodes the HashPath was
+ * found to realize while creating paths. We do not reconsider that decision
+ * here; if it is empty, this hashjoin pushes nothing. Otherwise we turn it
+ * into a concrete BloomFilter attached to the scan of the owner relation.
*
- * The main disadvantage is that injecting the filter to a scan node may
- * significantly alter the number of tuples produced by that scan node. If a
- * filter eliminates 99% of the rows, the scan produces 1/100 of the rows it
- * was planned with. It would not affect the scan itself, but if there are
- * other nodes (between the scan and the join), maybe we'd have planned them
- * differently if we knew about the lower cardinality?
+ * A hash join builds a single bloom filter, populated with the combined hash
+ * value of all of its hash keys (see ExecHashTableInsert / bloom_add_element
+ * in nodeHash.c). The recipient must therefore probe with the matching full
+ * set of outer hash keys, so we build the filter from all of them. All
+ * realized filters of one hash join share the same owner relation (the outer
+ * side of every hash clause is a bare Var of that relation), so a single
+ * pushed-down filter covers them all.
*
- * Similarly, it's confusing in the explain. That is, we'll get "rows=N"
- * with the planner cardinality (before the filter was pushed down), but
- * then in EXPLAIN ANALYZE it'll get much lower values. It'd be easy to
- * confuse with inaccurate estimates.
- *
- * It'd be better to know about the filter earlier, when constructing the scan
- * path. But that's not quite feasible with our bottom-up planner. When planing
- * the scan, we don't know which of the joins above it will be hashjoins, or
- * if it can pushdown the filter. We'd have to speculate, or maybe build more
- * paths with/without expectation of the bloom filter pushdown. But that seems
- * not great, as it'd add overhead for everyone.
+ * Note the pushdown still happens during plan creation, i.e. after the plan was
+ * already selected. The selectivity of the filter was, however, accounted for
+ * while creating paths (the affected scan paths carry reduced row estimates),
+ * so the plan-time row counts already reflect the expected elimination.
*/
static void
-try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan)
+try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan,
+ List *realized_filters)
{
- List *hashkeys = hj->hashkeys;
- List *hashops = hj->hashoperators;
- List *hashcolls = hj->hashcollations;
- ListCell *lc;
- Index target_relid = 0;
+ ExpectedFilter *f;
+ Index owner_relid;
Plan *recipient;
BloomFilter *bf;
- /* bail out if feature disabled. */
- if (!enable_hashjoin_bloom)
- return;
-
- /* XXX shouldn't really happen, I think */
- if (hashkeys == NIL)
+ /* Nothing to do unless the planner chose to realize a filter here. */
+ if (realized_filters == NIL)
return;
- Assert(list_length(hashkeys) == list_length(hashops));
- Assert(list_length(hashkeys) == list_length(hashcolls));
-
/*
- * Pushdown is unsafe for join types that emit unmatched outer tuples
- * (LEFT/ANTI/FULL): we'd risk dropping outer tuples the join would
- * otherwise have emitted (possibly NULL-extended).
+ * The feature must have been enabled when paths were built; otherwise no
+ * filter would have been realized.
*/
- switch (hj->join.jointype)
- {
- case JOIN_INNER:
- case JOIN_RIGHT:
- case JOIN_SEMI:
- case JOIN_RIGHT_SEMI:
- case JOIN_RIGHT_ANTI:
- /* these join types are OK */
- break;
- default:
- return;
- }
+ Assert(enable_hashjoin_bloom);
+ Assert(hj->hashkeys != NIL);
+ Assert(list_length(hj->hashkeys) == list_length(hj->hashoperators));
+ Assert(list_length(hj->hashkeys) == list_length(hj->hashcollations));
/*
- * All hashkeys must be bare Vars referencing the same base RTE.
- *
- * XXX We could be a bit less strict, and check that at least some of the
- * hashkeys are bare Vars, not all of them. So with joins on multiple
- * expressions we'd have better chance to push a filter down. Doesn't
- * seem worth it, at least for now.
+ * All realized filters share the same owner relation (every hash clause's
+ * outer side is a bare Var of that relation).
*/
- foreach(lc, hashkeys)
- {
- Node *k = (Node *) lfirst(lc);
- Var *var;
-
- /* not a plain Var */
- if (!IsA(k, Var))
- return;
+ f = (ExpectedFilter *) linitial(realized_filters);
+ owner_relid = f->owner_relid;
- var = (Var *) k;
+#ifdef USE_ASSERT_CHECKING
+ {
+ ListCell *lc;
- /*
- * Reject outer references, whole-row or system columns, and
- * special varnos (not sure we can get them here, though).
- */
- if ((var->varlevelsup != 0) ||
- (var->varattno <= 0) ||
- IS_SPECIAL_VARNO(var->varno))
- return;
-
- /* make sure all the vars are for the same relid */
- if (target_relid == 0)
- target_relid = var->varno;
- else if (var->varno != target_relid)
- return;
+ foreach(lc, realized_filters)
+ Assert(((ExpectedFilter *) lfirst(lc))->owner_relid == owner_relid);
}
-
- /* should have found at least one var */
- Assert(target_relid != 0);
+#endif
/*
- * See if we can find the scan node for target_relid. It certainly is
- * in the plan somewhere, but it may not be able to pushdown the filter
- * to it (because of a join or so).
+ * Locate the scan node for the owner relation in the outer subtree. The
+ * path machinery guaranteed such a recipient exists (the filter could not
+ * have been realized otherwise), but stay defensive.
*/
- recipient = find_bloom_filter_recipient(outer_plan, target_relid);
+ recipient = find_bloom_filter_recipient(outer_plan, owner_relid);
if (recipient == NULL)
return;
/*
- * If we found a recipient, assign the filter an ID. We'll use it to
- * register the filter in ExecRegisterBloomFilterProducer, and then
- * for lookups in LookupBloomFilterProducer during execution.
+ * Assign the filter an ID. We'll use it to register the filter in
+ * ExecRegisterBloomFilterProducer, and then for lookups in
+ * LookupBloomFilterProducer during execution.
*
* XXX We can't use plan_node_id, as it's not assigned yet, that only
- * happens in set_plan_refs. Also, if we ever allow multiple filters
- * per hashtable (e.g. for different subsets of keys), it's not work.
+ * happens in set_plan_refs.
*/
hj->bloom_filter_id = ++root->glob->lastBloomFilterId;
bf = makeNode(BloomFilter);
- bf->filter_exprs = (List *) copyObject(hashkeys);
- bf->hashops = list_copy(hashops);
- bf->hashcollations = list_copy(hashcolls);
+ bf->filter_exprs = (List *) copyObject(hj->hashkeys);
+ bf->hashops = list_copy(hj->hashoperators);
+ bf->hashcollations = list_copy(hj->hashcollations);
bf->producer_id = hj->bloom_filter_id;
recipient->bloom_filters = lappend(recipient->bloom_filters, bf);
- /*
- * XXX We've manged to push the filter to the scan node, but maybe
- * we should wait with updating bloom_consumer_count when it actually
- * initializes the filters in ExecInit()?
- */
hj->bloom_consumer_count++;
}
@@ -5152,11 +5084,14 @@ create_hashjoin_plan(PlannerInfo *root,
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
/*
- * Try to push the bloom filter for the hashtable down to nodes in the outer
- * subtree. If a suitable scan node exists, add the filter to bloom_filters,
- * and bump our bloom_consumer_count.
+ * Propagate the bloom filter pushdown decision made while creating paths:
+ * if this hash join was found to realize one or more bloom filters, push
+ * the corresponding filter down to the recipient scan in the outer
+ * subtree, and bump our bloom_consumer_count. No filter that was not
+ * selected as interesting during path creation is pushed here.
*/
- try_push_bloom_filter(root, join_plan, outer_plan);
+ try_push_bloom_filter(root, join_plan, outer_plan,
+ best_path->realized_filters);
return join_plan;
}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 73518c8f870..9cd9188a1cf 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -286,6 +286,16 @@ set_cheapest(RelOptInfo *parent_rel)
Path *path = (Path *) lfirst(p);
int cmp;
+ /*
+ * Paths that expect a pushed-down Bloom filter are speculative: their
+ * rows/cost estimates assume a hash join above will build and push a
+ * filter to them. They must never be chosen as the cheapest startup,
+ * total, or parameterized path; they are only consumed explicitly by
+ * join path generation (see joinpath.c). Skip them here.
+ */
+ if (path->expected_filters != NIL)
+ continue;
+
if (path->param_info)
{
/* Parameterized path, so add it to parameterized_paths */
@@ -383,6 +393,129 @@ set_cheapest(RelOptInfo *parent_rel)
parent_rel->cheapest_parameterized_paths = parameterized_paths;
}
+/*
+ * expected_filters_equal
+ * Return true if the two lists of ExpectedFilter nodes denote the same
+ * set of expected Bloom filters (order-independent).
+ */
+bool
+expected_filters_equal(List *a, List *b)
+{
+ ListCell *lc;
+
+ if (a == NIL && b == NIL)
+ return true;
+ if (list_length(a) != list_length(b))
+ return false;
+
+ foreach(lc, a)
+ {
+ ExpectedFilter *fa = (ExpectedFilter *) lfirst(lc);
+ ListCell *lc2;
+ bool found = false;
+
+ foreach(lc2, b)
+ {
+ ExpectedFilter *fb = (ExpectedFilter *) lfirst(lc2);
+
+ if (fa->owner_relid == fb->owner_relid &&
+ bms_equal(fa->build_relids, fb->build_relids) &&
+ equal(fa->clauses, fb->clauses))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * expected_filters_selectivity
+ * Combined surviving fraction of a set of expected filters, assuming
+ * independence. Returns a value in (0, 1].
+ */
+double
+expected_filters_selectivity(List *filters)
+{
+ double sel = 1.0;
+ ListCell *lc;
+
+ foreach(lc, filters)
+ {
+ ExpectedFilter *f = (ExpectedFilter *) lfirst(lc);
+
+ sel *= f->selectivity;
+ }
+
+ /* clamp to a sane range */
+ if (sel < 0.0)
+ sel = 0.0;
+ if (sel > 1.0)
+ sel = 1.0;
+
+ return sel;
+}
+
+/*
+ * create_filtered_scan_path
+ * Build a copy of a base-relation scan path that additionally expects the
+ * given set of pushed-down Bloom filters.
+ *
+ * The clone shares all substructure with the original path (parent,
+ * pathtarget, clauses, etc.); only the rows estimate is reduced to reflect
+ * the filters' combined selectivity, and expected_filters is set. This is
+ * safe because create_plan() treats the clone identically to the original
+ * (it ignores expected_filters), and add_path() may freely pfree the clone.
+ *
+ * Only the plain scan path node types that can receive a pushed-down filter
+ * are supported (matching find_bloom_filter_recipient in createplan.c).
+ * Returns NULL for unsupported path types.
+ *
+ * XXX This should probably adjust the CPU cost in some way. It assumes the
+ * filter checks are free, which does not seem right.
+ */
+Path *
+create_filtered_scan_path(PlannerInfo *root, Path *subpath, List *filters)
+{
+ Path *newpath;
+ size_t sz;
+
+ switch (nodeTag(subpath))
+ {
+ case T_Path:
+ /* plain seqscan/samplescan etc. */
+ sz = sizeof(Path);
+ break;
+ case T_IndexPath:
+ sz = sizeof(IndexPath);
+ break;
+ case T_BitmapHeapPath:
+ sz = sizeof(BitmapHeapPath);
+ break;
+ case T_TidPath:
+ sz = sizeof(TidPath);
+ break;
+ case T_TidRangePath:
+ sz = sizeof(TidRangePath);
+ break;
+ default:
+ /* unsupported scan path type */
+ return NULL;
+ }
+
+ newpath = (Path *) palloc(sz);
+ memcpy(newpath, subpath, sz);
+
+ newpath->expected_filters = filters;
+ newpath->rows = clamp_row_est(subpath->rows *
+ expected_filters_selectivity(filters));
+
+ return newpath;
+}
+
/*
* add_path
* Consider a potential implementation path for the specified parent rel,
@@ -485,6 +618,17 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
PathKeysComparison keyscmp;
BMS_Comparison outercmp;
+ /*
+ * Paths carrying different sets of expected Bloom filters serve
+ * different purposes (each may be consumed by a different parent join,
+ * or none at all), and their cost/row estimates aren't directly
+ * comparable. So if the two paths don't expect the same filters, keep
+ * both and don't let either dominate the other.
+ */
+ if (!expected_filters_equal(new_path->expected_filters,
+ old_path->expected_filters))
+ continue;
+
/*
* Do a fuzzy cost comparison with standard fuzziness limit.
*/
@@ -702,6 +846,20 @@ add_path_precheck(RelOptInfo *parent_rel, int disabled_nodes,
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
+ /*
+ * Paths carrying expected Bloom filters serve a different purpose and
+ * are not directly cost-comparable with ordinary paths, exactly as in
+ * add_path (which keeps both when the expected filter sets differ).
+ * The candidates submitted to this precheck never carry expected
+ * filters of their own, so any filter-bearing old path is a
+ * non-comparable speculative path and must not be allowed to dominate
+ * (and thereby suppress) the new path. Skipping them here also
+ * guarantees that a join relation always retains at least one ordinary,
+ * filter-free path to serve as cheapest_total_path.
+ */
+ if (old_path->expected_filters != NIL)
+ continue;
+
/*
* Since the pathlist is sorted by disabled_nodes and then by
* total_cost, we can stop looking once we reach a path with more
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index c9dcb294d4d..9ea40bcb798 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -380,6 +380,26 @@
max => 'BLCKSZ',
},
+{ name => 'bloom_filter_pushdown_max', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER',
+ short_desc => 'Maximum number of pushed-down hash join bloom filters considered per scan.',
+ long_desc => 'Bounds how many interesting bloom filters the planner enumerates subsets of when building filter-aware scan paths.',
+ flags => 'GUC_EXPLAIN',
+ variable => 'bloom_filter_pushdown_max',
+ boot_val => '3',
+ min => '0',
+ max => '10',
+},
+
+{ name => 'bloom_filter_pushdown_threshold', type => 'real', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER',
+ short_desc => 'Minimum fraction of tuples a pushed-down hash join bloom filter must be expected to eliminate.',
+ long_desc => 'A bloom filter is only considered during planning if it is expected to discard at least this fraction of the scanned tuples.',
+ flags => 'GUC_EXPLAIN',
+ variable => 'bloom_filter_pushdown_threshold',
+ boot_val => '0.3',
+ min => '0.0',
+ max => '1.0',
+},
+
{ name => 'bonjour', type => 'bool', context => 'PGC_POSTMASTER', group => 'CONN_AUTH_SETTINGS',
short_desc => 'Enables advertising the server via Bonjour.',
variable => 'enable_bonjour',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 34f98b42ff6..faf01bf7e43 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -485,6 +485,8 @@
# - Other Planner Options -
+#bloom_filter_pushdown_max = 3 # range 0-10
+#bloom_filter_pushdown_threshold = 0.3 # range 0.0-1.0
#default_statistics_target = 100 # range 1-10000
#constraint_exclusion = partition # on, off, or partition
#cursor_tuple_fraction = 0.1 # range 0.0-1.0
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 69f9ad2d5e3..f2f21f4ade1 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1836,6 +1836,54 @@ typedef struct GroupByOrdering
List *clauses;
} GroupByOrdering;
+/*
+ * ExpectedFilter
+ *
+ * Represents the planner's assumption that a scan path will receive a Bloom
+ * filter pushed down at plan-creation time by some hash join above it (see
+ * the bloom filter pushdown logic in createplan.c and nodeHashjoin.c). When
+ * a scan participates in a hashjoinable equality join, the hash join may build
+ * a Bloom filter on the inner ("build") side keys and push it to the scan on
+ * the outer ("probe") side, eliminating outer tuples that cannot match.
+ *
+ * Because that pushdown happens after path selection, the row/cost estimates
+ * of the affected paths would normally ignore the filter's selectivity. To
+ * account for it during planning, we generate additional scan paths that carry
+ * one or more ExpectedFilter nodes and whose rows/cost reflect the expected
+ * elimination. These filters are propagated up the join tree much like
+ * pathkeys, until the hash join that is their "source" realizes them.
+ *
+ * 'owner_relid' is the base relation the filter would be pushed down to.
+ * 'build_relids' is the set of base relids on the other (build) side of the
+ * join clause(s); the filter is "sourced" by the join that first brings
+ * those relids together with the owner.
+ * 'clauses' is the list of hashjoinable equality RestrictInfos defining the
+ * filter keys (the owner side of each clause is a bare Var of owner_relid).
+ * 'selectivity' is the expected surviving fraction of owner rows (in (0,1]).
+ *
+ * XXX The "owner_relid" may be a bit misleading, particularly if we allow
+ * pushing a filter to multiple nodes (e.g. scans on a partition). In that case
+ * we'd have multiple "owners", but ownership suggests there's just one. And
+ * some places already use "consumer" when referencing to the scan nodes, so
+ * maybe we should just use that?
+ *
+ * XXX What if we allow pushdown to non-scan nodes, e.g. above a join when
+ * pushdown to a scan is not possible (e.g. because the join clause is complex
+ * and references multiple relations)? Or maybe we could push to ForeignScan,
+ * and I'm not sure if those have a valid relid in all cases.
+ */
+typedef struct ExpectedFilter
+{
+ pg_node_attr(no_read, no_query_jumble)
+
+ NodeTag type;
+
+ Index owner_relid;
+ Relids build_relids;
+ List *clauses pg_node_attr(copy_as_scalar, equal_as_scalar);
+ Selectivity selectivity;
+} ExpectedFilter;
+
/*
* VolatileFunctionStatus -- allows nodes to cache their
* contain_volatile_functions properties. VOLATILITY_UNKNOWN means not yet
@@ -2012,6 +2060,14 @@ typedef struct Path
/* sort ordering of path's output; a List of PathKey nodes; see above */
List *pathkeys;
+
+ /*
+ * Bloom filters this path expects to receive from some hash join above
+ * it (a List of ExpectedFilter nodes; see below). An empty list means
+ * the path makes no such assumption. The path's rows/cost estimates
+ * already reflect the expected selectivity of these filters.
+ */
+ List *expected_filters;
} Path;
/* Macro for extracting a path's parameterization relids; beware double eval */
@@ -2493,6 +2549,16 @@ typedef struct HashPath
List *path_hashclauses; /* join clauses used for hashing */
int num_batches; /* number of batches expected */
Cardinality inner_rows_total; /* total inner rows expected */
+
+ /*
+ * Bloom filters this hash join "realizes": expected filters (see
+ * ExpectedFilter) carried by an input path for which this hash join is the
+ * source, and which it can push down to the scan of the owner relation.
+ * The pushdown decision is made here, while building paths; create_plan()
+ * merely propagates it to the plan (see create_hashjoin_plan). NIL means
+ * this hash join pushes no filter.
+ */
+ List *realized_filters;
} HashPath;
/*
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 7339979c008..35ef6bddab2 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -71,6 +71,8 @@ extern PGDLLIMPORT bool enable_parallel_hash;
extern PGDLLIMPORT bool enable_partition_pruning;
extern PGDLLIMPORT bool enable_presorted_aggregate;
extern PGDLLIMPORT bool enable_async_append;
+extern PGDLLIMPORT double bloom_filter_pushdown_threshold;
+extern PGDLLIMPORT int bloom_filter_pushdown_max;
extern PGDLLIMPORT int constraint_exclusion;
extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index e8db321f92b..0dcae2ae3b3 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -64,6 +64,11 @@ extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
int disabled_nodes, Cost startup_cost,
Cost total_cost, List *pathkeys);
+extern bool expected_filters_equal(List *a, List *b);
+extern double expected_filters_selectivity(List *filters);
+extern Path *create_filtered_scan_path(PlannerInfo *root, Path *subpath,
+ List *filters);
+
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer, int parallel_workers);
extern Path *create_samplescan_path(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 17f2099ec3b..636761f2e94 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -104,6 +104,7 @@ extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, SpecialJoinInfo *sjinfo,
List *restrictlist);
+extern bool bloom_join_side_preserved(JoinType jointype, bool to_outer);
/*
* joinrels.c
@@ -198,6 +199,11 @@ extern List *generate_implied_equalities_for_column(PlannerInfo *root,
ec_matches_callback_type callback,
void *callback_arg,
Relids prohibited_rels);
+extern List *generate_implied_equalities_for_all_columns(PlannerInfo *root,
+ RelOptInfo *rel,
+ ec_matches_callback_type callback,
+ void *callback_arg,
+ Relids prohibited_rels);
extern bool have_relevant_eclass_joinclause(PlannerInfo *root,
RelOptInfo *rel1, RelOptInfo *rel2);
extern bool has_relevant_eclass_joinclause(PlannerInfo *root,
diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out
index bbdb9526471..90578629061 100644
--- a/src/test/regress/expected/eager_aggregate.out
+++ b/src/test/regress/expected/eager_aggregate.out
@@ -147,15 +147,15 @@ GROUP BY t1.a ORDER BY t1.a;
Group Key: t2.b
-> Hash Join
Output: t2.c, t2.b, t3.c
- Hash Cond: (t3.a = t2.a)
- -> Seq Scan on public.eager_agg_t3 t3
- Output: t3.a, t3.b, t3.c
- Bloom Filter 1: keys=(t3.a)
+ Hash Cond: (t2.a = t3.a)
+ -> Seq Scan on public.eager_agg_t2 t2
+ Output: t2.a, t2.b, t2.c
+ Bloom Filter 1: keys=(t2.a)
-> Hash
- Output: t2.c, t2.b, t2.a
+ Output: t3.c, t3.a
Bloom Filter 1
- -> Seq Scan on public.eager_agg_t2 t2
- Output: t2.c, t2.b, t2.a
+ -> Seq Scan on public.eager_agg_t3 t3
+ Output: t3.c, t3.a
(29 rows)
SELECT t1.a, avg(t2.c + t3.c)
@@ -209,15 +209,15 @@ GROUP BY t1.a ORDER BY t1.a;
Sort Key: t2.b
-> Hash Join
Output: t2.c, t2.b, t3.c
- Hash Cond: (t3.a = t2.a)
- -> Seq Scan on public.eager_agg_t3 t3
- Output: t3.a, t3.b, t3.c
- Bloom Filter 1: keys=(t3.a)
+ Hash Cond: (t2.a = t3.a)
+ -> Seq Scan on public.eager_agg_t2 t2
+ Output: t2.a, t2.b, t2.c
+ Bloom Filter 1: keys=(t2.a)
-> Hash
- Output: t2.c, t2.b, t2.a
+ Output: t3.c, t3.a
Bloom Filter 1
- -> Seq Scan on public.eager_agg_t2 t2
- Output: t2.c, t2.b, t2.a
+ -> Seq Scan on public.eager_agg_t3 t3
+ Output: t3.c, t3.a
(32 rows)
SELECT t1.a, avg(t2.c + t3.c)
@@ -261,16 +261,14 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
- Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
- Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(20 rows)
+(18 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -309,13 +307,11 @@ GROUP BY t2.b ORDER BY t2.b;
Hash Cond: (t2.b = t1.b)
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
- Bloom Filter 1: keys=(t2.b)
-> Hash
Output: t1.b
- Bloom Filter 1
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.b
-(17 rows)
+(15 rows)
SELECT t2.b, avg(t2.c)
FROM eager_agg_t1 t1
@@ -460,12 +456,12 @@ GROUP BY t1.a ORDER BY t1.a;
-> Sort
Sort Key: t1.a
-> Hash Join
- Hash Cond: (t2.b = t1.b)
- -> Seq Scan on eager_agg_t2 t2
+ Hash Cond: (t1.b = t2.b)
+ -> Seq Scan on eager_agg_t1 t1
Bloom Filter 1: keys=(b)
-> Hash
Bloom Filter 1
- -> Seq Scan on eager_agg_t1 t1
+ -> Seq Scan on eager_agg_t2 t2
(11 rows)
EXPLAIN (COSTS OFF)
@@ -480,12 +476,12 @@ GROUP BY t1.a ORDER BY t1.a;
-> Sort
Sort Key: t1.a
-> Hash Join
- Hash Cond: (t2.b = t1.b)
- -> Seq Scan on eager_agg_t2 t2
+ Hash Cond: (t1.b = t2.b)
+ -> Seq Scan on eager_agg_t1 t1
Bloom Filter 1: keys=(b)
-> Hash
Bloom Filter 1
- -> Seq Scan on eager_agg_t1 t1
+ -> Seq Scan on eager_agg_t2 t2
(11 rows)
-- Eager aggregation must not push a partial aggregate onto the inner side of a
@@ -552,14 +548,16 @@ GROUP BY t2.b ORDER BY t2.b;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL count(*)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(18 rows)
+(20 rows)
SELECT t2.b, count(*)
FROM eager_agg_t2 t2
@@ -621,10 +619,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
- Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -638,10 +634,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
- Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -655,16 +649,14 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
- Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x, t1_2.y
-(55 rows)
+(49 rows)
SELECT t1.x, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -709,10 +701,8 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
- Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -726,10 +716,8 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
- Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -743,16 +731,14 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
- Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.y, t1_2.x
-(55 rows)
+(49 rows)
SELECT t2.y, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -799,10 +785,8 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.x, t2.y
- Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.x)), (PARTIAL count(*)), (PARTIAL avg(t1.x))
- Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.x), PARTIAL count(*), PARTIAL avg(t1.x)
Group Key: t1.x
@@ -813,10 +797,8 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.x, t2_1.y
- Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.x)), (PARTIAL count(*)), (PARTIAL avg(t1_1.x))
- Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.x), PARTIAL count(*), PARTIAL avg(t1_1.x)
Group Key: t1_1.x
@@ -827,16 +809,14 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.x, t2_2.y
- Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.x)), (PARTIAL count(*)), (PARTIAL avg(t1_2.x))
- Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.x), PARTIAL count(*), PARTIAL avg(t1_2.x)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x
-(50 rows)
+(44 rows)
SELECT t2.x, sum(t1.x), count(*)
FROM eager_agg_tab1 t1
@@ -878,10 +858,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab1_p1 t1
Output: t1.x
- Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y)))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y))
Group Key: t2.x
@@ -890,10 +868,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab1_p1 t2
Output: t2.y, t2.x
- Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
- Bloom Filter 1
-> Seq Scan on public.eager_agg_tab1_p1 t3
Output: t3.y, t3.x
-> Finalize HashAggregate
@@ -904,10 +880,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab1_p2 t1_1
Output: t1_1.x
- Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y)))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y))
Group Key: t2_1.x
@@ -916,10 +890,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab1_p2 t2_1
Output: t2_1.y, t2_1.x
- Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
- Bloom Filter 3
-> Seq Scan on public.eager_agg_tab1_p2 t3_1
Output: t3_1.y, t3_1.x
-> Finalize HashAggregate
@@ -930,10 +902,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x
- Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y)))
- Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y))
Group Key: t2_2.x
@@ -942,13 +912,11 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab1_p3 t2_2
Output: t2_2.y, t2_2.x
- Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
- Bloom Filter 5
-> Seq Scan on public.eager_agg_tab1_p3 t3_2
Output: t3_2.y, t3_2.x
-(82 rows)
+(70 rows)
SELECT t1.x, sum(t2.y + t3.y)
FROM eager_agg_tab1 t1
@@ -1118,10 +1086,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
- Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -1135,10 +1101,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
- Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -1152,16 +1116,14 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
- Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x, t1_2.y
-(55 rows)
+(49 rows)
SELECT t1.x, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -1224,10 +1186,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
- Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1241,10 +1201,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
- Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1258,10 +1216,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
- Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1275,10 +1231,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
- Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1292,16 +1246,14 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
- Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
- Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(89 rows)
+(79 rows)
SELECT t1.x, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1362,10 +1314,8 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.y, t1.x
- Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1376,10 +1326,8 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.y, t1_1.x
- Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1390,10 +1338,8 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.y, t1_2.x
- Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1404,10 +1350,8 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.y, t1_3.x
- Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1418,16 +1362,14 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.y, t1_4.x
- Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
- Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(77 rows)
+(67 rows)
SELECT t1.y, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1489,10 +1431,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
- Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*)
Group Key: t2.x
@@ -1501,10 +1441,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t2
Output: t2.y, t2.x
- Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
- Bloom Filter 1
-> Seq Scan on public.eager_agg_tab_ml_p1 t3
Output: t3.y, t3.x
-> Finalize HashAggregate
@@ -1515,10 +1453,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
- Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*)
Group Key: t2_1.x
@@ -1527,10 +1463,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1
Output: t2_1.y, t2_1.x
- Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
- Bloom Filter 3
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1
Output: t3_1.y, t3_1.x
-> Finalize HashAggregate
@@ -1541,10 +1475,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
- Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*))
- Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*)
Group Key: t2_2.x
@@ -1553,10 +1485,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2
Output: t2_2.y, t2_2.x
- Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
- Bloom Filter 5
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2
Output: t3_2.y, t3_2.x
-> Finalize HashAggregate
@@ -1567,10 +1497,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
- Bloom Filter 8: keys=(t1_3.x)
-> Hash
Output: t2_3.x, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*))
- Bloom Filter 8
-> Partial HashAggregate
Output: t2_3.x, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*)
Group Key: t2_3.x
@@ -1579,10 +1507,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_3.x = t3_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3
Output: t2_3.y, t2_3.x
- Bloom Filter 7: keys=(t2_3.x)
-> Hash
Output: t3_3.y, t3_3.x
- Bloom Filter 7
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3
Output: t3_3.y, t3_3.x
-> Finalize HashAggregate
@@ -1593,10 +1519,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
- Bloom Filter 10: keys=(t1_4.x)
-> Hash
Output: t2_4.x, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*))
- Bloom Filter 10
-> Partial HashAggregate
Output: t2_4.x, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*)
Group Key: t2_4.x
@@ -1605,13 +1529,11 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_4.x = t3_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
- Bloom Filter 9: keys=(t2_4.x)
-> Hash
Output: t3_4.y, t3_4.x
- Bloom Filter 9
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4
Output: t3_4.y, t3_4.x
-(134 rows)
+(114 rows)
SELECT t1.x, sum(t2.y + t3.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1673,10 +1595,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
- Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.y, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.y, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*)
Group Key: t2.x, t3.y, t3.x
@@ -1685,10 +1605,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t2
Output: t2.y, t2.x
- Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
- Bloom Filter 1
-> Seq Scan on public.eager_agg_tab_ml_p1 t3
Output: t3.y, t3.x
-> Hash Join
@@ -1696,10 +1614,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
- Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.y, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.y, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*)
Group Key: t2_1.x, t3_1.y, t3_1.x
@@ -1708,10 +1624,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1
Output: t2_1.y, t2_1.x
- Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
- Bloom Filter 3
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1
Output: t3_1.y, t3_1.x
-> Hash Join
@@ -1719,10 +1633,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
- Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.y, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*))
- Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.y, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*)
Group Key: t2_2.x, t3_2.y, t3_2.x
@@ -1731,10 +1643,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2
Output: t2_2.y, t2_2.x
- Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
- Bloom Filter 5
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2
Output: t3_2.y, t3_2.x
-> Hash Join
@@ -1742,10 +1652,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
- Bloom Filter 8: keys=(t1_3.x)
-> Hash
Output: t2_3.x, t3_3.y, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*))
- Bloom Filter 8
-> Partial HashAggregate
Output: t2_3.x, t3_3.y, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*)
Group Key: t2_3.x, t3_3.y, t3_3.x
@@ -1754,10 +1662,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_3.x = t3_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3
Output: t2_3.y, t2_3.x
- Bloom Filter 7: keys=(t2_3.x)
-> Hash
Output: t3_3.y, t3_3.x
- Bloom Filter 7
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3
Output: t3_3.y, t3_3.x
-> Hash Join
@@ -1765,10 +1671,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
- Bloom Filter 10: keys=(t1_4.x)
-> Hash
Output: t2_4.x, t3_4.y, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*))
- Bloom Filter 10
-> Partial HashAggregate
Output: t2_4.x, t3_4.y, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*)
Group Key: t2_4.x, t3_4.y, t3_4.x
@@ -1777,13 +1681,11 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_4.x = t3_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
- Bloom Filter 9: keys=(t2_4.x)
-> Hash
Output: t3_4.y, t3_4.x
- Bloom Filter 9
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4
Output: t3_4.y, t3_4.x
-(122 rows)
+(102 rows)
SELECT t3.y, sum(t2.y + t3.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1846,10 +1748,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
- Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1863,10 +1763,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
- Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1880,10 +1778,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
- Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1897,10 +1793,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
- Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1914,16 +1808,14 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
- Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
- Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(89 rows)
+(79 rows)
SELECT t1.x, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 70d986e8ab0..14fbdcc645a 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -250,8 +250,8 @@ SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'U
SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g;
a | customer_name | cid | order_id
---+---------------+-----+----------
- 1 | customer1 | 1 | 1
2 | customer1 | 1 | 1
+ 1 | customer1 | 1 | 1
(2 rows)
-- lateral reference with multi-label pattern, which is rewritten as UNION of
@@ -407,8 +407,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname, a.vprop1)
SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, b.ename AS conn, c.vname AS dest, c.lprop1, c.vprop2, c.vprop1));
src | conn | dest | lprop1 | vprop2 | vprop1
-----+------+------+----------+--------+--------
- v12 | e122 | v21 | vl2_prop | 1100 | 1010
v11 | e121 | v22 | vl2_prop | 1200 | 1020
+ v12 | e122 | v21 | vl2_prop | 1100 | 1010
v11 | e131 | v33 | vl3_prop | | 2030
v11 | e132 | v31 | vl3_prop | | 2010
(4 rows)
@@ -417,16 +417,16 @@ SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS
SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-[conn]-(v2) COLUMNS (v1.vname AS v1name, conn.ename AS cname, v2.vname AS v2name));
v1name | cname | v2name
--------+-------+--------
- v21 | e122 | v12
v22 | e121 | v11
+ v21 | e122 | v12
v22 | e231 | v32
(3 rows)
SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-(v2) COLUMNS (v1.vname AS v1name, v2.vname AS v2name));
v1name | v2name
--------+--------
- v21 | v12
v22 | v11
+ v21 | v12
v22 | v32
(3 rows)
@@ -487,8 +487,8 @@ LINE 1: SELECT * FROM GRAPH_TABLE (g1 MATCH (WHERE b.eprop1 = 10001)...
SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname, src.vprop1 AS svp1, src.vprop2 AS svp2, src.lprop1 AS slp1, dest.vprop1 AS dvp1, dest.vprop2 AS dvp2, dest.lprop1 AS dlp1, conn.eprop1 AS cep1, conn.lprop2 AS clp2));
svname | cename | dvname | svp1 | svp2 | slp1 | dvp1 | dvp2 | dlp1 | cep1 | clp2
--------+--------+--------+------+------+----------+------+------+----------+-------+--------
- v12 | e122 | v21 | 20 | | | 1010 | 1100 | vl2_prop | 10002 |
v11 | e121 | v22 | 10 | | | 1020 | 1200 | vl2_prop | 10001 |
+ v12 | e122 | v21 | 20 | | | 1010 | 1100 | vl2_prop | 10002 |
v11 | e131 | v33 | 10 | | | 2030 | | vl3_prop | 10003 |
v11 | e132 | v31 | 10 | | | 2010 | | vl3_prop | 10004 |
v22 | e231 | v32 | 1020 | 1200 | vl2_prop | 2020 | | vl3_prop | | 100050
@@ -498,8 +498,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS s
SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 | vl3)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname));
svname | cename | dvname
--------+--------+--------
- v12 | e122 | v21
v11 | e121 | v22
+ v12 | e122 | v21
v11 | e131 | v33
v11 | e132 | v31
v22 | e231 | v32
@@ -519,8 +519,8 @@ SELECT vn FROM all_vertices EXCEPT (SELECT svn FROM all_connected_vertices UNION
SELECT sn, cn, dn FROM GRAPH_TABLE (g1 MATCH (src IS l1)-[conn IS l1]->(dest IS l1) COLUMNS (src.elname AS sn, conn.elname AS cn, dest.elname AS dn));
sn | cn | dn
-----+------+-----
- v12 | e122 | v21
v11 | e121 | v22
+ v12 | e122 | v21
v11 | e131 | v33
v11 | e132 | v31
v22 | e231 | v32
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 4fccc7c4057..a04e99f1ed4 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -218,13 +218,13 @@ SELECT t1.a, t2.e
WHERE t1.a = t2.d;
a | e
---+----
- 0 |
1 | -1
2 | 2
- 2 | 4
3 | -3
+ 2 | 4
5 | -5
5 | -5
+ 0 |
(7 rows)
--
@@ -1573,13 +1573,13 @@ SELECT *
FROM J1_TBL INNER JOIN J2_TBL USING (i);
i | j | t | k
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
-- Same as above, slightly different syntax
@@ -1587,13 +1587,13 @@ SELECT *
FROM J1_TBL JOIN J2_TBL USING (i);
i | j | t | k
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
SELECT *
@@ -1681,35 +1681,35 @@ SELECT *
FROM J1_TBL NATURAL JOIN J2_TBL;
i | j | t | k
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
SELECT *
FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d);
a | b | c | d
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
SELECT *
FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
a | b | c | d
---+---+------+---
- 0 | | zero |
2 | 3 | two | 2
4 | 1 | four | 2
+ 0 | | zero |
(3 rows)
-- mismatch number of columns
@@ -1718,13 +1718,13 @@ SELECT *
FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a);
a | b | t | k
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
--
@@ -1734,22 +1734,22 @@ SELECT *
FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i);
i | j | t | i | k
---+---+-------+---+----
- 0 | | zero | 0 |
1 | 4 | one | 1 | -1
2 | 3 | two | 2 | 2
- 2 | 3 | two | 2 | 4
3 | 2 | three | 3 | -3
+ 2 | 3 | two | 2 | 4
5 | 0 | five | 5 | -5
5 | 0 | five | 5 | -5
+ 0 | | zero | 0 |
(7 rows)
SELECT *
FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k);
i | j | t | i | k
---+---+------+---+---
- 0 | | zero | | 0
2 | 3 | two | 2 | 2
4 | 1 | four | 2 | 4
+ 0 | | zero | | 0
(3 rows)
--
@@ -1909,8 +1909,8 @@ select * from tenk1 a, tenk1 b
where exists(select * from tenk1 c
where b.twothousand = c.twothousand and b.fivethous <> c.fivethous)
and a.tenthous = b.tenthous and a.tenthous < 5000;
- QUERY PLAN
---------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------
Hash Semi Join
Hash Cond: (b.twothousand = c.twothousand)
Join Filter: (b.fivethous <> c.fivethous)
@@ -1918,15 +1918,13 @@ where exists(select * from tenk1 c
Hash Cond: (b.tenthous = a.tenthous)
-> Seq Scan on tenk1 b
Bloom Filter 1: keys=(tenthous)
- Bloom Filter 2: keys=(twothousand)
-> Hash
Bloom Filter 1
-> Seq Scan on tenk1 a
Filter: (tenthous < 5000)
-> Hash
- Bloom Filter 2
-> Seq Scan on tenk1 c
-(15 rows)
+(13 rows)
--
-- More complicated constructs
@@ -2604,14 +2602,12 @@ select * from int4_tbl t1
Join Filter: (t2.f1 > 0)
Filter: (t3.f1 IS NULL)
-> Seq Scan on int4_tbl t2
- Bloom Filter 1: keys=(f1)
-> Materialize
-> Seq Scan on int4_tbl t3
-> Seq Scan on tenk1 t4
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl t1
-(15 rows)
+(13 rows)
explain (costs off)
select * from int4_tbl t1
@@ -2630,15 +2626,13 @@ select * from int4_tbl t1
Join Filter: (t2.f1 > 0)
Filter: (t2.f1 <> COALESCE(t3.f1, '-1'::integer))
-> Seq Scan on int4_tbl t2
- Bloom Filter 1: keys=(f1)
-> Materialize
-> Seq Scan on int4_tbl t3
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl t1
-> Materialize
-> Seq Scan on tenk1 t4
-(16 rows)
+(14 rows)
explain (costs off)
select * from onek t1
@@ -3126,17 +3120,17 @@ set enable_memoize to off;
explain (costs off)
select count(*) from tenk1 a, tenk1 b
where a.hundred = b.thousand and (b.fivethous % 10) < 10;
- QUERY PLAN
-------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------
Aggregate
-> Hash Join
- Hash Cond: (a.hundred = b.thousand)
- -> Index Only Scan using tenk1_hundred on tenk1 a
- Bloom Filter 1: keys=(hundred)
+ Hash Cond: (b.thousand = a.hundred)
+ -> Seq Scan on tenk1 b
+ Filter: ((fivethous % 10) < 10)
+ Bloom Filter 1: keys=(thousand)
-> Hash
Bloom Filter 1
- -> Seq Scan on tenk1 b
- Filter: ((fivethous % 10) < 10)
+ -> Index Only Scan using tenk1_hundred on tenk1 a
(9 rows)
select count(*) from tenk1 a, tenk1 b
@@ -3180,13 +3174,11 @@ ORDER BY 1;
Hash Cond: (b.f1 = c.f1)
Filter: (COALESCE(c.f1, 0) = 0)
-> Seq Scan on tt3 b
- Bloom Filter 1: keys=(f1)
-> Hash
-> Seq Scan on tt3 c
-> Hash
- Bloom Filter 1
-> Seq Scan on tt4 a
-(15 rows)
+(13 rows)
SELECT a.f1
FROM tt4 a
@@ -3224,12 +3216,10 @@ where t1.filt = 5;
Hash Join
Hash Cond: (t2.val = t1.val)
-> Seq Scan on skewedtable t2
- Bloom Filter 1: keys=(val)
-> Hash
- Bloom Filter 1
-> Seq Scan on skewedtable t1
Filter: (filt = 5)
-(8 rows)
+(6 rows)
drop table skewedtable;
--
@@ -3243,11 +3233,9 @@ where unique1 in (select unique2 from tenk1 b);
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
- Bloom Filter 1: keys=(unique1)
-> Hash
- Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(7 rows)
+(5 rows)
-- sadly, this is not an antijoin
explain (costs off)
@@ -3269,11 +3257,9 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2);
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
- Bloom Filter 1: keys=(unique1)
-> Hash
- Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(7 rows)
+(5 rows)
explain (costs off)
select a.* from tenk1 a
@@ -3306,17 +3292,15 @@ select 1 from tenk1
where (hundred, thousand) in (select twothousand, twothousand from onek);
QUERY PLAN
-------------------------------------------------
- Hash Join
- Hash Cond: (tenk1.hundred = onek.twothousand)
- -> Seq Scan on tenk1
- Filter: (hundred = thousand)
- Bloom Filter 1: keys=(hundred)
+ Hash Right Semi Join
+ Hash Cond: (onek.twothousand = tenk1.hundred)
+ -> Seq Scan on onek
+ Bloom Filter 1: keys=(twothousand)
-> Hash
Bloom Filter 1
- -> HashAggregate
- Group Key: onek.twothousand
- -> Seq Scan on onek
-(10 rows)
+ -> Seq Scan on tenk1
+ Filter: (hundred = thousand)
+(8 rows)
reset enable_memoize;
--
@@ -3333,19 +3317,17 @@ where t2.a is null;
Hash Right Anti Join
Hash Cond: (t2.b = t1.unique1)
-> Seq Scan on tbl_anti t2
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on tenk1 t1
-(7 rows)
+(5 rows)
-- this is an antijoin, as t2.a is non-null for any matching row
explain (costs off)
select * from tenk1 t1 left join
(tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b
where t2.a is null;
- QUERY PLAN
-----------------------------------------------
+ QUERY PLAN
+-------------------------------------------
Hash Right Anti Join
Hash Cond: (t2.b = t1.unique1)
-> Merge Left Join
@@ -3353,22 +3335,20 @@ where t2.a is null;
-> Sort
Sort Key: t2.c
-> Seq Scan on tbl_anti t2
- Bloom Filter 1: keys=(b)
-> Sort
Sort Key: t3.c
-> Seq Scan on tbl_anti t3
-> Hash
- Bloom Filter 1
-> Seq Scan on tenk1 t1
-(14 rows)
+(12 rows)
-- this is not an antijoin, as t3.a can be nulled by t2/t3 join
explain (costs off)
select * from tenk1 t1 left join
(tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b
where t3.a is null;
- QUERY PLAN
-----------------------------------------------
+ QUERY PLAN
+-------------------------------------------
Hash Right Join
Hash Cond: (t2.b = t1.unique1)
Filter: (t3.a IS NULL)
@@ -3377,14 +3357,12 @@ where t3.a is null;
-> Sort
Sort Key: t2.c
-> Seq Scan on tbl_anti t2
- Bloom Filter 1: keys=(b)
-> Sort
Sort Key: t3.c
-> Seq Scan on tbl_anti t3
-> Hash
- Bloom Filter 1
-> Seq Scan on tenk1 t1
-(15 rows)
+(13 rows)
rollback;
--
@@ -3398,11 +3376,9 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2 group by b.uniqu
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
- Bloom Filter 1: keys=(unique1)
-> Hash
- Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(7 rows)
+(5 rows)
--
-- regression test for proper handling of outer joins within antijoins
@@ -3572,14 +3548,16 @@ create temp table tidv (idv mycomptype);
create index on tidv (idv);
explain (costs off)
select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv;
- QUERY PLAN
-----------------------------------------------------------
- Merge Join
- Merge Cond: (a.idv = b.idv)
- -> Index Only Scan using tidv_idv_idx on tidv a
- -> Materialize
- -> Index Only Scan using tidv_idv_idx on tidv b
-(5 rows)
+ QUERY PLAN
+------------------------------------
+ Hash Join
+ Hash Cond: (a.idv = b.idv)
+ -> Seq Scan on tidv a
+ Bloom Filter 1: keys=(idv)
+ -> Hash
+ Bloom Filter 1
+ -> Seq Scan on tidv b
+(7 rows)
set enable_mergejoin = 0;
set enable_hashjoin = 0;
@@ -4019,13 +3997,11 @@ where q1 = thousand or q2 = thousand;
-> Seq Scan on q2
-> Bitmap Heap Scan on tenk1
Recheck Cond: ((q1.q1 = thousand) OR (q2.q2 = thousand))
- Bloom Filter 1: keys=(twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = ANY (ARRAY[q1.q1, q2.q2]))
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl
-(14 rows)
+(12 rows)
explain (costs off)
select * from
@@ -4042,13 +4018,11 @@ where thousand = (q1 + q2);
-> Seq Scan on q2
-> Bitmap Heap Scan on tenk1
Recheck Cond: (thousand = (q1.q1 + q2.q2))
- Bloom Filter 1: keys=(twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = (q1.q1 + q2.q2))
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl
-(14 rows)
+(12 rows)
--
-- test ability to generate a suitable plan for a star-schema query
@@ -4154,10 +4128,8 @@ where t1.unique1 < i4.f1;
Hash Cond: (t2.ten = t1.tenthous)
-> Seq Scan on public.tenk1 t2
Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4
- Bloom Filter 1: keys=(t2.ten)
-> Hash
Output: t1.tenthous, t1.unique1
- Bloom Filter 1
-> Nested Loop
Output: t1.tenthous, t1.unique1
-> Subquery Scan on ss0
@@ -4173,7 +4145,7 @@ where t1.unique1 < i4.f1;
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
Filter: (i8.q1 = ((64)::information_schema.cardinal_number)::integer)
-(35 rows)
+(33 rows)
select ss1.d1 from
tenk1 as t1
@@ -5270,7 +5242,6 @@ order by i0.f1, x;
Output: i1.f1, i2.q1, i2.q2, '123'::bigint
-> Seq Scan on public.int4_tbl i1
Output: i1.f1
- Bloom Filter 1: keys=(i1.f1)
-> Materialize
Output: i2.q1, i2.q2
-> Seq Scan on public.int8_tbl i2
@@ -5278,10 +5249,9 @@ order by i0.f1, x;
Filter: (123 = i2.q2)
-> Hash
Output: i0.f1
- Bloom Filter 1
-> Seq Scan on public.int4_tbl i0
Output: i0.f1
-(21 rows)
+(19 rows)
select * from
int4_tbl i0 left join
@@ -5335,10 +5305,8 @@ select t1.* from
Hash Cond: (i8.q1 = i8b2.q1)
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
- Bloom Filter 1: keys=(i8.q1)
-> Hash
Output: i8b2.q1, (NULL::integer)
- Bloom Filter 1
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, NULL::integer
-> Hash
@@ -5349,7 +5317,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(32 rows)
+(30 rows)
select t1.* from
text_tbl t1
@@ -5400,12 +5368,10 @@ select t1.* from
Output: i8b2.q1, NULL::integer
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, i8b2.q2
- Bloom Filter 1: keys=(i8b2.q1)
-> Materialize
-> Seq Scan on public.int4_tbl i4b2
-> Hash
Output: i8.q1, i8.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Hash
@@ -5416,7 +5382,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(36 rows)
+(34 rows)
select t1.* from
text_tbl t1
@@ -5469,16 +5435,12 @@ select t1.* from
Hash Cond: (i8b2.q1 = i4b2.f1)
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, i8b2.q2
- Bloom Filter 1: keys=(i8b2.q1)
- Bloom Filter 2: keys=(i8b2.q1)
-> Hash
Output: i4b2.f1
- Bloom Filter 1
-> Seq Scan on public.int4_tbl i4b2
Output: i4b2.f1
-> Hash
Output: i8.q1, i8.q2
- Bloom Filter 2
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Hash
@@ -5489,7 +5451,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(41 rows)
+(37 rows)
select t1.* from
text_tbl t1
@@ -5840,17 +5802,15 @@ where ss1.c2 = 0;
Filter: (i43.f1 = 0)
-> Seq Scan on public.int4_tbl i41
Output: i41.f1
- Bloom Filter 1: keys=(i41.f1)
-> Hash
Output: i42.f1
- Bloom Filter 1
-> Seq Scan on public.int4_tbl i42
Output: i42.f1
-> Limit
Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42))
-> Seq Scan on public.text_tbl
Output: i41.f1, i8.q1, i8.q2, i42.f1, i43.f1, (42)
-(27 rows)
+(25 rows)
select ss2.* from
int4_tbl i41
@@ -5976,19 +5936,19 @@ explain (costs off)
select a.unique1, b.unique2
from onek a left join onek b on a.unique1 = b.unique2
where (b.unique2, random() > 0) = any (select q1, random() > 0 from int8_tbl c where c.q1 < b.unique1);
- QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------
Hash Join
- Hash Cond: (b.unique2 = a.unique1)
- -> Seq Scan on onek b
- Filter: (ANY ((unique2 = (SubPlan any_1).col1) AND ((random() > '0'::double precision) = (SubPlan any_1).col2)))
- Bloom Filter 1: keys=(unique2)
- SubPlan any_1
- -> Seq Scan on int8_tbl c
- Filter: (q1 < b.unique1)
+ Hash Cond: (a.unique1 = b.unique2)
+ -> Index Only Scan using onek_unique1 on onek a
+ Bloom Filter 1: keys=(unique1)
-> Hash
Bloom Filter 1
- -> Index Only Scan using onek_unique1 on onek a
+ -> Seq Scan on onek b
+ Filter: (ANY ((unique2 = (SubPlan any_1).col1) AND ((random() > '0'::double precision) = (SubPlan any_1).col2)))
+ SubPlan any_1
+ -> Seq Scan on int8_tbl c
+ Filter: (q1 < b.unique1)
(11 rows)
select a.unique1, b.unique2
@@ -6142,16 +6102,14 @@ explain (costs off)
select id from a where id in (
select b.id from b left join c on b.id = c.id
);
- QUERY PLAN
------------------------------------
+ QUERY PLAN
+----------------------------
Hash Join
Hash Cond: (a.id = b.id)
-> Seq Scan on a
- Bloom Filter 1: keys=(id)
-> Hash
- Bloom Filter 1
-> Seq Scan on b
-(7 rows)
+(5 rows)
-- check optimization with oddly-nested outer joins
explain (costs off)
@@ -6574,18 +6532,16 @@ explain (costs off)
select c.id, ss.a from c
left join (select d.a from onerow, d left join b on d.a = b.id) ss
on c.id = ss.a;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+--------------------------------
Hash Right Join
Hash Cond: (d.a = c.id)
-> Nested Loop
-> Seq Scan on onerow
-> Seq Scan on d
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on c
-(9 rows)
+(7 rows)
-- check the case when the placeholder relates to an outer join and its
-- inner in the press field but actually uses only the outer side of the join
@@ -8254,33 +8210,32 @@ JOIN (
)
) _t2t3t4
ON sj_t1.id = _t2t3t4.id;
- QUERY PLAN
--------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
Nested Loop
- Join Filter: (sj_t3.id = sj_t1.id)
+ Join Filter: (sj_t1.id = sj_t3.id)
-> Nested Loop
- Join Filter: (sj_t2.id = sj_t3.id)
- -> Nested Loop Semi Join
+ Join Filter: (sj_t3.id = sj_t2_1.id)
+ -> Nested Loop
+ Join Filter: (sj_t2.id = sj_t3.id)
-> Nested Loop
- -> HashAggregate
- Group Key: sj_t3.id
+ -> Unique
+ -> Nested Loop
+ -> Index Only Scan using sj_t3_a_id_idx on sj_t3 sj_t3_1
+ Index Cond: (a = 1)
+ -> Seq Scan on sj_t4 sj_t4_1
+ -> Index Only Scan using sj_t2_id_idx on sj_t2
+ Index Cond: (id = sj_t3_1.id)
+ -> Materialize
+ -> Unique
-> Nested Loop
+ -> Index Only Scan using sj_t3_a_id_idx on sj_t3
+ Index Cond: (a = 1)
-> Seq Scan on sj_t4
- -> Materialize
- -> Bitmap Heap Scan on sj_t3
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on sj_t3_a_id_idx
- Index Cond: (a = 1)
- -> Index Only Scan using sj_t2_id_idx on sj_t2 sj_t2_1
- Index Cond: (id = sj_t3.id)
- -> Nested Loop
- -> Index Only Scan using sj_t3_a_id_idx on sj_t3 sj_t3_1
- Index Cond: ((a = 1) AND (id = sj_t3.id))
- -> Seq Scan on sj_t4 sj_t4_1
- -> Index Only Scan using sj_t2_id_idx on sj_t2
- Index Cond: (id = sj_t2_1.id)
+ -> Index Only Scan using sj_t2_id_idx on sj_t2 sj_t2_1
+ Index Cond: (id = sj_t2.id)
-> Seq Scan on sj_t1
-(24 rows)
+(23 rows)
--
-- Test RowMarks-related code
@@ -9143,15 +9098,13 @@ select * from
Output: b.q1, COALESCE(b.q2, '42'::bigint)
-> Seq Scan on public.int8_tbl d
Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)
- Bloom Filter 1: keys=(d.q1)
-> Hash
Output: c.q1, c.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Result
Output: (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))
-(26 rows)
+(24 rows)
-- another case requiring nested PlaceHolderVars
explain (verbose, costs off)
@@ -9210,29 +9163,25 @@ select c.*,a.*,ss1.q1,ss2.q1,ss3.* from
Join Filter: (b.q1 < b2.f1)
-> Seq Scan on public.int8_tbl b
Output: b.q1, b.q2
- Bloom Filter 1: keys=(b.q1)
-> Materialize
Output: b2.f1
-> Seq Scan on public.int4_tbl b2
Output: b2.f1
-> Hash
Output: a.q1, a.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl a
Output: a.q1, a.q2
-> Seq Scan on public.int8_tbl d
Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
- Bloom Filter 2: keys=(d.q1)
-> Hash
Output: c.q1, c.q2
- Bloom Filter 2
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Materialize
Output: i.f1
-> Seq Scan on public.int4_tbl i
Output: i.f1
-(38 rows)
+(34 rows)
-- check processing of postponed quals (bug #9041)
explain (verbose, costs off)
@@ -9539,10 +9488,8 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral
Hash Cond: (t3.b = t2.a)
-> Seq Scan on public.join_ut1 t3
Output: t3.a, t3.b, t3.c
- Bloom Filter 1: keys=(t3.b)
-> Hash
Output: t2.a
- Bloom Filter 1
-> Append
-> Seq Scan on public.join_pt1p1p1 t2_1
Output: t2_1.a
@@ -9550,7 +9497,7 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral
-> Seq Scan on public.join_pt1p2 t2_2
Output: t2_2.a
Filter: (t1.a = t2_2.a)
-(23 rows)
+(21 rows)
select t1.b, ss.phv from join_ut1 t1 left join lateral
(select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv
@@ -9600,22 +9547,21 @@ select * from fkest f1
join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100)
join fkest f3 on f1.x = f3.x
where f1.x100 = 2;
- QUERY PLAN
------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------
Hash Join
Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10))
- -> Hash Join
- Hash Cond: (f3.x = f2.x)
- -> Seq Scan on fkest f3
- Bloom Filter 1: keys=(x)
- -> Hash
- Bloom Filter 1
- -> Seq Scan on fkest f2
- Filter: (x100 = 2)
+ -> Nested Loop
+ -> Seq Scan on fkest f2
+ Filter: (x100 = 2)
+ Bloom Filter 1: keys=(x, x10b)
+ -> Index Scan using fkest_x_x10_x100_idx on fkest f3
+ Index Cond: (x = f2.x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on fkest f1
Filter: (x100 = 2)
-(13 rows)
+(12 rows)
rollback;
--
@@ -9668,21 +9614,19 @@ analyze j3;
-- ensure join is properly marked as unique
explain (verbose, costs off)
select * from j1 inner join j2 on j1.id = j2.id;
- QUERY PLAN
---------------------------------------
+ QUERY PLAN
+-----------------------------------
Hash Join
Output: j1.id, j2.id
Inner Unique: true
Hash Cond: (j1.id = j2.id)
-> Seq Scan on public.j1
Output: j1.id
- Bloom Filter 1: keys=(j1.id)
-> Hash
Output: j2.id
- Bloom Filter 1
-> Seq Scan on public.j2
Output: j2.id
-(12 rows)
+(10 rows)
-- ensure join is not unique when not an equi-join
explain (verbose, costs off)
@@ -9707,17 +9651,16 @@ select * from j1 inner join j3 on j1.id = j3.id;
--------------------------------------
Hash Join
Output: j1.id, j3.id
- Inner Unique: true
- Hash Cond: (j3.id = j1.id)
- -> Seq Scan on public.j3
- Output: j3.id
- Bloom Filter 1: keys=(j3.id)
- -> Hash
+ Hash Cond: (j1.id = j3.id)
+ -> Seq Scan on public.j1
Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
+ -> Hash
+ Output: j3.id
Bloom Filter 1
- -> Seq Scan on public.j1
- Output: j1.id
-(12 rows)
+ -> Seq Scan on public.j3
+ Output: j3.id
+(11 rows)
-- ensure left join is marked as unique
explain (verbose, costs off)
@@ -9788,64 +9731,70 @@ select * from j1 cross join j2;
-- ensure a natural join is marked as unique
explain (verbose, costs off)
select * from j1 natural join j2;
- QUERY PLAN
---------------------------------------
+ QUERY PLAN
+-----------------------------------
Hash Join
Output: j1.id
Inner Unique: true
Hash Cond: (j1.id = j2.id)
-> Seq Scan on public.j1
Output: j1.id
- Bloom Filter 1: keys=(j1.id)
-> Hash
Output: j2.id
- Bloom Filter 1
-> Seq Scan on public.j2
Output: j2.id
-(12 rows)
+(10 rows)
-- ensure a distinct clause allows the inner to become unique
explain (verbose, costs off)
select * from j1
inner join (select distinct id from j3) j3 on j1.id = j3.id;
- QUERY PLAN
------------------------------------------
- Nested Loop
+ QUERY PLAN
+-----------------------------------------------
+ Hash Join
Output: j1.id, j3.id
Inner Unique: true
- Join Filter: (j1.id = j3.id)
- -> Unique
+ Hash Cond: (j1.id = j3.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
+ -> Hash
Output: j3.id
- -> Sort
+ Bloom Filter 1
+ -> Unique
Output: j3.id
- Sort Key: j3.id
- -> Seq Scan on public.j3
+ -> Sort
Output: j3.id
- -> Seq Scan on public.j1
- Output: j1.id
-(13 rows)
+ Sort Key: j3.id
+ -> Seq Scan on public.j3
+ Output: j3.id
+(17 rows)
-- ensure group by clause allows the inner to become unique
explain (verbose, costs off)
select * from j1
inner join (select id from j3 group by id) j3 on j1.id = j3.id;
- QUERY PLAN
------------------------------------------
- Nested Loop
+ QUERY PLAN
+-----------------------------------------------
+ Hash Join
Output: j1.id, j3.id
Inner Unique: true
- Join Filter: (j1.id = j3.id)
- -> Group
+ Hash Cond: (j1.id = j3.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
+ -> Hash
Output: j3.id
- Group Key: j3.id
- -> Sort
+ Bloom Filter 1
+ -> Group
Output: j3.id
- Sort Key: j3.id
- -> Seq Scan on public.j3
+ Group Key: j3.id
+ -> Sort
Output: j3.id
- -> Seq Scan on public.j1
- Output: j1.id
-(14 rows)
+ Sort Key: j3.id
+ -> Seq Scan on public.j3
+ Output: j3.id
+(18 rows)
drop table j1;
drop table j2;
@@ -9959,14 +9908,16 @@ create index j2_id1_idx on j2 (id1) where id1 % 1000 = 1;
explain (costs off) select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
- QUERY PLAN
------------------------------------------
- Merge Join
- Merge Cond: (j1.id1 = j2.id1)
- Join Filter: (j2.id2 = j1.id2)
- -> Index Scan using j1_id1_idx on j1
- -> Index Scan using j2_id1_idx on j2
-(5 rows)
+ QUERY PLAN
+----------------------------------------------------------
+ Nested Loop
+ Disabled: true
+ Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2))
+ -> Seq Scan on j1
+ Filter: ((id1 % 1000) = 1)
+ -> Seq Scan on j2
+ Filter: ((id1 % 1000) = 1)
+(7 rows)
select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
@@ -9981,15 +9932,16 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
explain (costs off) select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
- QUERY PLAN
-----------------------------------------------------
- Merge Join
- Merge Cond: (j1.id1 = j2.id1)
- Join Filter: (j2.id2 = j1.id2)
- -> Index Scan using j1_id1_idx on j1
- -> Index Scan using j2_id1_idx on j2
- Index Cond: (id1 = ANY ('{1}'::integer[]))
-(6 rows)
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Nested Loop
+ Disabled: true
+ Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2))
+ -> Seq Scan on j1
+ Filter: ((id1 % 1000) = 1)
+ -> Seq Scan on j2
+ Filter: ((id1 = ANY ('{1}'::integer[])) AND ((id1 % 1000) = 1))
+(7 rows)
select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
@@ -10004,15 +9956,16 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
explain (costs off) select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
- QUERY PLAN
--------------------------------------------------------
- Merge Join
- Merge Cond: (j1.id1 = j2.id1)
- Join Filter: (j2.id2 = j1.id2)
- -> Index Scan using j1_id1_idx on j1
- -> Index Scan using j2_id1_idx on j2
- Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
-(6 rows)
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Nested Loop
+ Disabled: true
+ Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2))
+ -> Seq Scan on j1
+ Filter: ((id1 % 1000) = 1)
+ -> Seq Scan on j2
+ Filter: ((id1 >= ANY ('{1,5}'::integer[])) AND ((id1 % 1000) = 1))
+(7 rows)
select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
diff --git a/src/test/regress/expected/join_hash.out b/src/test/regress/expected/join_hash.out
index 0a8ade8b961..21900564149 100644
--- a/src/test/regress/expected/join_hash.out
+++ b/src/test/regress/expected/join_hash.out
@@ -90,17 +90,15 @@ set local work_mem = '4MB';
set local hash_mem_multiplier = 1.0;
explain (costs off)
select count(*) from simple r join simple s using (id);
- QUERY PLAN
------------------------------------------
+ QUERY PLAN
+----------------------------------------
Aggregate
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
- Bloom Filter 1: keys=(id)
-> Hash
- Bloom Filter 1
-> Seq Scan on simple s
-(8 rows)
+(6 rows)
select count(*) from simple r join simple s using (id);
count
@@ -205,17 +203,15 @@ set local work_mem = '128kB';
set local hash_mem_multiplier = 1.0;
explain (costs off)
select count(*) from simple r join simple s using (id);
- QUERY PLAN
------------------------------------------
+ QUERY PLAN
+----------------------------------------
Aggregate
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
- Bloom Filter 1: keys=(id)
-> Hash
- Bloom Filter 1
-> Seq Scan on simple s
-(8 rows)
+(6 rows)
select count(*) from simple r join simple s using (id);
count
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index c5aa11cd249..40461acf17c 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -39,18 +39,17 @@ USING source AS s
ON t.tid = s.sid
WHEN MATCHED THEN
DELETE;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+------------------------------------------
Merge on target t
- -> Merge Join
- Merge Cond: (t.tid = s.sid)
- -> Sort
- Sort Key: t.tid
- -> Seq Scan on target t
- -> Sort
- Sort Key: s.sid
+ -> Hash Join
+ Hash Cond: (t.tid = s.sid)
+ -> Seq Scan on target t
+ Bloom Filter 1: keys=(tid)
+ -> Hash
+ Bloom Filter 1
-> Seq Scan on source s
-(9 rows)
+(8 rows)
--
-- Errors
@@ -1640,42 +1639,38 @@ SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
WHEN MATCHED THEN
UPDATE SET b = t.b + 1');
- explain_merge
--------------------------------------------------------------------------
+ explain_merge
+------------------------------------------------------------------------------------------
Merge on ex_mtarget t (actual rows=0.00 loops=1)
Tuples: updated=50
- -> Merge Join (actual rows=50.00 loops=1)
- Merge Cond: (t.a = s.a)
- -> Sort (actual rows=50.00 loops=1)
- Sort Key: t.a
- Sort Method: quicksort Memory: xxx
- -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
- -> Sort (actual rows=100.00 loops=1)
- Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ -> Hash Join (actual rows=50.00 loops=1)
+ Hash Cond: (t.a = s.a)
+ -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
+ Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%)
+ -> Hash (actual rows=100.00 loops=1)
+ Buckets: xxx Batches: xxx Memory Usage: xxx
+ Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0
-> Seq Scan on ex_msource s (actual rows=100.00 loops=1)
-(12 rows)
+(10 rows)
-- only updates to selected tuples
SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
WHEN MATCHED AND t.a < 10 THEN
UPDATE SET b = t.b + 1');
- explain_merge
--------------------------------------------------------------------------
+ explain_merge
+------------------------------------------------------------------------------------------
Merge on ex_mtarget t (actual rows=0.00 loops=1)
Tuples: updated=5 skipped=45
- -> Merge Join (actual rows=50.00 loops=1)
- Merge Cond: (t.a = s.a)
- -> Sort (actual rows=50.00 loops=1)
- Sort Key: t.a
- Sort Method: quicksort Memory: xxx
- -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
- -> Sort (actual rows=100.00 loops=1)
- Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ -> Hash Join (actual rows=50.00 loops=1)
+ Hash Cond: (t.a = s.a)
+ -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
+ Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%)
+ -> Hash (actual rows=100.00 loops=1)
+ Buckets: xxx Batches: xxx Memory Usage: xxx
+ Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0
-> Seq Scan on ex_msource s (actual rows=100.00 loops=1)
-(12 rows)
+(10 rows)
-- updates + deletes
SELECT explain_merge('
@@ -1684,21 +1679,19 @@ WHEN MATCHED AND t.a < 10 THEN
UPDATE SET b = t.b + 1
WHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN
DELETE');
- explain_merge
--------------------------------------------------------------------------
+ explain_merge
+------------------------------------------------------------------------------------------
Merge on ex_mtarget t (actual rows=0.00 loops=1)
Tuples: updated=5 deleted=5 skipped=40
- -> Merge Join (actual rows=50.00 loops=1)
- Merge Cond: (t.a = s.a)
- -> Sort (actual rows=50.00 loops=1)
- Sort Key: t.a
- Sort Method: quicksort Memory: xxx
- -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
- -> Sort (actual rows=100.00 loops=1)
- Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ -> Hash Join (actual rows=50.00 loops=1)
+ Hash Cond: (t.a = s.a)
+ -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
+ Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%)
+ -> Hash (actual rows=100.00 loops=1)
+ Buckets: xxx Batches: xxx Memory Usage: xxx
+ Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0
-> Seq Scan on ex_msource s (actual rows=100.00 loops=1)
-(12 rows)
+(10 rows)
-- only inserts
SELECT explain_merge('
@@ -1795,21 +1788,20 @@ SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000
WHEN MATCHED AND t.a < 10 THEN
DO NOTHING');
- explain_merge
------------------------------------------------------------------------
+ explain_merge
+-----------------------------------------------------------------------------------------
Merge on ex_mtarget t (actual rows=0.00 loops=1)
- -> Merge Join (actual rows=0.00 loops=1)
- Merge Cond: (t.a = s.a)
- -> Sort (actual rows=0.00 loops=1)
- Sort Key: t.a
- Sort Method: quicksort Memory: xxx
+ -> Hash Join (actual rows=0.00 loops=1)
+ Hash Cond: (s.a = t.a)
+ -> Seq Scan on ex_msource s (actual rows=1.00 loops=1)
+ Bloom Filter 1: keys=(a) checked=0 rejected=0 (0.0%)
+ -> Hash (actual rows=0.00 loops=1)
+ Buckets: xxx Batches: xxx Memory Usage: xxx
+ Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=0 rejected=0
-> Seq Scan on ex_mtarget t (actual rows=0.00 loops=1)
Filter: (a < '-1000'::integer)
Rows Removed by Filter: 54
- -> Sort (never executed)
- Sort Key: s.a
- -> Seq Scan on ex_msource s (never executed)
-(12 rows)
+(11 rows)
DROP TABLE ex_msource, ex_mtarget;
DROP FUNCTION explain_merge(text);
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b52528870ef..7fff6a720aa 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -614,14 +614,16 @@ CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer
SUPPORT test_support_func;
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
- Hash Cond: (g.g = a.unique1)
- -> Function Scan on my_gen_series g
+ Hash Cond: (a.unique1 = g.g)
+ -> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
- -> Seq Scan on tenk1 a
-(5 rows)
+ Bloom Filter 1
+ -> Function Scan on my_gen_series g
+(7 rows)
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g;
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index be56036461b..c30304b99c7 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -460,29 +460,23 @@ SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
- Bloom Filter 1: keys=(x)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> HashAggregate
Group Key: t1_1.x
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
- Bloom Filter 2: keys=(x)
-> Hash
- Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> HashAggregate
Group Key: t1_2.x
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
- Bloom Filter 3: keys=(y)
-> Hash
- Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(30 rows)
+(24 rows)
SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3;
x | sum | count
@@ -539,29 +533,23 @@ SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
- Bloom Filter 1: keys=(x)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> HashAggregate
Group Key: t2_1.y
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
- Bloom Filter 2: keys=(x)
-> Hash
- Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> HashAggregate
Group Key: t2_2.y
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
- Bloom Filter 3: keys=(y)
-> Hash
- Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(30 rows)
+(24 rows)
-- When GROUP BY clause does not match; partial aggregation is performed for each partition.
-- Also test GroupAggregate paths by disabling hash aggregates.
@@ -584,9 +572,7 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
- Bloom Filter 1: keys=(x)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> Partial GroupAggregate
Group Key: t1_1.y
@@ -595,9 +581,7 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
- Bloom Filter 2: keys=(x)
-> Hash
- Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> Partial GroupAggregate
Group Key: t1_2.y
@@ -606,11 +590,9 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
- Bloom Filter 3: keys=(y)
-> Hash
- Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(40 rows)
+(34 rows)
SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3;
y | sum | count
@@ -656,11 +638,9 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP B
-> Hash Right Join
Hash Cond: (b_2.y = a_2.x)
-> Seq Scan on pagg_tab2_p3 b_2
- Bloom Filter 1: keys=(y)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab1_p3 a_2
-(28 rows)
+(26 rows)
SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
y | sum
@@ -687,18 +667,14 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
-> Hash Right Join
Hash Cond: (a.x = b.y)
-> Seq Scan on pagg_tab1_p1 a
- Bloom Filter 1: keys=(x)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 b
-> HashAggregate
Group Key: b_1.y
-> Hash Right Join
Hash Cond: (a_1.x = b_1.y)
-> Seq Scan on pagg_tab1_p2 a_1
- Bloom Filter 2: keys=(x)
-> Hash
- Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 b_1
-> HashAggregate
Group Key: b_2.y
@@ -707,7 +683,7 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
-> Seq Scan on pagg_tab2_p3 b_2
-> Hash
-> Seq Scan on pagg_tab1_p3 a_2
-(28 rows)
+(24 rows)
SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
y | sum
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 1906b3641a3..38643d41fd7 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -36,28 +36,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -83,28 +77,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a =
-> Hash Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = b)
-> Hash Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = b)
-> Hash Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_p3 t2_3
Filter: (a = b)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a = t2.b ORDER BY t1.a, t2.b;
a | c | b | c
@@ -214,17 +202,13 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE
-> Hash Right Join
Hash Cond: (t1_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = 0)
-> Hash Right Join
Hash Cond: (t1_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Left Join
@@ -232,7 +216,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE
Filter: (a = 0)
-> Index Scan using iprt1_p3_a on prt1_p3 t1_3
Index Cond: (a = t2_3.b)
-(24 rows)
+(20 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -299,12 +283,10 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a <
Hash Cond: (t2.b = t1.a)
-> Seq Scan on prt2_p2 t2
Filter: (b > 250)
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p2 t1
Filter: ((a < 450) AND (b = 0))
-(11 rows)
+(9 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -400,18 +382,14 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
Hash Cond: (t1_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = 0)
-> Hash Semi Join
Hash Cond: (t1_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Semi Join
@@ -421,7 +399,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
-> Materialize
-> Seq Scan on prt2_p3 t2_3
Filter: (a = 0)
-(28 rows)
+(24 rows)
SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -537,25 +515,19 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
-> Hash Join
Hash Cond: (t2_1.a = t3_1.b)
-> Seq Scan on prt1_p1 t2_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t3_1
-> Hash Join
Hash Cond: (t2_2.a = t3_2.b)
-> Seq Scan on prt1_p2 t2_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t3_2
-> Hash Join
Hash Cond: (t2_3.a = t3_3.b)
-> Seq Scan on prt1_p3 t2_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_p3 t3_3
-(32 rows)
+(26 rows)
SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
@@ -756,41 +728,29 @@ SELECT * FROM prt1 t1 JOIN prt1 t2 ON t1.a = t2.a WHERE t1.a IN (SELECT a FROM p
-> Hash Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t2_1
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p1 t3_1
-> Hash Semi Join
Hash Cond: (t1_2.a = t3_2.a)
-> Hash Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 3: keys=(a)
- Bloom Filter 4: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p2 t2_2
-> Hash
- Bloom Filter 4
-> Seq Scan on prt1_p2 t3_2
-> Hash Semi Join
Hash Cond: (t1_3.a = t3_3.a)
-> Hash Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
- Bloom Filter 5: keys=(a)
- Bloom Filter 6: keys=(a)
-> Hash
- Bloom Filter 5
-> Seq Scan on prt1_p3 t2_3
-> Hash
- Bloom Filter 6
-> Seq Scan on prt1_p3 t3_3
-(40 rows)
+(28 rows)
--
-- partitioned by expression
@@ -861,9 +821,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3_1
@@ -873,9 +831,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2
@@ -885,14 +841,12 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3
Index Cond: (((a + b) / 2) = t2_3.b)
-(39 rows)
+(33 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c | ?column? | c
@@ -917,9 +871,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
@@ -929,9 +881,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
@@ -941,12 +891,10 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(39 rows)
+(33 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
a | c | b | c | ?column? | c
@@ -976,9 +924,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_1.a = ((t3_1.a + t3_1.b) / 2))
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_e_p1 t3_1
Filter: (c = 0)
-> Index Scan using iprt2_p1_b on prt2_p1 t2_1
@@ -987,9 +933,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_2.a = ((t3_2.a + t3_2.b) / 2))
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_e_p2 t3_2
Filter: (c = 0)
-> Index Scan using iprt2_p2_b on prt2_p2 t2_2
@@ -998,14 +942,12 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_3.a = ((t3_3.a + t3_3.b) / 2))
-> Seq Scan on prt1_p3 t1_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_e_p3 t3_3
Filter: (c = 0)
-> Index Scan using iprt2_p3_b on prt2_p3 t2_3
Index Cond: (b = t1_3.a)
-(36 rows)
+(30 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
a | c | b | c | ?column? | c
@@ -1263,9 +1205,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_6.b = ((t1_9.a + t1_9.b) / 2))
-> Seq Scan on prt2_p1 t1_6
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_e_p1 t1_9
Filter: (c = 0)
-> Index Scan using iprt1_p1_a on prt1_p1 t1_3
@@ -1278,9 +1218,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_7.b = ((t1_10.a + t1_10.b) / 2))
-> Seq Scan on prt2_p2 t1_7
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_e_p2 t1_10
Filter: (c = 0)
-> Index Scan using iprt1_p2_a on prt1_p2 t1_4
@@ -1293,15 +1231,13 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2))
-> Seq Scan on prt2_p3 t1_8
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_e_p3 t1_11
Filter: (c = 0)
-> Index Scan using iprt1_p3_a on prt1_p3 t1_5
Index Cond: (a = t1_8.b)
Filter: (b = 0)
-(47 rows)
+(41 rows)
SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -1631,41 +1567,29 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, pl
-> Hash Join
Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on plt1_p1 t1_1
- Bloom Filter 1: keys=(b, c)
- Bloom Filter 2: keys=(c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt2_p1 t2_1
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_e_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on plt1_p2 t1_2
- Bloom Filter 3: keys=(b, c)
- Bloom Filter 4: keys=(c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt2_p2 t2_2
-> Hash
- Bloom Filter 4
-> Seq Scan on plt1_e_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
-> Seq Scan on plt1_p3 t1_3
- Bloom Filter 5: keys=(b, c)
- Bloom Filter 6: keys=(c)
-> Hash
- Bloom Filter 5
-> Seq Scan on plt2_p3 t2_3
-> Hash
- Bloom Filter 6
-> Seq Scan on plt1_e_p3 t3_3
-(44 rows)
+(32 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
avg | avg | avg | c | c | c
@@ -1713,29 +1637,23 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1
-> Hash Join
Hash Cond: (t3_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t3_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
-> Hash Join
Hash Cond: (t3_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t3_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
-> Hash Join
Hash Cond: (t3_3.a = t2_3.b)
-> Seq Scan on prt1_p3 t3_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_p3 t2_3
-> Hash
-> Result
Replaces: Scan on prt1
One-Time Filter: false
-(28 rows)
+(22 rows)
EXPLAIN (COSTS OFF)
SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
@@ -1797,41 +1715,29 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, ph
-> Hash Join
Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on pht1_p1 t1_1
- Bloom Filter 1: keys=(b, c)
- Bloom Filter 2: keys=(c)
-> Hash
- Bloom Filter 1
-> Seq Scan on pht2_p1 t2_1
-> Hash
- Bloom Filter 2
-> Seq Scan on pht1_e_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on pht1_p2 t1_2
- Bloom Filter 3: keys=(b, c)
- Bloom Filter 4: keys=(c)
-> Hash
- Bloom Filter 3
-> Seq Scan on pht2_p2 t2_2
-> Hash
- Bloom Filter 4
-> Seq Scan on pht1_e_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
-> Seq Scan on pht1_p3 t1_3
- Bloom Filter 5: keys=(b, c)
- Bloom Filter 6: keys=(c)
-> Hash
- Bloom Filter 5
-> Seq Scan on pht2_p3 t2_3
-> Hash
- Bloom Filter 6
-> Seq Scan on pht1_e_p3 t3_3
-(44 rows)
+(32 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
avg | avg | avg | c | c | c
@@ -1861,28 +1767,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
-- test default partition behavior for list
ALTER TABLE plt1 DETACH PARTITION plt1_p3;
@@ -1903,28 +1803,22 @@ SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c
-> Hash Join
Hash Cond: (t2_1.c = t1_1.c)
-> Seq Scan on plt2_p1 t2_1
- Bloom Filter 1: keys=(c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_p1 t1_1
Filter: ((a % 25) = 0)
-> Hash Join
Hash Cond: (t2_2.c = t1_2.c)
-> Seq Scan on plt2_p2 t2_2
- Bloom Filter 2: keys=(c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_p2 t1_2
Filter: ((a % 25) = 0)
-> Hash Join
Hash Cond: (t2_3.c = t1_3.c)
-> Seq Scan on plt2_p3 t2_3
- Bloom Filter 3: keys=(c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_p3 t1_3
Filter: ((a % 25) = 0)
-(29 rows)
+(23 rows)
--
-- multiple levels of partitioning
@@ -1960,9 +1854,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_l_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_l_p1 t1_1
Filter: (b = 0)
-> Hash Join
@@ -1984,7 +1876,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Hash
-> Seq Scan on prt1_l_p3_p1 t1_5
Filter: (b = 0)
-(30 rows)
+(28 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2463,25 +2355,19 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a
-> Hash Join
Hash Cond: (t1_1.a = t3_1.b)
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.a = t3_2.b)
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.a = t3_3.b)
-> Seq Scan on prt1_p3 t1_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_p3 t3_3
-(29 rows)
+(23 rows)
-- partitionwise join can not be applied if there are no equi-join conditions
-- between partition keys
@@ -2753,28 +2639,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2800,28 +2680,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2847,28 +2721,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2898,28 +2766,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2986,28 +2848,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3033,28 +2889,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -3080,28 +2930,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3157,28 +3001,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -3264,32 +3102,24 @@ SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2
-> Hash Right Join
Hash Cond: (t2_2.a = t1_2.b)
-> Seq Scan on prt1_adv_p2 t2_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Hash Join
Hash Cond: (t3_2.a = t1_2.b)
-> Seq Scan on prt1_adv_p2 t3_2
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_adv_p2 t1_2
Filter: (a = 0)
-> Hash Right Join
Hash Cond: (t2_3.a = t1_3.b)
-> Seq Scan on prt1_adv_p3 t2_3
- Bloom Filter 4: keys=(a)
-> Hash
- Bloom Filter 4
-> Hash Join
Hash Cond: (t3_3.a = t1_3.b)
-> Seq Scan on prt1_adv_p3 t3_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_adv_p3 t1_3
Filter: (a = 0)
-(39 rows)
+(31 rows)
SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a;
b | c | a | c | a | c
@@ -3459,20 +3289,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_2.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_1.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_1
Filter: (b = 0)
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3564,32 +3390,24 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2
-> Hash Right Join
Hash Cond: (t3_1.a = t1_1.a)
-> Seq Scan on prt3_adv_p1 t3_1
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Hash Right Join
Hash Cond: (t2_2.b = t1_1.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p2 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t3_2.a = t1_2.a)
-> Seq Scan on prt3_adv_p2 t3_2
- Bloom Filter 4: keys=(a)
-> Hash
- Bloom Filter 4
-> Hash Right Join
Hash Cond: (t2_1.b = t1_2.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p1 t1_2
Filter: (b = 0)
-(31 rows)
+(23 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a;
a | c | b | c | a | c
@@ -3631,20 +3449,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: ((a < 300) AND (b = 0))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: ((a < 300) AND (b = 0))
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3674,20 +3488,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3728,28 +3538,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3771,28 +3575,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3814,28 +3612,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3859,28 +3651,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3945,28 +3731,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3988,28 +3768,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4031,28 +3805,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4102,28 +3870,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4336,28 +4098,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4379,28 +4135,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4422,28 +4172,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4468,28 +4212,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4565,28 +4303,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4662,28 +4394,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4705,25 +4431,19 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-> Nested Loop Left Join
@@ -4731,7 +4451,7 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Seq Scan on plt1_adv_extra t1_4
Filter: (b < 10)
-> Seq Scan on plt2_adv_extra t2_4
-(32 rows)
+(26 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4805,43 +4525,31 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
-> Hash Right Join
Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
-> Seq Scan on plt1_adv_p1 t3_1
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
-> Seq Scan on plt1_adv_p2 t3_2
- Bloom Filter 4: keys=(a, c)
-> Hash
- Bloom Filter 4
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c))
-> Seq Scan on plt1_adv_p3 t3_3
- Bloom Filter 6: keys=(a, c)
-> Hash
- Bloom Filter 6
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 5: keys=(a, c)
-> Hash
- Bloom Filter 5
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-> Nested Loop Left Join
@@ -4852,7 +4560,7 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
Filter: (b < 10)
-> Seq Scan on plt2_adv_extra t2_4
-> Seq Scan on plt1_adv_extra t3_4
-(53 rows)
+(41 rows)
SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c | a | c
@@ -4888,20 +4596,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4983,32 +4687,24 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
-> Hash Right Join
Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
-> Seq Scan on plt3_adv_p1 t3_1
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
-> Seq Scan on plt3_adv_p2 t3_2
- Bloom Filter 4: keys=(a, c)
-> Hash
- Bloom Filter 4
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-(31 rows)
+(23 rows)
SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c | a | c
@@ -5037,20 +4733,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1_null t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5075,12 +4767,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p2 t2
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p2 t1
Filter: (b < 10)
-(10 rows)
+(8 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5119,20 +4809,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p3 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p3 t1_1
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p4 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p4 t1_2
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5151,12 +4837,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p4 t2
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p4 t1
Filter: ((c IS NULL) AND (b < 10))
-(10 rows)
+(8 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5178,20 +4862,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p3 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p3 t1_1
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p4 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p4 t1_2
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5210,12 +4890,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p4 t2
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p4 t1
Filter: ((c IS NULL) AND (b < 10))
-(10 rows)
+(8 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5337,16 +5015,12 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b))
-> Seq Scan on alpha_neg_p1 t1_1
Filter: ((b >= 125) AND (b < 225))
- Bloom Filter 1: keys=(a, b)
-> Hash
- Bloom Filter 1
-> Seq Scan on beta_neg_p1 t2_1
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-> Seq Scan on beta_neg_p2 t2_2
- Bloom Filter 2: keys=(a, b)
-> Hash
- Bloom Filter 2
-> Seq Scan on alpha_neg_p2 t1_2
Filter: ((b >= 125) AND (b < 225))
-> Hash Join
@@ -5363,7 +5037,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Filter: ((b >= 125) AND (b < 225))
-> Seq Scan on alpha_pos_p3 t1_6
Filter: ((b >= 125) AND (b < 225))
-(33 rows)
+(29 rows)
SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
a | b | c | a | b | c
@@ -5476,18 +5150,14 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on alpha_neg_p1 t1_1
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
- Bloom Filter 1: keys=(a, b, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on beta_neg_p1 t2_1
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-> Hash Join
Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on alpha_neg_p2 t1_2
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
- Bloom Filter 2: keys=(a, b, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on beta_neg_p2 t2_2
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-> Nested Loop
@@ -5502,7 +5172,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
-> Seq Scan on beta_pos_p3 t2_4
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-(33 rows)
+(29 rows)
SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b;
a | b | c | a | b | c
@@ -5646,25 +5316,19 @@ EXPLAIN (COSTS OFF) SELECT * FROM pht1 p1 JOIN pht1 p2 USING (c) LIMIT 1000;
-> Hash Join
Hash Cond: (p1_1.c = p2_1.c)
-> Seq Scan on pht1_p1 p1_1
- Bloom Filter 1: keys=(c)
-> Hash
- Bloom Filter 1
-> Seq Scan on pht1_p1 p2_1
-> Hash Join
Hash Cond: (p1_2.c = p2_2.c)
-> Seq Scan on pht1_p2 p1_2
- Bloom Filter 2: keys=(c)
-> Hash
- Bloom Filter 2
-> Seq Scan on pht1_p2 p2_2
-> Hash Join
Hash Cond: (p1_3.c = p2_3.c)
-> Seq Scan on pht1_p3 p1_3
- Bloom Filter 3: keys=(c)
-> Hash
- Bloom Filter 3
-> Seq Scan on pht1_p3 p2_3
-(23 rows)
+(17 rows)
RESET enable_mergejoin;
SET max_parallel_workers_per_gather = 1;
diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out
index 079f6422fdc..feae77cb840 100644
--- a/src/test/regress/expected/predicate.out
+++ b/src/test/regress/expected/predicate.out
@@ -748,16 +748,14 @@ SELECT id FROM dist_tab WHERE row_nn IS DISTINCT FROM ROW(1, 5)::dist_row_t;
SET enable_nestloop TO off;
EXPLAIN (COSTS OFF)
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
- QUERY PLAN
----------------------------------------
+ QUERY PLAN
+--------------------------------------
Hash Join
Hash Cond: (t1.val_nn = t2.val_nn)
-> Seq Scan on dist_tab t1
- Bloom Filter 1: keys=(val_nn)
-> Hash
- Bloom Filter 1
-> Seq Scan on dist_tab t2
-(7 rows)
+(5 rows)
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
id | val_nn | val_null | row_nn | id | val_nn | val_null | row_nn
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index dc44871b682..50cd3be8030 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -713,33 +713,31 @@ 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
+ Bloom Filter 2: keys=(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
+ Bloom Filter 2
-> 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)
+ 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)
-> Seq Scan on pg_temp.joinme
Output: joinme.ctid, joinme.other, joinme.f2j
- Bloom Filter 2: keys=(joinme.f2j)
+ Bloom Filter 1: keys=(joinme.f2j)
-> Hash
- Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
- 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)
-(31 rows)
+ Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+ Bloom Filter 1
+ -> Nested Loop
+ Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+ Join Filter: (foo_1.f2 = foo_2.f2)
+ -> 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
+(29 rows)
UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 11cf56fdba4..07854247020 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3610,17 +3610,16 @@ ANALYZE sb_1, sb_2;
-- bucket size is quite big because there are possibly many correlations.
EXPLAIN (COSTS OFF) -- Choose merge join
SELECT * FROM sb_1 a, sb_2 b WHERE a.x = b.x AND a.y = b.y AND a.z = b.z;
- QUERY PLAN
--------------------------------------------------------------
- Merge Join
- Merge Cond: ((a.z = b.z) AND (a.x = b.x) AND (a.y = b.y))
- -> Sort
- Sort Key: a.z, a.x, a.y
+ QUERY PLAN
+------------------------------------------------------------
+ Hash Join
+ Hash Cond: ((b.x = a.x) AND (b.y = a.y) AND (b.z = a.z))
+ -> Seq Scan on sb_2 b
+ Bloom Filter 1: keys=(x, y, z)
+ -> Hash
+ Bloom Filter 1
-> Seq Scan on sb_1 a
- -> Sort
- Sort Key: b.z, b.x, b.y
- -> Seq Scan on sb_2 b
-(8 rows)
+(7 rows)
-- The ndistinct extended statistics on (x, y, z) provides more reliable value
-- of bucket size.
@@ -3633,11 +3632,9 @@ SELECT * FROM sb_1 a, sb_2 b WHERE a.x = b.x AND a.y = b.y AND a.z = b.z;
Hash Join
Hash Cond: ((a.x = b.x) AND (a.y = b.y) AND (a.z = b.z))
-> Seq Scan on sb_1 a
- Bloom Filter 1: keys=(x, y, z)
-> Hash
- Bloom Filter 1
-> Seq Scan on sb_2 b
-(7 rows)
+(5 rows)
-- Check that the Hash Join bucket size estimator detects equal clauses correctly.
SET enable_nestloop = 'off';
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index 5c7d49050db..3519942030b 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -406,11 +406,9 @@ select * from int4_tbl o where exists
Hash Semi Join
Hash Cond: (o.f1 = i.f1)
-> Seq Scan on int4_tbl o
- Bloom Filter 1: keys=(f1)
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl i
-(7 rows)
+(5 rows)
explain (costs off)
select * from int4_tbl o where not exists
@@ -1673,7 +1671,7 @@ select * from int4_tbl where
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop Semi Join
Output: int4_tbl.f1
- Join Filter: (CASE WHEN (ANY (int4_tbl.f1 = (hashed SubPlan any_1).col1)) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten)
+ Join Filter: (b.ten = CASE WHEN (ANY (int4_tbl.f1 = (hashed SubPlan any_1).col1)) THEN int4_tbl.f1 ELSE NULL::integer END)
-> Seq Scan on public.int4_tbl
Output: int4_tbl.f1
-> Seq Scan on public.tenk1 b
@@ -2180,13 +2178,11 @@ order by t1.ten;
Hash Cond: (t2.fivethous = t1.unique1)
-> Seq Scan on public.tenk1 t2
Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4
- Bloom Filter 1: keys=(t2.fivethous)
-> Hash
Output: t1.ten, t1.unique1
- Bloom Filter 1
-> Seq Scan on public.tenk1 t1
Output: t1.ten, t1.unique1
-(17 rows)
+(15 rows)
select t1.ten, sum(x) from
tenk1 t1 left join lateral (
@@ -2281,19 +2277,15 @@ order by 1, 2;
Hash Cond: (t2.q2 = t3.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q2)
- Bloom Filter 2: keys=(t2.q2)
-> Hash
Output: t3.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t3
Output: t3.q2
-> Hash
Output: t1.q1, t1.q2
- Bloom Filter 2
-> Seq Scan on public.int8_tbl t1
Output: t1.q1, t1.q2
-(23 rows)
+(19 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2336,16 +2328,14 @@ order by 1, 2;
Output: t2.q2, ((t2.q1 + 1))
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q2)
-> Seq Scan on public.int8_tbl t3
Output: t3.q2, (t2.q1 + 1)
Filter: (t2.q2 = t3.q2)
-> Hash
Output: t1.q1, t1.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q1, t1.q2
-(19 rows)
+(17 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2390,19 +2380,15 @@ order by 1, 2;
Hash Cond: (t2.q2 = t3.q1)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q2)
- Bloom Filter 2: keys=(t2.q1)
-> Hash
Output: t3.q1
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t3
Output: t3.q1
-> Hash
Output: t1.q1
- Bloom Filter 2
-> Seq Scan on public.int8_tbl t1
Output: t1.q1
-(23 rows)
+(19 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2455,16 +2441,14 @@ order by 1, 2;
Output: t2.q1, (t2.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q1)
-> Seq Scan on public.int8_tbl t3
Output: t3.q1, t2.q2
Filter: (t2.q2 = t3.q1)
-> Hash
Output: t1.q1
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q1
-(19 rows)
+(17 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2528,21 +2512,19 @@ order by 1, 2, 3;
Hash Cond: (t2.q1 = t3.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q2)
-> Hash
Output: t3.q2, (COALESCE(t3.q1, t3.q1))
-> Seq Scan on public.int8_tbl t3
Output: t3.q2, COALESCE(t3.q1, t3.q1)
-> Hash
Output: t4.q1, t4.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t4
Output: t4.q1, t4.q2
-> Hash
Output: t1.q2
-> Seq Scan on public.int8_tbl t1
Output: t1.q2
-(28 rows)
+(26 rows)
select ss2.* from
int8_tbl t1 left join
@@ -2610,13 +2592,11 @@ order by 1, 2, 3;
Output: t3.q2, COALESCE(t3.q1, t3.q1)
-> Seq Scan on public.int8_tbl t4
Output: t4.q1, t4.q2, (COALESCE(t3.q1, t3.q1))
- Bloom Filter 1: keys=(t4.q1)
-> Hash
Output: t1.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q2
-(26 rows)
+(24 rows)
select ss2.* from
int8_tbl t1 left join
@@ -2917,13 +2897,11 @@ select * from tenk1 A where hundred in (select hundred from tenk2 B where B.odd
Hash Join
Hash Cond: ((a.odd = b.odd) AND (a.hundred = b.hundred))
-> Seq Scan on tenk1 a
- Bloom Filter 1: keys=(odd, hundred)
-> Hash
- Bloom Filter 1
-> HashAggregate
Group Key: b.odd, b.hundred
-> Seq Scan on tenk2 b
-(9 rows)
+(7 rows)
explain (costs off)
select * from tenk1 A where exists
@@ -2988,13 +2966,11 @@ ON B.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = b.odd);
-> Hash Join
Hash Cond: ((b.odd = c.odd) AND (b.hundred = c.hundred))
-> Seq Scan on tenk2 b
- Bloom Filter 1: keys=(odd, hundred)
-> Hash
- Bloom Filter 1
-> HashAggregate
Group Key: c.odd, c.hundred
-> Seq Scan on tenk2 c
-(12 rows)
+(10 rows)
-- we can pull up the sublink into the inner JoinExpr.
explain (costs off)
@@ -3009,15 +2985,13 @@ WHERE a.thousand < 750;
Hash Cond: (a.hundred = c.hundred)
-> Seq Scan on tenk1 a
Filter: (thousand < 750)
- Bloom Filter 1: keys=(hundred)
-> Hash
- Bloom Filter 1
-> HashAggregate
Group Key: c.odd, c.hundred
-> Seq Scan on tenk2 c
-> Hash
-> Seq Scan on tenk2 b
-(14 rows)
+(12 rows)
-- we can pull up the aggregate sublink into RHS of a left join.
explain (costs off)
@@ -3154,11 +3128,9 @@ WHERE a.ten IN (VALUES (1), (2));
Hash Cond: (a.ten = c.ten)
-> Seq Scan on onek a
Filter: (ten = ANY ('{1,2}'::integer[]))
- Bloom Filter 1: keys=(ten)
-> Hash
- Bloom Filter 1
-> Seq Scan on tenk1 c
-(8 rows)
+(6 rows)
EXPLAIN (COSTS OFF)
SELECT c.unique1,c.ten FROM tenk1 c JOIN onek a USING (ten)
@@ -3169,11 +3141,9 @@ WHERE c.ten IN (VALUES (1), (2));
Hash Cond: (c.ten = a.ten)
-> Seq Scan on tenk1 c
Filter: (ten = ANY ('{1,2}'::integer[]))
- Bloom Filter 1: keys=(ten)
-> Hash
- Bloom Filter 1
-> Seq Scan on onek a
-(8 rows)
+(6 rows)
-- Constant expressions are simplified
EXPLAIN (COSTS OFF)
@@ -3494,15 +3464,14 @@ WHERE id NOT IN (
Hash Cond: (not_null_tab.id = t2.id)
-> Seq Scan on not_null_tab
-> Hash
- -> Merge Join
- Merge Cond: (t1.id = t2.id)
- -> Sort
- Sort Key: t1.id
- -> Seq Scan on not_null_tab t1
- -> Sort
- Sort Key: t2.id
+ -> Hash Join
+ Hash Cond: (t1.id = t2.id)
+ -> Seq Scan on not_null_tab t1
+ Bloom Filter 1: keys=(id)
+ -> Hash
+ Bloom Filter 1
-> Seq Scan on not_null_tab t2
-(12 rows)
+(11 rows)
-- ANTI JOIN: outer side is defined NOT NULL, inner side is forced nonnullable
-- by qual clause
@@ -3543,11 +3512,8 @@ WHERE id NOT IN (
);
QUERY PLAN
-------------------------------------------------
- Merge Anti Join
- Merge Cond: (not_null_tab.id = t1.id)
- -> Sort
- Sort Key: not_null_tab.id
- -> Seq Scan on not_null_tab
+ Merge Right Anti Join
+ Merge Cond: (t1.id = not_null_tab.id)
-> Nested Loop Left Join
-> Merge Join
Merge Cond: (t1.id = t2.id)
@@ -3559,6 +3525,9 @@ WHERE id NOT IN (
-> Seq Scan on null_tab t2
-> Materialize
-> Seq Scan on null_tab t3
+ -> Sort
+ Sort Key: not_null_tab.id
+ -> Seq Scan on not_null_tab
(16 rows)
-- ANTI JOIN: outer side is defined NOT NULL and is not nulled by outer join,
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 7d4af80faf6..13025cf93c5 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -623,13 +623,11 @@ MERGE INTO rw_view1 t
Hash Cond: (base_tbl.a = generate_series.generate_series)
-> Bitmap Heap Scan on base_tbl
Recheck Cond: (a > 0)
- Bloom Filter 1: keys=(a)
-> Bitmap Index Scan on base_tbl_pkey
Index Cond: (a > 0)
-> Hash
- Bloom Filter 1
-> Function Scan on generate_series
-(11 rows)
+(9 rows)
-- it's still updatable if we add a DO ALSO rule
CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
@@ -3528,18 +3526,17 @@ EXPLAIN (COSTS OFF) UPDATE v2 SET a = 1;
Update on t1
InitPlan exists_1
-> Result
- -> Merge Join
- Merge Cond: (t1.a = v1.a)
- -> Sort
- Sort Key: t1.a
- -> Seq Scan on t1
- -> Sort
- Sort Key: v1.a
+ -> Hash Join
+ Hash Cond: (t1.a = v1.a)
+ -> Seq Scan on t1
+ Bloom Filter 1: keys=(a)
+ -> Hash
+ Bloom Filter 1
-> Subquery Scan on v1
-> Result
One-Time Filter: (InitPlan exists_1).col1
-> Seq Scan on t1 t1_1
-(14 rows)
+(13 rows)
DROP VIEW v2;
DROP VIEW v1;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 5c86619f023..dfda848b13c 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4324,15 +4324,14 @@ WHERE s.c = 1;
Run Condition: (ntile(e2.salary) OVER w1 <= 1)
-> Sort
Sort Key: e1.depname
- -> Merge Join
- Merge Cond: (e1.empno = e2.empno)
- -> Sort
- Sort Key: e1.empno
- -> Seq Scan on empsalary e1
- -> Sort
- Sort Key: e2.empno
+ -> Hash Join
+ Hash Cond: (e1.empno = e2.empno)
+ -> Seq Scan on empsalary e1
+ Bloom Filter 1: keys=(empno)
+ -> Hash
+ Bloom Filter 1
-> Seq Scan on empsalary e2
-(15 rows)
+(14 rows)
-- Ensure the run condition optimization is used in cases where the WindowFunc
-- has a Var from another query level
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index db8c77721e7..25262b08839 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -686,11 +686,9 @@ select count(*) from tenk1 a
-> Hash Semi Join
Hash Cond: (a.unique1 = x.unique1)
-> Index Only Scan using tenk1_unique1 on tenk1 a
- Bloom Filter 1: keys=(unique1)
-> Hash
- Bloom Filter 1
-> CTE Scan on x
-(10 rows)
+(8 rows)
explain (costs off)
with x as materialized (insert into tenk1 default values returning unique1)
@@ -753,22 +751,20 @@ select * from search_graph order by seq;
-> Recursive Union
-> Seq Scan on pg_temp.graph0 g
Output: g.f, g.t, g.label, ARRAY[ROW(g.f, g.t)]
- -> Merge Join
+ -> Hash Join
Output: g_1.f, g_1.t, g_1.label, array_cat(sg.seq, ARRAY[ROW(g_1.f, g_1.t)])
- Merge Cond: (g_1.f = sg.t)
- -> Sort
+ Hash Cond: (g_1.f = sg.t)
+ -> Seq Scan on pg_temp.graph0 g_1
Output: g_1.f, g_1.t, g_1.label
- Sort Key: g_1.f
- -> Seq Scan on pg_temp.graph0 g_1
- Output: g_1.f, g_1.t, g_1.label
- -> Sort
+ Bloom Filter 1: keys=(g_1.f)
+ -> Hash
Output: sg.seq, sg.t
- Sort Key: sg.t
+ Bloom Filter 1
-> WorkTable Scan on search_graph sg
Output: sg.seq, sg.t
-> CTE Scan on search_graph
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
-(22 rows)
+(20 rows)
with recursive search_graph(f, t, label) as (
select * from graph0 g
@@ -826,22 +822,20 @@ select * from search_graph order by seq;
-> Recursive Union
-> Seq Scan on pg_temp.graph0 g
Output: g.f, g.t, g.label, ROW('0'::bigint, g.f, g.t)
- -> Merge Join
+ -> Hash Join
Output: g_1.f, g_1.t, g_1.label, ROW(int8inc((sg.seq)."*DEPTH*"), g_1.f, g_1.t)
- Merge Cond: (g_1.f = sg.t)
- -> Sort
+ Hash Cond: (g_1.f = sg.t)
+ -> Seq Scan on pg_temp.graph0 g_1
Output: g_1.f, g_1.t, g_1.label
- Sort Key: g_1.f
- -> Seq Scan on pg_temp.graph0 g_1
- Output: g_1.f, g_1.t, g_1.label
- -> Sort
+ Bloom Filter 1: keys=(g_1.f)
+ -> Hash
Output: sg.seq, sg.t
- Sort Key: sg.t
+ Bloom Filter 1
-> WorkTable Scan on search_graph sg
Output: sg.seq, sg.t
-> CTE Scan on search_graph
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
-(22 rows)
+(20 rows)
with recursive search_graph(f, t, label) as (
select * from graph0 g
@@ -1097,20 +1091,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1135,20 +1129,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1210,21 +1204,19 @@ select * from search_graph;
-> Recursive Union
-> Seq Scan on pg_temp.graph g
Output: g.f, g.t, g.label, false, ARRAY[ROW(g.f, g.t)]
- -> Merge Join
+ -> Hash Join
Output: g_1.f, g_1.t, g_1.label, CASE WHEN (ROW(g_1.f, g_1.t) = ANY (sg.path)) THEN true ELSE false END, array_cat(sg.path, ARRAY[ROW(g_1.f, g_1.t)])
- Merge Cond: (g_1.f = sg.t)
- -> Sort
+ Hash Cond: (g_1.f = sg.t)
+ -> Seq Scan on pg_temp.graph g_1
Output: g_1.f, g_1.t, g_1.label
- Sort Key: g_1.f
- -> Seq Scan on pg_temp.graph g_1
- Output: g_1.f, g_1.t, g_1.label
- -> Sort
+ Bloom Filter 1: keys=(g_1.f)
+ -> Hash
Output: sg.path, sg.t
- Sort Key: sg.t
+ Bloom Filter 1
-> WorkTable Scan on search_graph sg
Output: sg.path, sg.t
Filter: (NOT sg.is_cycle)
-(20 rows)
+(18 rows)
with recursive search_graph(f, t, label) as (
select * from graph g
@@ -1244,20 +1236,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1281,20 +1273,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | N | {"(5,1)"}
1 | 2 | arc 1 -> 2 | N | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | N | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | N | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | N | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | N | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | N | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | N | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | N | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | N | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | N | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | N | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | N | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | Y | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | Y | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1446,20 +1438,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | {"(5,1)"} | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1484,20 +1476,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | (0,5,1) | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | (1,1,2) | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | (1,1,3) | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | (1,2,3) | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | (1,4,5) | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | (1,5,1) | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | (2,1,2) | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | (2,1,3) | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | (2,2,3) | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | (2,4,5) | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | (2,5,1) | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | (3,1,2) | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | (3,1,3) | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | (3,2,3) | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | (3,4,5) | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | (3,5,1) | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | (4,2,3) | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1677,20 +1669,20 @@ select * from v_cycle1;
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
2 | 3 | arc 2 -> 3
@@ -1707,20 +1699,20 @@ select * from v_cycle2;
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
2 | 3 | arc 2 -> 3
@@ -3248,10 +3240,8 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
Hash Cond: (m.k = o.k)
-> Seq Scan on public.m
Output: m.ctid, m.k
- Bloom Filter 1: keys=(m.k)
-> Hash
Output: o.k, o.v, o.*
- Bloom Filter 1
-> Subquery Scan on o
Output: o.k, o.v, o.*
-> Result
@@ -3262,7 +3252,7 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
-> CTE Scan on cte_basic
Output: (cte_basic.b || ' merge update'::text)
Filter: (cte_basic.a = m.k)
-(23 rows)
+(21 rows)
-- InitPlan
WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b)
@@ -3299,15 +3289,13 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
Hash Cond: (m.k = o.k)
-> Seq Scan on public.m
Output: m.ctid, m.k
- Bloom Filter 1: keys=(m.k)
-> Hash
Output: o.k, o.v, o.*
- Bloom Filter 1
-> Subquery Scan on o
Output: o.k, o.v, o.*
-> Result
Output: 1, 'merge source InitPlan'::text
-(23 rows)
+(21 rows)
-- MERGE source comes from CTE:
WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b)
@@ -3345,13 +3333,11 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text
Hash Cond: (m.k = merge_source_cte.a)
-> Seq Scan on public.m
Output: m.ctid, m.k
- Bloom Filter 1: keys=(m.k)
-> Hash
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
- Bloom Filter 1
-> CTE Scan on merge_source_cte
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
-(22 rows)
+(20 rows)
DROP TABLE m;
-- check that run to completion happens in proper ordering
--
2.50.1 (Apple Git-155)
From 81b42bfbd75d2a466c6119a92a1bb206081ae165 Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <[email protected]>
Date: Thu, 2 Jul 2026 15:46:05 -0300
Subject: [PATCH v4 3/4] Add tests for CustomScan filter pushdown
---
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../modules/test_bloom_customscan/Makefile | 23 ++
.../expected/test_bloom_customscan.out | 113 ++++++
.../modules/test_bloom_customscan/meson.build | 33 ++
.../sql/test_bloom_customscan.sql | 66 ++++
.../test_bloom_customscan--1.0.sql | 20 +
.../test_bloom_customscan.c | 362 ++++++++++++++++++
.../test_bloom_customscan.control | 5 +
9 files changed, 624 insertions(+)
create mode 100644 src/test/modules/test_bloom_customscan/Makefile
create mode 100644 src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
create mode 100644 src/test/modules/test_bloom_customscan/meson.build
create mode 100644 src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql
create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql
create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan.c
create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan.control
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 0a74ab5c86f..3f83feeb074 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -20,6 +20,7 @@ SUBDIRS = \
test_binaryheap \
test_bitmapset \
test_bloomfilter \
+ test_bloom_customscan \
test_cloexec \
test_checksums \
test_copy_callbacks \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 4bca42bb370..e9efdb1e8a4 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -20,6 +20,7 @@ subdir('test_autovacuum')
subdir('test_binaryheap')
subdir('test_bitmapset')
subdir('test_bloomfilter')
+subdir('test_bloom_customscan')
subdir('test_cloexec')
subdir('test_checksums')
subdir('test_copy_callbacks')
diff --git a/src/test/modules/test_bloom_customscan/Makefile b/src/test/modules/test_bloom_customscan/Makefile
new file mode 100644
index 00000000000..5de5b5e6f4b
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_bloom_customscan/Makefile
+
+MODULE_big = test_bloom_customscan
+OBJS = \
+ $(WIN32RES) \
+ test_bloom_customscan.o
+PGFILEDESC = "test_bloom_customscan - CustomScan consuming a hashjoin bloom filter"
+
+EXTENSION = test_bloom_customscan
+DATA = test_bloom_customscan--1.0.sql
+
+REGRESS = test_bloom_customscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_bloom_customscan
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
new file mode 100644
index 00000000000..e1ce5903a02
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
@@ -0,0 +1,113 @@
+CREATE EXTENSION test_bloom_customscan;
+-- ensure the library (and its planner hook + GUC) is loaded
+LOAD 'test_bloom_customscan';
+-- Force a serial hash join, which is where the bloom-filter pushdown applies.
+SET enable_hashjoin_bloom = on;
+SET enable_mergejoin = off;
+SET enable_nestloop = off;
+SET max_parallel_workers_per_gather = 0;
+CREATE TABLE cs_fact (a int, b int, payload int);
+CREATE TABLE cs_dim (id int, id2 int, label text);
+-- Fact keys range over 0..999; the dimension only covers 1..50, so the join
+-- is highly selective and the pushed-down filter should reject most fact rows.
+INSERT INTO cs_fact
+ SELECT g % 1000, g % 1000, g FROM generate_series(1, 20000) g;
+INSERT INTO cs_dim
+ SELECT g, g, 'x' || g FROM generate_series(1, 50) g;
+ANALYZE cs_fact;
+ANALYZE cs_dim;
+-- Offer the bloom-capable custom scan and force it to be chosen (the CustomPath
+-- keeps disabled_nodes = 0, so disabling seqscan makes it win).
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+-- The fact relation is scanned by our Custom Scan and receives the filter.
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Aggregate
+ Output: count(*)
+ -> Hash Join
+ Hash Cond: (f.a = d.id)
+ -> Custom Scan (TestBloomCustomScan) on public.cs_fact f
+ Output: f.a
+ -> Hash
+ Output: d.id
+ -> Custom Scan (TestBloomCustomScan) on public.cs_dim d
+ Output: d.id
+(10 rows)
+
+-- Single-key join: the filter must actually reject fact rows.
+SELECT test_bloom_cs_reset();
+ test_bloom_cs_reset
+---------------------
+
+(1 row)
+
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
+ count
+-------
+ 1000
+(1 row)
+
+SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
+ filter_rejected_rows
+----------------------
+ f
+(1 row)
+
+-- Correctness: the result must be identical with and without the filter.
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+CREATE TEMP TABLE r_cs AS
+ SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a;
+SET test_bloom_customscan.enable = off;
+SET enable_seqscan = on;
+CREATE TEMP TABLE r_plain AS
+ SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a;
+SELECT count(*) AS cs_minus_plain
+ FROM (SELECT * FROM r_cs EXCEPT SELECT * FROM r_plain) x;
+ cs_minus_plain
+----------------
+ 0
+(1 row)
+
+SELECT count(*) AS plain_minus_cs
+ FROM (SELECT * FROM r_plain EXCEPT SELECT * FROM r_cs) x;
+ plain_minus_cs
+----------------
+ 0
+(1 row)
+
+-- Two-key join: the opted-in recipient also gets per-key filters built.
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+SELECT test_bloom_cs_reset();
+ test_bloom_cs_reset
+---------------------
+
+(1 row)
+
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id AND f.b = d.id2;
+ count
+-------
+ 1000
+(1 row)
+
+SELECT test_bloom_cs_perkey_built() AS perkey_filters_built;
+ perkey_filters_built
+----------------------
+ f
+(1 row)
+
+SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
+ filter_rejected_rows
+----------------------
+ f
+(1 row)
+
+-- cleanup
+SET test_bloom_customscan.enable = off;
+SET enable_seqscan = on;
+DROP TABLE cs_fact, cs_dim;
+DROP EXTENSION test_bloom_customscan;
diff --git a/src/test/modules/test_bloom_customscan/meson.build b/src/test/modules/test_bloom_customscan/meson.build
new file mode 100644
index 00000000000..e12dc80942c
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2026, PostgreSQL Global Development Group
+
+test_bloom_customscan_sources = files(
+ 'test_bloom_customscan.c',
+)
+
+if host_system == 'windows'
+ test_bloom_customscan_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_bloom_customscan',
+ '--FILEDESC', 'test_bloom_customscan - CustomScan consuming a hashjoin bloom filter',])
+endif
+
+test_bloom_customscan = shared_module('test_bloom_customscan',
+ test_bloom_customscan_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_bloom_customscan
+
+test_install_data += files(
+ 'test_bloom_customscan.control',
+ 'test_bloom_customscan--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_bloom_customscan',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_bloom_customscan',
+ ],
+ },
+}
diff --git a/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql b/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql
new file mode 100644
index 00000000000..daae84a531a
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql
@@ -0,0 +1,66 @@
+CREATE EXTENSION test_bloom_customscan;
+-- ensure the library (and its planner hook + GUC) is loaded
+LOAD 'test_bloom_customscan';
+
+-- Force a serial hash join, which is where the bloom-filter pushdown applies.
+SET enable_hashjoin_bloom = on;
+SET enable_mergejoin = off;
+SET enable_nestloop = off;
+SET max_parallel_workers_per_gather = 0;
+
+CREATE TABLE cs_fact (a int, b int, payload int);
+CREATE TABLE cs_dim (id int, id2 int, label text);
+
+-- Fact keys range over 0..999; the dimension only covers 1..50, so the join
+-- is highly selective and the pushed-down filter should reject most fact rows.
+INSERT INTO cs_fact
+ SELECT g % 1000, g % 1000, g FROM generate_series(1, 20000) g;
+INSERT INTO cs_dim
+ SELECT g, g, 'x' || g FROM generate_series(1, 50) g;
+
+ANALYZE cs_fact;
+ANALYZE cs_dim;
+
+-- Offer the bloom-capable custom scan and force it to be chosen (the CustomPath
+-- keeps disabled_nodes = 0, so disabling seqscan makes it win).
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+
+-- The fact relation is scanned by our Custom Scan and receives the filter.
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
+
+-- Single-key join: the filter must actually reject fact rows.
+SELECT test_bloom_cs_reset();
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
+SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
+
+-- Correctness: the result must be identical with and without the filter.
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+CREATE TEMP TABLE r_cs AS
+ SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a;
+
+SET test_bloom_customscan.enable = off;
+SET enable_seqscan = on;
+CREATE TEMP TABLE r_plain AS
+ SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a;
+
+SELECT count(*) AS cs_minus_plain
+ FROM (SELECT * FROM r_cs EXCEPT SELECT * FROM r_plain) x;
+SELECT count(*) AS plain_minus_cs
+ FROM (SELECT * FROM r_plain EXCEPT SELECT * FROM r_cs) x;
+
+-- Two-key join: the opted-in recipient also gets per-key filters built.
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+SELECT test_bloom_cs_reset();
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id AND f.b = d.id2;
+SELECT test_bloom_cs_perkey_built() AS perkey_filters_built;
+SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
+
+-- cleanup
+SET test_bloom_customscan.enable = off;
+SET enable_seqscan = on;
+DROP TABLE cs_fact, cs_dim;
+DROP EXTENSION test_bloom_customscan;
diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql b/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql
new file mode 100644
index 00000000000..329d795d6f0
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql
@@ -0,0 +1,20 @@
+/* src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_bloom_customscan" to load this file. \quit
+
+CREATE FUNCTION test_bloom_cs_reset()
+ RETURNS void
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bloom_cs_scanned_rows()
+ RETURNS bigint
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bloom_cs_rejected_rows()
+ RETURNS bigint
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bloom_cs_perkey_built()
+ RETURNS boolean
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan.c b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
new file mode 100644
index 00000000000..124cbef9ded
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
@@ -0,0 +1,362 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_bloom_customscan.c
+ * Minimal CustomScan provider that consumes a hash-join Bloom filter.
+ *
+ * This is a test-only stand-in for a storage-level scan provider. When enabled, it
+ * installs a set_rel_pathlist_hook that offers a CustomPath for plain heap
+ * base relations, advertising CUSTOMPATH_SUPPORT_BLOOM_FILTERS. That opt-in
+ * is what lets the planner (a) generate filter-bearing paths for the scan and
+ * (b) push a hash-join Bloom filter down to it.
+ *
+ * The scan itself is an ordinary heap sequential scan; the only interesting
+ * part is that its per-tuple loop probes the pushed-down combined filter with
+ * ExecBloomFilters() -- exactly what a stock scan does inside ExecScanExtended,
+ * but here done by the provider itself -- and, for a multi-key join, checks
+ * that the per-key filters were populated on the producer side.
+ *
+ * A few SQL-callable functions expose counters so the regression test can
+ * assert that the filter was actually probed and rejected tuples, and that
+ * per-key filters were built, without depending on timing or EXPLAIN output.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/modules/test_bloom_customscan/test_bloom_customscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/relscan.h"
+#include "access/tableam.h"
+#include "executor/executor.h"
+#include "fmgr.h"
+#include "nodes/execnodes.h"
+#include "nodes/extensible.h"
+#include "nodes/pathnodes.h"
+#include "nodes/plannodes.h"
+#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/restrictinfo.h"
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+/* GUC: when off, the hook does nothing and plans look exactly like upstream. */
+static bool test_bloom_cs_enabled = false;
+
+/* Observation counters, reset/read from SQL. */
+static uint64 test_bloom_cs_scanned = 0;
+static uint64 test_bloom_cs_rejected = 0;
+static bool test_bloom_cs_perkey_seen = false;
+
+static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook = NULL;
+
+/* Execution state, embedding CustomScanState as its first field. */
+typedef struct BloomCSScanState
+{
+ CustomScanState css;
+ TableScanDesc scandesc;
+ bool inspected; /* have we checked per-key filters yet? */
+} BloomCSScanState;
+
+/* forward declarations */
+static Plan *bloom_cs_plan_custom_path(PlannerInfo *root, RelOptInfo *rel,
+ CustomPath *best_path, List *tlist,
+ List *clauses, List *custom_plans);
+static Node *bloom_cs_create_scan_state(CustomScan *cscan);
+static void bloom_cs_begin_scan(CustomScanState *node, EState *estate,
+ int eflags);
+static TupleTableSlot *bloom_cs_exec_scan(CustomScanState *node);
+static void bloom_cs_end_scan(CustomScanState *node);
+static void bloom_cs_rescan(CustomScanState *node);
+
+static const CustomPathMethods bloom_cs_path_methods = {
+ .CustomName = "TestBloomCustomScan",
+ .PlanCustomPath = bloom_cs_plan_custom_path,
+};
+
+static const CustomScanMethods bloom_cs_scan_methods = {
+ .CustomName = "TestBloomCustomScan",
+ .CreateCustomScanState = bloom_cs_create_scan_state,
+};
+
+static const CustomExecMethods bloom_cs_exec_methods = {
+ .CustomName = "TestBloomCustomScan",
+ .BeginCustomScan = bloom_cs_begin_scan,
+ .ExecCustomScan = bloom_cs_exec_scan,
+ .EndCustomScan = bloom_cs_end_scan,
+ .ReScanCustomScan = bloom_cs_rescan,
+};
+
+/*
+ * set_rel_pathlist_hook: offer a bloom-filter-capable CustomPath for plain
+ * heap base relations. We copy the cost of the existing sequential scan path
+ * so join costing stays sane; the test forces the custom scan to be chosen
+ * with "SET enable_seqscan = off" (the CustomPath keeps disabled_nodes = 0).
+ */
+static void
+bloom_cs_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti,
+ RangeTblEntry *rte)
+{
+ CustomPath *cpath;
+ Cost startup_cost = 0;
+ Cost total_cost = 0;
+ ListCell *lc;
+
+ if (prev_set_rel_pathlist_hook)
+ prev_set_rel_pathlist_hook(root, rel, rti, rte);
+
+ if (!test_bloom_cs_enabled)
+ return;
+
+ /* Only plain, ordinary base tables. */
+ if (rel->reloptkind != RELOPT_BASEREL)
+ return;
+ if (rte->rtekind != RTE_RELATION || rte->relkind != RELKIND_RELATION)
+ return;
+
+ /* Borrow the sequential scan's cost estimate. */
+ foreach(lc, rel->pathlist)
+ {
+ Path *path = (Path *) lfirst(lc);
+
+ if (path->pathtype == T_SeqScan && path->param_info == NULL)
+ {
+ startup_cost = path->startup_cost;
+ total_cost = path->total_cost;
+ break;
+ }
+ }
+
+ cpath = makeNode(CustomPath);
+ cpath->path.pathtype = T_CustomScan;
+ cpath->path.parent = rel;
+ cpath->path.pathtarget = rel->reltarget;
+ cpath->path.param_info = NULL;
+ cpath->path.parallel_aware = false;
+ cpath->path.parallel_safe = false;
+ cpath->path.parallel_workers = 0;
+ cpath->path.rows = rel->rows;
+ cpath->path.startup_cost = startup_cost;
+ cpath->path.total_cost = total_cost;
+ cpath->path.pathkeys = NIL;
+
+ cpath->custom_paths = NIL;
+ cpath->custom_private = NIL;
+ cpath->methods = &bloom_cs_path_methods;
+
+ add_path(rel, (Path *) cpath);
+}
+
+static Plan *
+bloom_cs_plan_custom_path(PlannerInfo *root, RelOptInfo *rel,
+ CustomPath *best_path, List *tlist,
+ List *clauses, List *custom_plans)
+{
+ CustomScan *cscan = makeNode(CustomScan);
+
+ cscan->flags = best_path->flags;
+ cscan->methods = &bloom_cs_scan_methods;
+ cscan->custom_plans = custom_plans;
+
+ /* A base-relation scan: leave custom_scan_tlist NIL, use rel's rowtype. */
+ cscan->scan.scanrelid = rel->relid;
+ cscan->scan.plan.targetlist = tlist;
+ cscan->scan.plan.qual = extract_actual_clauses(clauses, false);
+
+ return &cscan->scan.plan;
+}
+
+static Node *
+bloom_cs_create_scan_state(CustomScan *cscan)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) newNode(sizeof(BloomCSScanState),
+ T_CustomScanState);
+
+ bcss->css.methods = &bloom_cs_exec_methods;
+ bcss->scandesc = NULL;
+ bcss->inspected = false;
+
+ /*
+ * We scan a heap table with table_scan_getnextslot(), which stores
+ * on-disk heap tuples, so the scan tuple slot must be a buffer-heap slot
+ * rather than the default virtual slot. ExecInitCustomScan() reads
+ * css.slotOps when building ss_ScanTupleSlot (before BeginCustomScan
+ * runs), so it has to be set here.
+ */
+ bcss->css.slotOps = &TTSOpsBufferHeapTuple;
+
+ return (Node *) bcss;
+}
+
+static void
+bloom_cs_begin_scan(CustomScanState *node, EState *estate, int eflags)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) node;
+
+ /*
+ * ExecInitCustomScan already opened the scan relation (scanrelid > 0) and
+ * called ExecInitBloomFilters(), so node->ss.ps.bloom_filters is set up.
+ * We just need a table scan descriptor.
+ */
+ if (!(eflags & (EXEC_FLAG_EXPLAIN_ONLY | EXEC_FLAG_EXPLAIN_GENERIC)))
+ bcss->scandesc = table_beginscan(node->ss.ss_currentRelation,
+ estate->es_snapshot, 0, NULL, 0);
+}
+
+/*
+ * On the first tuple (by which point an eager producer has built its hash
+ * table and filters), record whether the per-key filters were populated.
+ */
+static void
+bloom_cs_maybe_inspect_perkey(BloomCSScanState * bcss)
+{
+ ListCell *lc;
+
+ if (bcss->inspected)
+ return;
+
+ foreach(lc, bcss->css.ss.ps.bloom_filters)
+ {
+ BloomFilterState *bfs = (BloomFilterState *) lfirst(lc);
+ HashJoinState *producer = bfs->producer;
+ HashState *hashNode;
+
+ if (producer == NULL)
+ continue;
+ hashNode = castNode(HashState, innerPlanState(&producer->js.ps));
+
+ /* Hash table (and filters) not built yet; try again next tuple. */
+ if (hashNode->bloom_filter == NULL)
+ return;
+
+ }
+
+ bcss->inspected = true;
+}
+
+static TupleTableSlot *
+bloom_cs_exec_scan(CustomScanState *node)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) node;
+ ExprContext *econtext = node->ss.ps.ps_ExprContext;
+ TupleTableSlot *scanslot = node->ss.ss_ScanTupleSlot;
+ ExprState *qual = node->ss.ps.qual;
+ ProjectionInfo *proj = node->ss.ps.ps_ProjInfo;
+
+ bloom_cs_maybe_inspect_perkey(bcss);
+
+ for (;;)
+ {
+ ResetExprContext(econtext);
+
+ if (!table_scan_getnextslot(bcss->scandesc, ForwardScanDirection,
+ scanslot))
+ {
+ if (proj)
+ return ExecClearTuple(proj->pi_state.resultslot);
+ return ExecClearTuple(scanslot);
+ }
+
+ test_bloom_cs_scanned++;
+ econtext->ecxt_scantuple = scanslot;
+
+ /*
+ * Probe the pushed-down combined Bloom filter, just as a stock scan
+ * would inside ExecScanExtended. A conclusive miss lets us drop the
+ * tuple without evaluating the qual or projection.
+ */
+ if (!ExecBloomFilters(node->ss.ps.bloom_filters, econtext))
+ {
+ test_bloom_cs_rejected++;
+ continue;
+ }
+
+ if (qual != NULL && !ExecQual(qual, econtext))
+ continue;
+
+ if (proj != NULL)
+ return ExecProject(proj);
+
+ return scanslot;
+ }
+}
+
+static void
+bloom_cs_end_scan(CustomScanState *node)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) node;
+
+ if (bcss->scandesc != NULL)
+ table_endscan(bcss->scandesc);
+}
+
+static void
+bloom_cs_rescan(CustomScanState *node)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) node;
+
+ bcss->inspected = false;
+ if (bcss->scandesc != NULL)
+ table_rescan(bcss->scandesc, NULL);
+}
+
+/* ------------------------------------------------------------------------
+ * SQL-callable observation helpers
+ * ------------------------------------------------------------------------
+ */
+PG_FUNCTION_INFO_V1(test_bloom_cs_reset);
+PG_FUNCTION_INFO_V1(test_bloom_cs_scanned_rows);
+PG_FUNCTION_INFO_V1(test_bloom_cs_rejected_rows);
+PG_FUNCTION_INFO_V1(test_bloom_cs_perkey_built);
+
+Datum
+test_bloom_cs_reset(PG_FUNCTION_ARGS)
+{
+ test_bloom_cs_scanned = 0;
+ test_bloom_cs_rejected = 0;
+ test_bloom_cs_perkey_seen = false;
+ PG_RETURN_VOID();
+}
+
+Datum
+test_bloom_cs_scanned_rows(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64((int64) test_bloom_cs_scanned);
+}
+
+Datum
+test_bloom_cs_rejected_rows(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64((int64) test_bloom_cs_rejected);
+}
+
+Datum
+test_bloom_cs_perkey_built(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(test_bloom_cs_perkey_seen);
+}
+
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("test_bloom_customscan.enable",
+ "Offer a bloom-filter-capable CustomScan for heap tables.",
+ NULL,
+ &test_bloom_cs_enabled,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL, NULL, NULL);
+
+ MarkGUCPrefixReserved("test_bloom_customscan");
+
+ RegisterCustomScanMethods(&bloom_cs_scan_methods);
+
+ prev_set_rel_pathlist_hook = set_rel_pathlist_hook;
+ set_rel_pathlist_hook = bloom_cs_set_rel_pathlist;
+}
diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan.control b/src/test/modules/test_bloom_customscan/test_bloom_customscan.control
new file mode 100644
index 00000000000..10f90510302
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan.control
@@ -0,0 +1,5 @@
+# test_bloom_customscan extension
+comment = 'Test CustomScan provider that consumes a hashjoin bloom filter'
+default_version = '1.0'
+module_pathname = '$libdir/test_bloom_customscan'
+relocatable = true
--
2.50.1 (Apple Git-155)
From 903311f0c1a494f530dfa7dba92edad518eb9329 Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <[email protected]>
Date: Thu, 2 Jul 2026 16:29:26 -0300
Subject: [PATCH v4 4/4] Allow a CustomScan to consume a pushed-down hashjoin
Bloom filter
Extend the hashjoin Bloom-filter pushdown so that a base-relation
CustomScan can build filter-aware paths, be chosen as a filter
recipient, and consume the filter inside its own scan loop The whole
mechanism is gated on a new opt-in path flag,
CUSTOMPATH_SUPPORT_BLOOM_FILTERS; providers that do not set it, and
heap, are unaffected.
The core of this change is teaching path generation about the custom
scan:
* generate_expected_filter_paths()/create_filtered_scan_path() now clone
a base-relation CustomPath into filter-bearing variants when the path
advertised CUSTOMPATH_SUPPORT_BLOOM_FILTERS, exactly as they already do
for the stock scan path types.
* find_bloom_filter_recipient() treats a base-rel CustomScan
(scanrelid > 0) that carries the flag the same as a SeqScan. The probe
is not wired into ExecScanExtended() -- a CustomScan dispatches to the
provider's ExecCustomScan -- so the provider calls ExecBloomFilters()
itself at whatever granularity it supports. ExecInitCustomScan()
compiles the probe state up front via ExecInitBloomFilters(), and
set_customscan_references() fixes the pushed key expressions for a
base-relation scan just like the scan qual.
In addition, an opted-in recipient gets two things a stock scan does
not:
* Per-key filters. Alongside the combined-hash filter, the hash join
optionally builds one Bloom filter per join key.
The combined filter, keyed on the hash of all keys together, stays the
default and remains the more selective one for a per-row probe: per-key
filters only test whether each column's value appears somewhere in the
build side, so on a multi-column join they are strictly weaker. What
they enable is testing a single key column on its own (a column store
can check one column against its per-column dictionary or zone map and
skip whole row groups before decompression, which the combined filter
cannot support). The build reuses per-key inner hash ExprStates; the
extra CPU and memory are paid only by a consumer that opted in, and a
recipient correlates HashState.perkey_filters[i] with
BloomFilter.filter_exprs[i] by position. Heap and single-key joins are
unaffected.
* Eager filter build. When the outer relation's startup cost is below
the hash-table build cost, ExecHashJoinImpl fetches the first outer
tuple before building the hash table to take the empty-outer shortcut.
For a CustomScan that applies the filter in its own scan loop that is
too late -- its first tuple request may decompress a whole row group
before the filter exists. A HashJoin.bloom_eager flag, set when the
filter is pushed to such a recipient, tells the executor to skip the
prefetch and build the hash table (and filter) first. Only such a
recipient pays the cost of a possibly-needless hash build on an empty
outer.
Based on previous patches from Andrew Dunstan, adapted to the path-based
pushdown design.
---
src/backend/executor/nodeCustom.c | 11 +++++
src/backend/executor/nodeHash.c | 35 +++++++++++++++
src/backend/executor/nodeHashjoin.c | 36 +++++++++++++++
src/backend/optimizer/path/allpaths.c | 14 ++++++
src/backend/optimizer/plan/createplan.c | 45 +++++++++++++++++++
src/backend/optimizer/plan/setrefs.c | 10 +++++
src/backend/optimizer/util/pathnode.c | 23 +++++++---
src/include/nodes/execnodes.h | 14 ++++++
src/include/nodes/extensible.h | 2 +
src/include/nodes/plannodes.h | 21 +++++++++
.../expected/test_bloom_customscan.out | 9 ++--
.../test_bloom_customscan.c | 5 +++
12 files changed, 215 insertions(+), 10 deletions(-)
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index b7cc890cd20..c18dcb1035d 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -101,6 +101,17 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
css->ss.ps.qual =
ExecInitQual(cscan->scan.plan.qual, (PlanState *) css);
+ /*
+ * Set up any bloom filters a hash join pushed down to this scan (see
+ * nodeHashjoin.c). This compiles the probe expressions against the scan
+ * tuple slot; the provider is responsible for actually probing them with
+ * ExecBloomFilters() from its ExecCustomScan callback, at whatever
+ * granularity it supports. A no-op unless the provider advertised
+ * CUSTOMPATH_SUPPORT_BLOOM_FILTERS and the planner found a filter to
+ * push.
+ */
+ ExecInitBloomFilters((PlanState *) css, css->ss.ss_ScanTupleSlot);
+
/*
* The callback of custom-scan provider applies the final initialization
* of the custom-scan-state node according to its logic.
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 37224324bce..2b045eae186 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -197,6 +197,25 @@ MultiExecPrivateHash(HashState *node)
(unsigned char *) &hashvalue,
sizeof(hashvalue));
+ /*
+ * Likewise for the optional per-key filters, using the per-key
+ * (single-key) hash ExprStates. Same econtext as the combined
+ * hash above (ecxt_outertuple is the just-fetched inner tuple).
+ */
+ for (int k = 0; k < node->perkey_nfilters; k++)
+ {
+ bool keyisnull;
+ uint32 keyhash;
+
+ keyhash = DatumGetUInt32(ExecEvalExprSwitchContext(node->perkey_hash[k],
+ econtext,
+ &keyisnull));
+ if (!keyisnull)
+ bloom_add_element(node->perkey_filters[k],
+ (unsigned char *) &keyhash,
+ sizeof(keyhash));
+ }
+
bucketNumber = ExecHashGetSkewBucket(hashtable, hashvalue);
if (bucketNumber != INVALID_SKEW_BUCKET_NO)
{
@@ -722,6 +741,22 @@ ExecHashTableCreate(HashState *state)
oldctx = MemoryContextSwitchTo(hashtable->hashCxt);
state->bloom_filter = bloom_create((int64) Max(rows, 1.0),
bloom_work_mem, 0);
+
+ /*
+ * If a recipient opted in, also build one filter per join key (in
+ * addition to the combined one above). These let a recipient test an
+ * individual key column on its own; they are less selective than the
+ * combined filter, so they are built only on demand.
+ */
+ if (state->want_perkey_bloom)
+ {
+ state->perkey_filters = palloc_array(struct bloom_filter *,
+ state->perkey_nfilters);
+ for (int i = 0; i < state->perkey_nfilters; i++)
+ state->perkey_filters[i] = bloom_create((int64) Max(rows, 1.0),
+ bloom_work_mem, 0);
+ }
+
MemoryContextSwitchTo(oldctx);
}
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index e3467f14739..c501b98067d 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -317,6 +317,17 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel)
*/
node->hj_FirstOuterTupleSlot = NULL;
}
+ else if (((HashJoin *) node->js.ps.plan)->bloom_eager)
+ {
+ /*
+ * We pushed a bloom filter to a CustomScan on the outer
+ * side that wants it at scan start (e.g. to skip row groups
+ * before decompression). Skip the empty-outer prefetch and
+ * build the hash table -- and the filter -- first, so it is
+ * ready before the outer scan produces its first tuple.
+ */
+ node->hj_FirstOuterTupleSlot = NULL;
+ }
else if (HJ_FILL_OUTER(node) ||
(outerNode->plan->startup_cost < hashNode->ps.plan->total_cost &&
!node->hj_OuterNotEmpty))
@@ -908,6 +919,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
hashState = castNode(HashState, innerPlanState(hjstate));
hashState->want_bloom_filter = (node->bloom_consumer_count > 0);
hashState->bloom_filter_id = node->bloom_filter_id;
+ hashState->want_perkey_bloom = node->bloom_perkey;
/*
* Initialize result slot, type and projection.
@@ -1031,6 +1043,28 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
&hashstate->ps,
0);
+ /*
+ * If a recipient opted in to per-key bloom filters, build one inner
+ * (single-key) hash ExprState per join key, used by the Hash node to
+ * populate the per-key filters. The combined hash above cannot be
+ * decomposed, so this is the extra cost a per-key consumer pays.
+ */
+ if (hashstate->want_perkey_bloom)
+ {
+ hashstate->perkey_nfilters = nkeys;
+ hashstate->perkey_hash = palloc_array(ExprState *, nkeys);
+ for (int i = 0; i < nkeys; i++)
+ hashstate->perkey_hash[i] =
+ ExecBuildHash32Expr(hashstate->ps.ps_ResultTupleDesc,
+ hashstate->ps.resultops,
+ &inner_hashfuncid[i],
+ list_make1_oid(list_nth_oid(node->hashcollations, i)),
+ list_make1(list_nth(hash->hashkeys, i)),
+ &hash_strict[i],
+ &hashstate->ps,
+ 0);
+ }
+
/* Remember whether we need to save tuples with null join keys */
hjstate->hj_KeepNullTuples = HJ_FILL_OUTER(hjstate);
hashstate->keep_null_tuples = HJ_FILL_INNER(hjstate);
@@ -1118,6 +1152,7 @@ ExecEndHashJoin(HashJoinState *node)
ExecHashTableDestroy(node->hj_HashTable);
node->hj_HashTable = NULL;
hashNode->bloom_filter = NULL;
+ hashNode->perkey_filters = NULL;
}
/*
@@ -1775,6 +1810,7 @@ ExecReScanHashJoin(HashJoinState *node)
* freed by the ExecHashTableDestroy call.
*/
hashNode->bloom_filter = NULL;
+ hashNode->perkey_filters = NULL;
/*
* if chgParam of subnode is not null then plan will be re-scanned
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8829c7c3108..72c51422394 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -25,6 +25,7 @@
#include "catalog/pg_proc.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
+#include "nodes/extensible.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
@@ -1294,6 +1295,19 @@ generate_expected_filter_paths(PlannerInfo *root, RelOptInfo *rel)
case T_TidRangePath:
basepaths = lappend(basepaths, path);
break;
+ case T_CustomPath:
+
+ /*
+ * A base-relation CustomScan can receive a pushed-down filter,
+ * but only if the provider advertised that it knows how to
+ * consume one (it probes the filter itself inside its scan
+ * loop; see find_bloom_filter_recipient in createplan.c and
+ * ExecInitCustomScan). Providers that don't opt in, like heap,
+ * are unaffected.
+ */
+ if (((CustomPath *) path)->flags & CUSTOMPATH_SUPPORT_BLOOM_FILTERS)
+ basepaths = lappend(basepaths, path);
+ break;
default:
break;
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 51990b98419..b9e481ba298 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4781,6 +4781,25 @@ find_bloom_filter_recipient(Plan *plan, Index target_relid)
return plan;
return NULL;
}
+ case T_CustomScan:
+ {
+ /*
+ * A CustomScan on a base relation can act as a recipient, but
+ * only if the provider advertised that it knows how to consume
+ * a pushed-down bloom filter. Unlike the stock scans, the
+ * probe is not performed by ExecScanExtended() (a CustomScan
+ * dispatches to the provider's own ExecCustomScan); the
+ * provider is responsible for calling ExecBloomFilters() at
+ * whatever granularity it likes. Non-leaf custom nodes have
+ * scanrelid == 0 and so are rejected by the relid test.
+ */
+ CustomScan *cscan = (CustomScan *) plan;
+
+ if ((cscan->flags & CUSTOMPATH_SUPPORT_BLOOM_FILTERS) &&
+ cscan->scan.scanrelid == target_relid)
+ return plan;
+ return NULL;
+ }
case T_Sort:
case T_IncrementalSort:
case T_Material:
@@ -4910,6 +4929,32 @@ try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan,
recipient->bloom_filters = lappend(recipient->bloom_filters, bf);
+ /*
+ * A CustomScan recipient that opted in consumes the filter in its own scan
+ * loop, possibly at the storage level, so it wants two things a stock scan
+ * does not.
+ */
+ if (IsA(recipient, CustomScan) &&
+ (((CustomScan *) recipient)->flags & CUSTOMPATH_SUPPORT_BLOOM_FILTERS))
+ {
+ /*
+ * Build the hash table (and filter) before the outer scan starts, so
+ * the filter is available on the first tuple request rather than after
+ * a batch has already been scanned unfiltered.
+ */
+ hj->bloom_eager = true;
+
+ /*
+ * Also build a separate filter per join key, so the recipient can test
+ * a single column on its own (e.g. against a per-column dictionary or
+ * zone map). The combined filter is always built and is the more
+ * selective one for a per-row probe; there is nothing to gain for a
+ * single-key join, where the two coincide.
+ */
+ if (list_length(hj->hashkeys) > 1)
+ hj->bloom_perkey = true;
+ }
+
hj->bloom_consumer_count++;
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 0059acfccbe..74c7a5bf3a5 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1826,6 +1826,16 @@ set_customscan_references(PlannerInfo *root,
cscan->custom_exprs =
fix_scan_list(root, cscan->custom_exprs,
rtoffset, NUM_EXEC_QUAL((Plan *) cscan));
+
+ /*
+ * Bloom filters pushed down to a base-relation CustomScan: the key
+ * expressions are plain Vars of the scanned relation, so they are
+ * fixed up the same way as the scan qual. (A CustomScan emitting a
+ * custom_scan_tlist takes the branch above and would instead need
+ * fix_upper_expr against the tlist index, like IndexOnlyScan; no
+ * in-tree provider needs that yet.)
+ */
+ fix_scan_bloom_filters(root, (Plan *) cscan, rtoffset);
}
/* Adjust child plan-nodes recursively, if needed */
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 9cd9188a1cf..65690553cf5 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -501,6 +501,17 @@ create_filtered_scan_path(PlannerInfo *root, Path *subpath, List *filters)
case T_TidRangePath:
sz = sizeof(TidRangePath);
break;
+ case T_CustomPath:
+
+ /*
+ * A base-relation CustomScan provider that advertised
+ * CUSTOMPATH_SUPPORT_BLOOM_FILTERS can receive a pushed-down
+ * filter and apply it in its own scan loop
+ * .generate_expected_filter_paths() only offers such paths here,
+ * so we need not re-check the flag.
+ */
+ sz = sizeof(CustomPath);
+ break;
default:
/* unsupported scan path type */
return NULL;
@@ -620,10 +631,10 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
/*
* Paths carrying different sets of expected Bloom filters serve
- * different purposes (each may be consumed by a different parent join,
- * or none at all), and their cost/row estimates aren't directly
- * comparable. So if the two paths don't expect the same filters, keep
- * both and don't let either dominate the other.
+ * different purposes (each may be consumed by a different parent
+ * join, or none at all), and their cost/row estimates aren't directly
+ * comparable. So if the two paths don't expect the same filters,
+ * keep both and don't let either dominate the other.
*/
if (!expected_filters_equal(new_path->expected_filters,
old_path->expected_filters))
@@ -854,8 +865,8 @@ add_path_precheck(RelOptInfo *parent_rel, int disabled_nodes,
* filters of their own, so any filter-bearing old path is a
* non-comparable speculative path and must not be allowed to dominate
* (and thereby suppress) the new path. Skipping them here also
- * guarantees that a join relation always retains at least one ordinary,
- * filter-free path to serve as cheapest_total_path.
+ * guarantees that a join relation always retains at least one
+ * ordinary, filter-free path to serve as cheapest_total_path.
*/
if (old_path->expected_filters != NIL)
continue;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index aad721f3421..873c02fb080 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2786,6 +2786,20 @@ typedef struct HashState
*/
struct bloom_filter *bloom_filter;
+ /*
+ * Optional per-key bloom filters, built in addition to the combined
+ * bloom_filter above when a recipient opted in (HashJoin.bloom_perkey).
+ * perkey_filters has perkey_nfilters entries, one per join key, in hashkey
+ * order; a recipient correlates them with BloomFilter.filter_exprs by
+ * position. perkey_hash holds the matching per-key (single-key) hash
+ * ExprStates used to populate them during the build. All live in hashCxt
+ * and follow the same lifecycle as bloom_filter.
+ */
+ bool want_perkey_bloom;
+ int perkey_nfilters;
+ struct bloom_filter **perkey_filters;
+ ExprState **perkey_hash;
+
/*
* Counters with total per-filter instrumentation. Separate from the
* per-recipient counters in BloomFilterState. Redundant, but will be
diff --git a/src/include/nodes/extensible.h b/src/include/nodes/extensible.h
index 517db95c4a3..ea2cef4fe3b 100644
--- a/src/include/nodes/extensible.h
+++ b/src/include/nodes/extensible.h
@@ -84,6 +84,8 @@ extern const ExtensibleNodeMethods *GetExtensibleNodeMethods(const char *extnode
#define CUSTOMPATH_SUPPORT_BACKWARD_SCAN 0x0001
#define CUSTOMPATH_SUPPORT_MARK_RESTORE 0x0002
#define CUSTOMPATH_SUPPORT_PROJECTION 0x0004
+/* provider can accept a hashjoin bloom filter pushed down to its scan */
+#define CUSTOMPATH_SUPPORT_BLOOM_FILTERS 0x0008
/*
* Custom path methods. Mostly, we just need to know how to convert a
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 4e35d77cc49..0e011f3d4e2 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1124,6 +1124,27 @@ typedef struct HashJoin
* Zero when this HashJoin has no consumers.
*/
int bloom_filter_id;
+
+ /*
+ * Whether to also build one bloom filter per join key (in addition to the
+ * combined-hash filter), so that a recipient can test an individual key
+ * column on its own -- e.g. a column store probing a per-column dictionary
+ * or zone map. Set at plan time only when the recipient is a CustomScan
+ * that advertised CUSTOMPATH_SUPPORT_BLOOM_FILTERS. The combined filter is
+ * always built and remains the more selective one; per-key filters are an
+ * opt-in extra that nobody else pays for.
+ */
+ bool bloom_perkey;
+
+ /*
+ * Whether to build the hash table (and bloom filter) before fetching the
+ * first outer tuple, skipping the empty-outer prefetch optimization. Set
+ * at plan time when the filter is pushed to a CustomScan recipient, which
+ * may want to apply the filter the moment its scan starts (e.g. a column
+ * store skipping row groups before decompression) rather than after having
+ * already produced a batch unfiltered. See ExecHashJoinImpl.
+ */
+ bool bloom_eager;
} HashJoin;
/* ----------------
diff --git a/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
index e1ce5903a02..8e784209901 100644
--- a/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
+++ b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
@@ -33,9 +33,10 @@ SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
Output: f.a
-> Hash
Output: d.id
+ Bloom Filter 1
-> Custom Scan (TestBloomCustomScan) on public.cs_dim d
Output: d.id
-(10 rows)
+(11 rows)
-- Single-key join: the filter must actually reject fact rows.
SELECT test_bloom_cs_reset();
@@ -53,7 +54,7 @@ SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
filter_rejected_rows
----------------------
- f
+ t
(1 row)
-- Correctness: the result must be identical with and without the filter.
@@ -97,13 +98,13 @@ SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id AND f.b = d.id2;
SELECT test_bloom_cs_perkey_built() AS perkey_filters_built;
perkey_filters_built
----------------------
- f
+ t
(1 row)
SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
filter_rejected_rows
----------------------
- f
+ t
(1 row)
-- cleanup
diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan.c b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
index 124cbef9ded..99338c7d361 100644
--- a/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
+++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
@@ -145,6 +145,7 @@ bloom_cs_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti,
cpath->path.total_cost = total_cost;
cpath->path.pathkeys = NIL;
+ cpath->flags = CUSTOMPATH_SUPPORT_BLOOM_FILTERS;
cpath->custom_paths = NIL;
cpath->custom_private = NIL;
cpath->methods = &bloom_cs_path_methods;
@@ -234,6 +235,10 @@ bloom_cs_maybe_inspect_perkey(BloomCSScanState * bcss)
if (hashNode->bloom_filter == NULL)
return;
+ if (hashNode->want_perkey_bloom &&
+ hashNode->perkey_nfilters > 0 &&
+ hashNode->perkey_filters != NULL)
+ test_bloom_cs_perkey_seen = true;
}
bcss->inspected = true;
--
2.50.1 (Apple Git-155)
Attachments:
[text/plain] v4-0001-PoC-hashjoin-bloom-filter-pushdown.patch (280.8K, ../[email protected]/2-v4-0001-PoC-hashjoin-bloom-filter-pushdown.patch)
download | inline diff:
From 1646d007f42fd4998e32a0a6b16cc6f7b4918a4d Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 20 Jun 2026 20:44:00 +0200
Subject: [PATCH v4 1/4] PoC: hashjoin bloom filter pushdown
When construction hashjoin plans, try to pushdown a Bloom filter built
on the hashtable to a scan node in the outer side of the join. This has
multiple significant benefits:
a) Probing a bloom filter is cheaper than probing a hash table, so if
a tuple gets eliminated using the bloom filter, we save cycles.
b) The Bloom filter is more compact, and so more cache efficient. The
hash table may not even split into memory, and the hashjoin has to
spill data to files. The Bloom filter may still fit into memory,
and eliminate many tuples on the outer side (which reduces the
amount of data spilled to disk).
c) The Bloom filter is pushed to a scan node, which may be multiple
steps before the join. This increases the benefit, because the
eliminated tuples don't need to pass through any of the nodes in
between. This futehr amplifies the difference between probing a
filter and probing the hashtable.
d) If a table joins with multiple other tables (e.g. in a starjoin), the
scan node may receive multiple Bloom filters. The selectivity of the
filters multiply, once again amplifying the benefits. If a scan gets
two filters, each discarding 90% of tuples, the scan will discard 99%
of tuples, i.e. 2 orders of magnitude fewer tuples.
This patch performs Bloom filter pushdown when constructing the plan,
after path selection. That means the filters are not considered when
estimating and costing the paths, and we only have a chance to do the
pushdown if we happen to a pick a plan with a hashjoin. If the pushdown
is what makes the plan fast (faster than plans without hashjoins), we
may not pick it. With our bottom-up planning it's hard to do better.
The decision which Bloom filters to build (and which scan nodes should
evaluate them) happens in create_hashjoin_plan. This registers the
filters in both the hashjoin and the recipient scan node, etc.
Then at execution time, the Hash node builds the filter with the
hashtable, and the scan node probes the Bloom filter similarly to
evaluating the regular quals.
How effective this is depends on how many tuples the filter eliminates.
A highly selective filter (e.g. discarding >90% tuples) is going to be a
win no matter what. But even a "poor" filter (e.g. discarding only 10%
tuples) may still be a win, if the hashjoin has to perform batching, and
thus spill data to disk.
It's hard to know in advance which filters are selective. The patch has
a simple adaptive logic, that disables filters that are not selective
enough (too many probes find a match), and the enables the filter when
the filter gets more selective.
There's a number of open questions to solve:
- Pushdown after path construction means we can't consider Bloom
filters when costing the paths, and the cost are as if no tuples were
eliminated by the scan node. Solving this with the bottom-up planning
is unlikely, or would have disadvantages (e.g. would increase the
number of paths we have to condsider).
- The EXPLAIN ANALYZE output can be somewhat confusing/misleading. The
path estimates don't consider how many rows may be eliminated by the
filter. This may lead to huge differences between estimated and actual
"rows" in the EXPLAIN output, even with perfect estimates. The EXPLAIN
now includes information about Bloom filters (number of probes and
number of discarded rows), but it's still hard to interpret.
- We have little control over the Bloom filter parameters. The library
picks most of the attributes on our behalf. Works reasonably well, but
we may need to know e.g. false positive rate (if it gets too high, the
filter becomes useless, and we should stop using it).
- We only push filters to scan nodes, and only through some other nodes
(e.g. through joins, sort, ...). We could expand this to also pushdown
through aggregations, etc.
- The patch does not support parallel queries. This can be addressed
later, it's certainly doable.
- Similarly, there's no support for partitioned tables (fixing this
should be simpler than supporting parallel queries).
- It might be interesting to allow the scan nodes to use the Bloom
filters in other ways. E.g. it might push the filter to storage, or
perhaps to remote node (with a ForeignScan), and let it do smart things
with it. The storage might prefilter data, foreign server could filter
data on the remote end. That'd require using some well defined and
portable library for the filter.
- The cost model determining which filters are effective is a bit crude
and based on empirical observations. For example the thresholds used
in the adaptive logic are somewhat arbitrary and need more thought.
- We could push filters into other nodes, not just scans. Might be
useful for more complex joins.
---
.../pg_plan_advice/expected/join_order.out | 52 +-
.../pg_plan_advice/expected/join_strategy.out | 20 +-
.../pg_plan_advice/expected/partitionwise.out | 40 +-
contrib/pg_plan_advice/expected/semijoin.out | 24 +-
.../expected/pg_stash_advice.out | 54 ++-
.../expected/level_tracking.out | 24 +-
.../postgres_fdw/expected/postgres_fdw.out | 8 +-
src/backend/commands/explain.c | 189 ++++++++
src/backend/executor/execUtils.c | 2 +
src/backend/executor/nodeBitmapHeapscan.c | 3 +
src/backend/executor/nodeHash.c | 60 +++
src/backend/executor/nodeHashjoin.c | 455 ++++++++++++++++++
src/backend/executor/nodeIndexonlyscan.c | 3 +
src/backend/executor/nodeIndexscan.c | 3 +
src/backend/executor/nodeSamplescan.c | 3 +
src/backend/executor/nodeSeqscan.c | 3 +
src/backend/executor/nodeTidrangescan.c | 3 +
src/backend/executor/nodeTidscan.c | 3 +
src/backend/lib/bloomfilter.c | 19 +
src/backend/optimizer/path/costsize.c | 1 +
src/backend/optimizer/plan/createplan.c | 299 ++++++++++++
src/backend/optimizer/plan/planner.c | 1 +
src/backend/optimizer/plan/setrefs.c | 63 +++
src/backend/utils/misc/guc_parameters.dat | 7 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/executor/execScan.h | 22 +-
src/include/executor/executor.h | 12 +
src/include/executor/nodeHashjoin.h | 9 +
src/include/lib/bloomfilter.h | 2 +
src/include/nodes/execnodes.h | 91 ++++
src/include/nodes/pathnodes.h | 3 +
src/include/nodes/plannodes.h | 51 ++
src/include/optimizer/cost.h | 1 +
src/test/regress/expected/aggregates.out | 8 +-
src/test/regress/expected/eager_aggregate.out | 166 ++++++-
src/test/regress/expected/join.out | 176 +++++--
src/test/regress/expected/join_hash.out | 28 +-
src/test/regress/expected/merge.out | 20 +-
src/test/regress/expected/misc_functions.out | 4 +-
.../regress/expected/partition_aggregate.out | 34 +-
src/test/regress/expected/partition_join.out | 452 ++++++++++++++---
src/test/regress/expected/predicate.out | 8 +-
src/test/regress/expected/privileges.out | 4 +-
src/test/regress/expected/returning.out | 10 +-
src/test/regress/expected/rowsecurity.out | 2 +
src/test/regress/expected/select_views.out | 2 +
src/test/regress/expected/stats_ext.out | 4 +-
src/test/regress/expected/subselect.out | 64 ++-
src/test/regress/expected/sysviews.out | 3 +-
src/test/regress/expected/updatable_views.out | 12 +-
src/test/regress/expected/window.out | 4 +-
src/test/regress/expected/with.out | 16 +-
src/test/regress/sql/rowsecurity.sql | 3 +
src/test/regress/sql/select_views.sql | 3 +
54 files changed, 2313 insertions(+), 241 deletions(-)
diff --git a/contrib/pg_plan_advice/expected/join_order.out b/contrib/pg_plan_advice/expected/join_order.out
index a5a9728e3fd..0e5f93a046f 100644
--- a/contrib/pg_plan_advice/expected/join_order.out
+++ b/contrib/pg_plan_advice/expected/join_order.out
@@ -27,17 +27,21 @@ SELECT * FROM jo_fact f
LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
Generated Plan Advice:
@@ -45,7 +49,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(d2 d1)
SEQ_SCAN(f d2 d1)
NO_GATHER(f d1 d2)
-(16 rows)
+(20 rows)
-- Force a few different join orders. Some of these are very inefficient,
-- but the planner considers them all viable.
@@ -56,17 +60,21 @@ SELECT * FROM jo_fact f
LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim1_id)
+ Bloom Filter 2: keys=(dim2_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on jo_dim2 d2
Filter: (val2 = 1)
Supplied Plan Advice:
@@ -76,7 +84,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(d1 d2)
SEQ_SCAN(f d1 d2)
NO_GATHER(f d1 d2)
-(18 rows)
+(22 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(f d2 d1)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -84,17 +92,21 @@ SELECT * FROM jo_fact f
LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
Supplied Plan Advice:
@@ -104,7 +116,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(d2 d1)
SEQ_SCAN(f d2 d1)
NO_GATHER(f d1 d2)
-(18 rows)
+(22 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(d1 f d2)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -120,7 +132,9 @@ SELECT * FROM jo_fact f
Hash Cond: (d1.id = f.dim1_id)
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_fact f
-> Hash
-> Seq Scan on jo_dim2 d2
@@ -132,7 +146,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(f d2)
SEQ_SCAN(d1 f d2)
NO_GATHER(f d1 d2)
-(18 rows)
+(20 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(f (d1 d2))';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -145,7 +159,9 @@ SELECT * FROM jo_fact f
Hash Join
Hash Cond: ((f.dim1_id = d1.id) AND (f.dim2_id = d2.id))
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim1_id, dim2_id)
-> Hash
+ Bloom Filter 1
-> Nested Loop
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
@@ -160,7 +176,7 @@ SELECT * FROM jo_fact f
HASH_JOIN((d1 d2))
SEQ_SCAN(f d1 d2)
NO_GATHER(f d1 d2)
-(18 rows)
+(20 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(f {d1 d2})';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -173,7 +189,9 @@ SELECT * FROM jo_fact f
Hash Join
Hash Cond: ((f.dim1_id = d1.id) AND (f.dim2_id = d2.id))
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim1_id, dim2_id)
-> Hash
+ Bloom Filter 1
-> Nested Loop
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
@@ -188,7 +206,7 @@ SELECT * FROM jo_fact f
HASH_JOIN((d1 d2))
SEQ_SCAN(f d1 d2)
NO_GATHER(f d1 d2)
-(18 rows)
+(20 rows)
COMMIT;
-- Force a join order by mentioning just a prefix of the join list.
@@ -199,17 +217,21 @@ SELECT * FROM jo_fact f
LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------
Hash Join
Hash Cond: (d2.id = f.dim2_id)
-> Seq Scan on jo_dim2 d2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Seq Scan on jo_fact f
+ Bloom Filter 1: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on jo_dim1 d1
Filter: (val1 = 1)
Supplied Plan Advice:
@@ -219,7 +241,7 @@ SELECT * FROM jo_fact f
HASH_JOIN(d1 (f d1))
SEQ_SCAN(d2 f d1)
NO_GATHER(f d1 d2)
-(18 rows)
+(22 rows)
SET LOCAL pg_plan_advice.advice = 'join_order(d2 d1)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
diff --git a/contrib/pg_plan_advice/expected/join_strategy.out b/contrib/pg_plan_advice/expected/join_strategy.out
index 0f9db692190..ce105856fda 100644
--- a/contrib/pg_plan_advice/expected/join_strategy.out
+++ b/contrib/pg_plan_advice/expected/join_strategy.out
@@ -15,19 +15,21 @@ VACUUM ANALYZE join_fact;
-- We expect a hash join by default.
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+---------------------------------------
Hash Join
Hash Cond: (f.dim_id = d.id)
-> Seq Scan on join_fact f
+ Bloom Filter 1: keys=(dim_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on join_dim d
Generated Plan Advice:
JOIN_ORDER(f d)
HASH_JOIN(d)
SEQ_SCAN(f d)
NO_GATHER(f d)
-(10 rows)
+(12 rows)
-- Try forcing each join method in turn with join_dim as the inner table.
-- All of these should work except for MERGE_JOIN_MATERIALIZE; that will
@@ -37,12 +39,14 @@ BEGIN;
SET LOCAL pg_plan_advice.advice = 'HASH_JOIN(d)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+---------------------------------------
Hash Join
Hash Cond: (f.dim_id = d.id)
-> Seq Scan on join_fact f
+ Bloom Filter 1: keys=(dim_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on join_dim d
Supplied Plan Advice:
HASH_JOIN(d) /* matched */
@@ -51,7 +55,7 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE)
HASH_JOIN(d)
SEQ_SCAN(f d)
NO_GATHER(f d)
-(12 rows)
+(14 rows)
SET LOCAL pg_plan_advice.advice = 'MERGE_JOIN_MATERIALIZE(d)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -162,7 +166,9 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE)
Hash Join
Hash Cond: (d.id = f.dim_id)
-> Seq Scan on join_dim d
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on join_fact f
Supplied Plan Advice:
HASH_JOIN(f) /* matched */
@@ -171,7 +177,7 @@ EXPLAIN (COSTS OFF, PLAN_ADVICE)
HASH_JOIN(f)
SEQ_SCAN(d f)
NO_GATHER(f d)
-(12 rows)
+(14 rows)
SET LOCAL pg_plan_advice.advice = 'MERGE_JOIN_MATERIALIZE(f)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
diff --git a/contrib/pg_plan_advice/expected/partitionwise.out b/contrib/pg_plan_advice/expected/partitionwise.out
index 2b3d0a82443..3b003a927ac 100644
--- a/contrib/pg_plan_advice/expected/partitionwise.out
+++ b/contrib/pg_plan_advice/expected/partitionwise.out
@@ -60,7 +60,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_1.id = pt3_1.id)
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt3a pt3_1
Filter: (val3 = 1)
-> Index Scan using pt1a_pkey on pt1a pt1_1
@@ -71,7 +73,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_2.id = pt3_2.id)
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt3b pt3_2
Filter: (val3 = 1)
-> Index Scan using pt1b_pkey on pt1b pt1_2
@@ -82,7 +86,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_3.id = pt3_3.id)
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt3c pt3_3
Filter: (val3 = 1)
-> Index Scan using pt1c_pkey on pt1c pt1_3
@@ -101,7 +107,7 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2 pt3))
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(47 rows)
+(53 rows)
-- Suppress partitionwise join, or do it just partially.
BEGIN;
@@ -169,21 +175,27 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt1_1.id = pt2_1.id)
-> Seq Scan on pt1a pt1_1
Filter: (val1 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
-> Hash Join
Hash Cond: (pt1_2.id = pt2_2.id)
-> Seq Scan on pt1b pt1_2
Filter: (val1 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
-> Hash Join
Hash Cond: (pt1_3.id = pt2_3.id)
-> Seq Scan on pt1c pt1_3
Filter: (val1 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
-> Hash
@@ -209,7 +221,7 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2) pt3)
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(47 rows)
+(53 rows)
COMMIT;
-- Test conflicting advice.
@@ -227,7 +239,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_1.id = pt3_1.id)
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt3a pt3_1
Filter: (val3 = 1)
-> Index Scan using pt1a_pkey on pt1a pt1_1
@@ -238,7 +252,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_2.id = pt3_2.id)
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt3b pt3_2
Filter: (val3 = 1)
-> Index Scan using pt1b_pkey on pt1b pt1_2
@@ -249,7 +265,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_3.id = pt3_3.id)
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt3c pt3_3
Filter: (val3 = 1)
-> Index Scan using pt1c_pkey on pt1c pt1_3
@@ -271,7 +289,7 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2 pt3))
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(51 rows)
+(57 rows)
COMMIT;
-- Can't force a partitionwise join with a mismatched table.
@@ -321,7 +339,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt3_1.id = pt2_1.id)
-> Seq Scan on pt3a pt3_1
Filter: (val3 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
-> Index Scan using pt1a_pkey on pt1a pt1_1
@@ -332,7 +352,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_2.id = pt3_2.id)
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt3b pt3_2
Filter: (val3 = 1)
-> Index Scan using pt1b_pkey on pt1b pt1_2
@@ -343,7 +365,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_3.id = pt3_3.id)
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt3c pt3_3
Filter: (val3 = 1)
-> Index Scan using pt1c_pkey on pt1c pt1_3
@@ -364,7 +388,7 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2 pt3))
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(49 rows)
+(55 rows)
SET LOCAL pg_plan_advice.advice = 'JOIN_ORDER(pt3/pt3a pt2/pt2a pt1/pt1a)';
EXPLAIN (PLAN_ADVICE, COSTS OFF)
@@ -378,7 +402,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt3_1.id = pt2_1.id)
-> Seq Scan on pt3a pt3_1
Filter: (val3 = 1)
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pt2a pt2_1
Filter: (val2 = 1)
-> Index Scan using pt1a_pkey on pt1a pt1_1
@@ -389,7 +415,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_2.id = pt3_2.id)
-> Seq Scan on pt2b pt2_2
Filter: (val2 = 1)
+ Bloom Filter 2: keys=(id)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pt3b pt3_2
Filter: (val3 = 1)
-> Index Scan using pt1b_pkey on pt1b pt1_2
@@ -400,7 +428,9 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
Hash Cond: (pt2_3.id = pt3_3.id)
-> Seq Scan on pt2c pt2_3
Filter: (val2 = 1)
+ Bloom Filter 3: keys=(id)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pt3c pt3_3
Filter: (val3 = 1)
-> Index Scan using pt1c_pkey on pt1c pt1_3
@@ -421,6 +451,6 @@ SELECT * FROM pt1, pt2, pt3 WHERE pt1.id = pt2.id AND pt2.id = pt3.id
PARTITIONWISE((pt1 pt2 pt3))
NO_GATHER(pt1/public.pt1a pt1/public.pt1b pt1/public.pt1c pt2/public.pt2a
pt2/public.pt2b pt2/public.pt2c pt3/public.pt3a pt3/public.pt3b pt3/public.pt3c)
-(49 rows)
+(55 rows)
COMMIT;
diff --git a/contrib/pg_plan_advice/expected/semijoin.out b/contrib/pg_plan_advice/expected/semijoin.out
index db6b069ec8e..f60778d0d38 100644
--- a/contrib/pg_plan_advice/expected/semijoin.out
+++ b/contrib/pg_plan_advice/expected/semijoin.out
@@ -75,7 +75,9 @@ SELECT * FROM sj_wide
Hash Semi Join
Hash Cond: ((sj_wide.id = "*VALUES*".column1) AND (sj_wide.val1 = "*VALUES*".column2))
-> Seq Scan on sj_wide
+ Bloom Filter 1: keys=(id, val1)
-> Hash
+ Bloom Filter 1
-> Values Scan on "*VALUES*"
Supplied Plan Advice:
SEMIJOIN_NON_UNIQUE("*VALUES*") /* matched */
@@ -85,7 +87,7 @@ SELECT * FROM sj_wide
SEQ_SCAN(sj_wide)
SEMIJOIN_NON_UNIQUE("*VALUES*")
NO_GATHER(sj_wide "*VALUES*")
-(13 rows)
+(15 rows)
COMMIT;
-- Because this table is narrower than the previous one, a sequential scan
@@ -100,7 +102,9 @@ SELECT * FROM sj_narrow
Hash Semi Join
Hash Cond: ((sj_narrow.id = "*VALUES*".column1) AND (sj_narrow.val1 = "*VALUES*".column2))
-> Seq Scan on sj_narrow
+ Bloom Filter 1: keys=(id, val1)
-> Hash
+ Bloom Filter 1
-> Values Scan on "*VALUES*"
Generated Plan Advice:
JOIN_ORDER(sj_narrow "*VALUES*")
@@ -108,7 +112,7 @@ SELECT * FROM sj_narrow
SEQ_SCAN(sj_narrow)
SEMIJOIN_NON_UNIQUE("*VALUES*")
NO_GATHER(sj_narrow "*VALUES*")
-(11 rows)
+(13 rows)
-- Here, we expect advising a unique semijoin to swith to the same plan that
-- we got with sj_wide, and advising a non-unique semijoin should not change
@@ -123,7 +127,9 @@ SELECT * FROM sj_narrow
Hash Join
Hash Cond: ((sj_narrow.id = "*VALUES*".column1) AND (sj_narrow.val1 = "*VALUES*".column2))
-> Seq Scan on sj_narrow
+ Bloom Filter 1: keys=(id, val1)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: "*VALUES*".column1, "*VALUES*".column2
-> Values Scan on "*VALUES*"
@@ -135,7 +141,7 @@ SELECT * FROM sj_narrow
SEQ_SCAN(sj_narrow)
SEMIJOIN_UNIQUE("*VALUES*")
NO_GATHER(sj_narrow "*VALUES*")
-(15 rows)
+(17 rows)
SET LOCAL pg_plan_advice.advice = 'semijoin_non_unique("*VALUES*")';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
@@ -146,7 +152,9 @@ SELECT * FROM sj_narrow
Hash Semi Join
Hash Cond: ((sj_narrow.id = "*VALUES*".column1) AND (sj_narrow.val1 = "*VALUES*".column2))
-> Seq Scan on sj_narrow
+ Bloom Filter 1: keys=(id, val1)
-> Hash
+ Bloom Filter 1
-> Values Scan on "*VALUES*"
Supplied Plan Advice:
SEMIJOIN_NON_UNIQUE("*VALUES*") /* matched */
@@ -156,7 +164,7 @@ SELECT * FROM sj_narrow
SEQ_SCAN(sj_narrow)
SEMIJOIN_NON_UNIQUE("*VALUES*")
NO_GATHER(sj_narrow "*VALUES*")
-(13 rows)
+(15 rows)
COMMIT;
-- In the above example, we made the outer side of the join unique, but here,
@@ -261,7 +269,9 @@ SELECT * FROM generate_series(1,1000) g
Hash Right Semi Join
Hash Cond: (sj_narrow.val1 = g.g)
-> Seq Scan on sj_narrow
+ Bloom Filter 1: keys=(val1)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series g
Supplied Plan Advice:
SEMIJOIN_NON_UNIQUE(sj_narrow) /* matched */
@@ -272,7 +282,7 @@ SELECT * FROM generate_series(1,1000) g
SEQ_SCAN(sj_narrow)
SEMIJOIN_NON_UNIQUE(sj_narrow)
NO_GATHER(g sj_narrow)
-(14 rows)
+(16 rows)
COMMIT;
-- However, mentioning the wrong side of the join should result in an advice
@@ -407,11 +417,13 @@ SELECT 1 FROM generate_series(1, 1000) g WHERE EXISTS
-> Unique
-> Nested Loop
-> Index Only Scan using sj_narrow_pkey on sj_narrow t2
+ Bloom Filter 1: keys=(id)
-> Materialize
-> Nested Loop Left Join
-> Result
-> Seq Scan on sj_narrow
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series g
Generated Plan Advice:
JOIN_ORDER(t2 ("*RESULT*" sj_narrow) g)
@@ -422,5 +434,5 @@ SELECT 1 FROM generate_series(1, 1000) g WHERE EXISTS
INDEX_ONLY_SCAN(t2 public.sj_narrow_pkey)
SEMIJOIN_UNIQUE((t2 sj_narrow "*RESULT*"))
NO_GATHER(g t2 sj_narrow "*RESULT*")
-(20 rows)
+(22 rows)
diff --git a/contrib/pg_stash_advice/expected/pg_stash_advice.out b/contrib/pg_stash_advice/expected/pg_stash_advice.out
index 788da854aa7..d62afaa6651 100644
--- a/contrib/pg_stash_advice/expected/pg_stash_advice.out
+++ b/contrib/pg_stash_advice/expected/pg_stash_advice.out
@@ -57,20 +57,24 @@ EXPLAIN (COSTS OFF)
SELECT * FROM aa_fact f LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on aa_dim1 d1
Filter: (val1 = 1)
-(11 rows)
+(15 rows)
-- Force an index scan on dim1
SELECT pg_set_stashed_advice('regress_stash', :'qid',
@@ -91,15 +95,19 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Index Scan using aa_dim1_pkey on aa_dim1 d1
Filter: (val1 = 1)
Supplied Plan Advice:
INDEX_SCAN(d1 aa_dim1_pkey) /* matched */
-(13 rows)
+(17 rows)
-- Force an alternative join order
SELECT pg_set_stashed_advice('regress_stash', :'qid',
@@ -113,22 +121,26 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim1_id)
+ Bloom Filter 2: keys=(dim2_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim1 d1
Filter: (val1 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
Supplied Plan Advice:
JOIN_ORDER(f d1 d2) /* matched */
-(13 rows)
+(17 rows)
-- Force an alternative join strategy
SELECT pg_set_stashed_advice('regress_stash', :'qid',
@@ -148,7 +160,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Index Scan using aa_dim1_pkey on aa_dim1 d1
@@ -156,7 +170,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
Filter: (val1 = 1)
Supplied Plan Advice:
NESTED_LOOP_PLAIN(d1) /* matched */
-(12 rows)
+(14 rows)
-- Add a useless extra entry to our test stash. Shouldn't change the result
-- from the previous test.
@@ -178,7 +192,9 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Index Scan using aa_dim1_pkey on aa_dim1 d1
@@ -186,7 +202,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
Filter: (val1 = 1)
Supplied Plan Advice:
NESTED_LOOP_PLAIN(d1) /* matched */
-(12 rows)
+(14 rows)
-- Try an empty stash to be sure it does nothing
SELECT pg_create_advice_stash('regress_empty_stash');
@@ -200,20 +216,24 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on aa_dim1 d1
Filter: (val1 = 1)
-(11 rows)
+(15 rows)
-- Test that we can list each stash individually and all of them together,
-- but not a nonexistent stash.
@@ -263,20 +283,24 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f
LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id
LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id
WHERE val1 = 1 AND val2 = 1;
- QUERY PLAN
-------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
Hash Cond: (f.dim1_id = d1.id)
-> Hash Join
Hash Cond: (f.dim2_id = d2.id)
-> Seq Scan on aa_fact f
+ Bloom Filter 1: keys=(dim2_id)
+ Bloom Filter 2: keys=(dim1_id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on aa_dim2 d2
Filter: (val2 = 1)
-> Hash
+ Bloom Filter 2
-> Seq Scan on aa_dim1 d1
Filter: (val1 = 1)
-(11 rows)
+(15 rows)
SELECT * FROM pg_get_advice_stashes() ORDER BY stash_name;
stash_name | num_entries
diff --git a/contrib/pg_stat_statements/expected/level_tracking.out b/contrib/pg_stat_statements/expected/level_tracking.out
index 832d65e97ca..db84cc6af01 100644
--- a/contrib/pg_stat_statements/expected/level_tracking.out
+++ b/contrib/pg_stat_statements/expected/level_tracking.out
@@ -189,9 +189,11 @@ EXPLAIN (COSTS OFF) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF) SELECT 1 UNION SELECT 2;
QUERY PLAN
@@ -315,9 +317,11 @@ EXPLAIN (COSTS OFF) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF) SELECT 1 UNION SELECT 2;
QUERY PLAN
@@ -536,9 +540,11 @@ EXPLAIN (COSTS OFF) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
QUERY PLAN
------------
@@ -664,9 +670,11 @@ EXPLAIN (COSTS OFF) MERGE INTO stats_track_tab USING (SELECT id FROM generate_se
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
QUERY PLAN
------------
@@ -772,9 +780,11 @@ EXPLAIN (COSTS OFF) WITH a AS (SELECT 4) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF) WITH a AS (select 4) SELECT 1 UNION SELECT 2;
QUERY PLAN
@@ -866,9 +876,11 @@ EXPLAIN (COSTS OFF) WITH a AS (SELECT 4) MERGE INTO stats_track_tab
-> Hash Right Join
Hash Cond: (stats_track_tab.x = id.id)
-> Seq Scan on stats_track_tab
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series id
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF) WITH a AS (select 4) SELECT 1 UNION SELECT 2;
QUERY PLAN
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0805c56cb1b..428b7d8c2b3 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -11823,12 +11823,14 @@ INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AN
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.b = t1_3.b))
-> Seq Scan on public.async_p3 t2_3
Output: t2_3.a, t2_3.b, t2_3.c
+ Bloom Filter 1: keys=(t2_3.a, t2_3.b)
-> Hash
Output: t1_3.a, t1_3.b, t1_3.c
+ Bloom Filter 1
-> Seq Scan on public.async_p3 t1_3
Output: t1_3.a, t1_3.b, t1_3.c
Filter: ((t1_3.b % 100) = 0)
-(20 rows)
+(22 rows)
INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0;
SELECT * FROM join_tbl ORDER BY a1;
@@ -11886,12 +11888,14 @@ INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.b = t1_3.b))
-> Seq Scan on public.async_p3 t2_3
Output: t2_3.a, t2_3.b, t2_3.c
+ Bloom Filter 1: keys=(t2_3.a, t2_3.b)
-> Hash
Output: t1_3.a, t1_3.b, t1_3.c
+ Bloom Filter 1
-> Seq Scan on public.async_p3 t1_3
Output: t1_3.a, t1_3.b, t1_3.c
Filter: ((t1_3.b % 100) = 0)
-(20 rows)
+(22 rows)
INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0;
SELECT * FROM join_tbl ORDER BY a1;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a40d03d35f3..8893e36c8aa 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -25,6 +25,7 @@
#include "commands/prepare.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
+#include "lib/bloomfilter.h"
#include "libpq/pqformat.h"
#include "libpq/protocol.h"
#include "nodes/extensible.h"
@@ -92,6 +93,8 @@ static void show_scan_qual(List *qual, const char *qlabel,
static void show_upper_qual(List *qual, const char *qlabel,
PlanState *planstate, List *ancestors,
ExplainState *es);
+static void show_bloom_filter_info(PlanState *planstate, List *ancestors,
+ ExplainState *es);
static void show_sort_keys(SortState *sortstate, List *ancestors,
ExplainState *es);
static void show_incremental_sort_keys(IncrementalSortState *incrsortstate,
@@ -1978,6 +1981,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
show_indexsearches_info(planstate, es);
break;
case T_IndexOnlyScan:
@@ -1995,6 +1999,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (es->analyze)
ExplainPropertyFloat("Heap Fetches", NULL,
planstate->instrument->ntuples2, 0, es);
+ show_bloom_filter_info(planstate, ancestors, es);
show_indexsearches_info(planstate, es);
break;
case T_BitmapIndexScan:
@@ -2012,6 +2017,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
show_scan_io_usage((ScanState *) planstate, es);
break;
@@ -2030,6 +2036,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
if (IsA(plan, CteScan))
show_ctescan_info(castNode(CteScanState, planstate), es);
show_scan_io_usage((ScanState *) planstate, es);
@@ -2132,6 +2139,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
}
break;
case T_TidRangeScan:
@@ -2149,6 +2157,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_bloom_filter_info(planstate, ancestors, es);
show_scan_io_usage((ScanState *) planstate, es);
}
break;
@@ -2578,6 +2587,120 @@ show_upper_qual(List *qual, const char *qlabel,
show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
}
+/*
+ * show_bloom_filter_info
+ * Show info about every bloom filter pushed down to a scan node.
+ *
+ * In TEXT format each filter is rendered on a single line, e.g.
+ *
+ * Bloom Filter N: (a, b) producer=3 checked=99999 rejected=99990
+ *
+ * The checked/rejected fields are omitted outside of ANALYZE). In
+ * structured formats we emit a group per filter with the same fields
+ * broken out as properties.
+ *
+ * Called from the per-recipient cases in ExplainNode; the recipient is
+ * expected to be a scan node in the current PoC, but nothing here
+ * depends on that.
+ */
+static void
+show_bloom_filter_info(PlanState *planstate, List *ancestors,
+ ExplainState *es)
+{
+ Plan *plan = planstate->plan;
+ ListCell *lc1,
+ *lc2;
+ List *deparse_cxt = NIL;
+ bool useprefix = false;
+
+ if (plan->bloom_filters == NIL)
+ return;
+
+ deparse_cxt = set_deparse_context_plan(es->deparse_cxt,
+ plan, ancestors);
+ useprefix = (IsA(plan, SubqueryScan) || es->verbose);
+
+ if (es->format != EXPLAIN_FORMAT_TEXT)
+ ExplainOpenGroup("Bloom Filters", "Bloom Filters", false, es);
+
+ /* print info about all the bloom filters */
+ forboth(lc1, plan->bloom_filters,
+ lc2, planstate->bloom_filters)
+ {
+ BloomFilter *bf = lfirst_node(BloomFilter, lc1);
+ BloomFilterState *bfs = (BloomFilterState *) lfirst(lc2);
+ ListCell *lc;
+ StringInfoData keys;
+ bool first = true;
+
+ initStringInfo(&keys);
+
+ /* deparse the filter expressions */
+ appendStringInfoChar(&keys, '(');
+ foreach(lc, bf->filter_exprs)
+ {
+ char *key;
+ Node *expr = (Node *) lfirst(lc);
+
+ if (!first)
+ appendStringInfoString(&keys, ", ");
+ key = deparse_expression(expr, deparse_cxt,
+ useprefix, false);
+ appendStringInfoString(&keys, key);
+ first = false;
+ }
+ appendStringInfoChar(&keys, ')');
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+
+ /* show the filter ID only when there are more filters */
+ appendStringInfo(&buf, "Bloom Filter %d: keys=%s",
+ bf->producer_id, keys.data);
+
+ /* include the counts only during ANALYZE */
+ if (es->analyze && bfs != NULL)
+ {
+ /* rejected fraction */
+ double frac = 100.0 * bfs->rejected / Max(1, bfs->checked);
+
+ appendStringInfo(&buf,
+ " checked=" UINT64_FORMAT
+ " rejected=" UINT64_FORMAT
+ " (%.1f%%)",
+ bfs->checked, bfs->rejected, frac);
+ }
+
+ ExplainIndentText(es);
+ appendStringInfoString(es->str, buf.data);
+ appendStringInfoChar(es->str, '\n');
+ pfree(buf.data);
+ }
+ else /* non-text format */
+ {
+ ExplainOpenGroup("Bloom Filter", NULL, true, es);
+ ExplainPropertyText("Keys", keys.data, es);
+ ExplainPropertyInteger("ID", NULL, bf->producer_id, es);
+ if (es->analyze && bfs != NULL)
+ {
+ ExplainPropertyFloat("Checked", NULL,
+ (double) bfs->checked, 0, es);
+ ExplainPropertyFloat("Rejected", NULL,
+ (double) bfs->rejected, 0, es);
+ }
+ ExplainCloseGroup("Bloom Filter", NULL, true, es);
+ }
+
+ pfree(keys.data);
+ }
+
+ if (es->format != EXPLAIN_FORMAT_TEXT)
+ ExplainCloseGroup("Bloom Filters", "Bloom Filters", false, es);
+}
+
/*
* Show the sort keys for a Sort node.
*/
@@ -3474,6 +3597,72 @@ show_hash_info(HashState *hashstate, ExplainState *es)
spacePeakKb);
}
}
+
+ /*
+ * Show infromation about the bloom filter produced by this Hash node
+ * (if any). For plain EXPLAIN, the filter is not initialized / built,
+ * but we still show available plan-time metadata (at least the ID).
+ */
+ if (hashstate->bloom_filter_id > 0)
+ {
+ int producer_id = hashstate->bloom_filter_id;
+ uint64 nbits = 0;
+ int nhashfns = 0;
+ uint64 bytes = 0;
+
+ if (hashstate->bloom_filter != NULL)
+ {
+ nbits = bloom_total_bits(hashstate->bloom_filter);
+ nhashfns = bloom_hash_funcs(hashstate->bloom_filter);
+ bytes = nbits / 8;
+ }
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ ExplainIndentText(es);
+ if (hashstate->bloom_filter != NULL)
+ appendStringInfo(es->str,
+ "Bloom Filter %d: bits=" UINT64_FORMAT
+ " hashes=%d memory=" UINT64_FORMAT "kB"
+ " checked=" UINT64_FORMAT " rejected=" UINT64_FORMAT "\n",
+ producer_id,
+ nbits,
+ nhashfns,
+ BYTES_TO_KILOBYTES(bytes),
+ hashstate->bloomFilterChecked,
+ hashstate->bloomFilterRejected);
+ else if (es->analyze)
+ appendStringInfo(es->str,
+ "Bloom Filter %d: (not initialized)\n",
+ producer_id);
+ else
+ appendStringInfo(es->str,
+ "Bloom Filter %d\n",
+ producer_id);
+ }
+ else
+ {
+ /* there can be just one bloom filter per fproducer (for now) */
+ ExplainOpenGroup("Bloom Filter", "Bloom Filter", true, es);
+
+ ExplainPropertyInteger("Producer", NULL, producer_id, es);
+ ExplainPropertyBool("Initialized",
+ (hashstate->bloom_filter != NULL), es);
+
+ if (hashstate->bloom_filter != NULL)
+ {
+ ExplainPropertyUInteger("Bits", NULL, nbits, es);
+ ExplainPropertyInteger("Hash Functions", NULL, nhashfns, es);
+ ExplainPropertyUInteger("Memory Usage", "kB",
+ BYTES_TO_KILOBYTES(bytes), es);
+ ExplainPropertyFloat("Checked", NULL,
+ (double) hashstate->bloomFilterChecked, 0, es);
+ ExplainPropertyFloat("Rejected", NULL,
+ (double) hashstate->bloomFilterRejected, 0, es);
+ }
+ ExplainCloseGroup("Bloom Filter", "Bloom Filter", true, es);
+ }
+ }
}
/*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 1eb6b9f1f40..3c32a61dddd 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -159,6 +159,8 @@ CreateExecutorState(void)
estate->es_auxmodifytables = NIL;
+ estate->es_bloom_producers = NIL;
+
estate->es_per_tuple_exprcontext = NULL;
estate->es_sourceText = NULL;
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 83d6478bc2b..e71a47b6205 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -456,6 +456,9 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
scanstate->bitmapqualorig =
ExecInitQual(node->bitmapqualorig, (PlanState *) scanstate);
+ ExecInitBloomFilters((PlanState *) scanstate,
+ scanstate->ss.ss_ScanTupleSlot);
+
scanstate->ss.ss_currentRelation = currentRelation;
/*
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 8825bb6fa23..37224324bce 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -35,6 +35,7 @@
#include "executor/instrument.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
+#include "lib/bloomfilter.h"
#include "miscadmin.h"
#include "port/pg_bitutils.h"
#include "utils/lsyscache.h"
@@ -184,6 +185,18 @@ MultiExecPrivateHash(HashState *node)
uint32 hashvalue = DatumGetUInt32(hashdatum);
int bucketNumber;
+ /*
+ * Add the tuple to the pushed-down bloom filter (if any). Do
+ * it here (rather than in ExecHashTableInsert) so that each
+ * tuple is added exactly once, even if it later gets shuffled
+ * between batches by ExecHashIncreaseNumBatches. The filter
+ * would still produce the same matches, but it costs CPU.
+ */
+ if (node->bloom_filter != NULL)
+ bloom_add_element(node->bloom_filter,
+ (unsigned char *) &hashvalue,
+ sizeof(hashvalue));
+
bucketNumber = ExecHashGetSkewBucket(hashtable, hashvalue);
if (bucketNumber != INVALID_SKEW_BUCKET_NO)
{
@@ -665,6 +678,53 @@ ExecHashTableCreate(HashState *state)
MemoryContextSwitchTo(oldcxt);
}
+ /*
+ * If we managed to push down a bloom filter to the outer side of the
+ * hash join, allocate it with the hash table.
+ *
+ * Whether we build the filter is decided by try_push_bloom_filter at
+ * plan time. If there's no recipient node, or when the GUC is set to
+ * off, state->want_bloom_filter is false.
+ *
+ * XXX We don't do this for parallel hash joins, to keep the PoC simple.
+ * The filter would need to live in shared memory, and the workers would
+ * need to coordinate to build it. But it's doable.
+ *
+ * The filter lives in the HashState, in the hashCtx memory context.
+ * That means it gets destroyed along with the hashtable, and it follows
+ * the same lifecycle (during rescans, etc.).
+ *
+ * The size of the filter is bounded by both the estimated inner row
+ * count and a fixed fraction of work_mem. bloom_create() will round
+ * down to the next power-of-two bitset and enforces a 1MB minimum.
+ *
+ * XXX This may need more thought. If we limit bloom_work_mem too much,
+ * the false positive rate will get too bad, and we won't filter enough
+ * tuples for the filter to pay for itself. The adaptive behavior will
+ * eventually skip the filter, but we could just not build it at all?
+ * Or do we want to take the chance, sometimes?
+ */
+ if (state->want_bloom_filter)
+ {
+ MemoryContext oldctx;
+ int bloom_work_mem;
+
+ /* only serial hashjoins for now, init only once */
+ Assert(hashtable->parallel_state == NULL);
+ Assert(state->bloom_filter == NULL);
+
+ state->bloomFilterChecked = 0;
+ state->bloomFilterRejected = 0;
+
+ /* Cap bloom filter at ~1/8 of work_mem, but not less than 1MB. */
+ bloom_work_mem = Max(1024, work_mem / 8);
+
+ oldctx = MemoryContextSwitchTo(hashtable->hashCxt);
+ state->bloom_filter = bloom_create((int64) Max(rows, 1.0),
+ bloom_work_mem, 0);
+ MemoryContextSwitchTo(oldctx);
+ }
+
return hashtable;
}
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 0b365d5b475..8fa7af4cfef 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -169,7 +169,9 @@
#include "executor/instrument.h"
#include "executor/nodeHash.h"
#include "executor/nodeHashjoin.h"
+#include "lib/bloomfilter.h"
#include "miscadmin.h"
+#include "port/pg_bitutils.h"
#include "utils/lsyscache.h"
#include "utils/sharedtuplestore.h"
#include "utils/tuplestore.h"
@@ -834,6 +836,7 @@ HashJoinState *
ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
{
HashJoinState *hjstate;
+ HashState *hashState;
Plan *outerNode;
Hash *hashNode;
TupleDesc outerDesc,
@@ -875,11 +878,37 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
outerNode = outerPlan(node);
hashNode = (Hash *) innerPlan(node);
+ /*
+ * Register ourselves as a bloom-filter producer in the EState before
+ * recursing into the outer subtree, so the scan node (we pushed the
+ * filter to) can find us. We do this only if we actually managed to
+ * push down the filter to a scan node.
+ */
+ if (node->bloom_consumer_count > 0)
+ ExecRegisterBloomFilterProducer(hjstate);
+
outerPlanState(hjstate) = ExecInitNode(outerNode, estate, eflags);
outerDesc = ExecGetResultType(outerPlanState(hjstate));
innerPlanState(hjstate) = ExecInitNode((Plan *) hashNode, estate, eflags);
innerDesc = ExecGetResultType(innerPlanState(hjstate));
+ /*
+ * Tell the Hash child to actually build the bloom filter, and the
+ * ID assigned to the filter.
+ *
+ * XXX Seems a bit ugly to manipulate the inner plan state like this.
+ * Surely there's a better way. OTOH the two nodes are pretty tightly
+ * coupled already, so maybe it's fine.
+ *
+ * XXX Also, this assumes the hash table is not built by ExecInitNode(),
+ * which is true for now. But maybe we will relax that in the future
+ * (e.g. so that the scan can push the filter to storage / to remote FDW
+ * node / ...)?
+ */
+ hashState = castNode(HashState, innerPlanState(hjstate));
+ hashState->want_bloom_filter = (node->bloom_consumer_count > 0);
+ hashState->bloom_filter_id = node->bloom_filter_id;
+
/*
* Initialize result slot, type and projection.
*/
@@ -1080,11 +1109,15 @@ ExecEndHashJoin(HashJoinState *node)
/*
* Free hash table
+ *
+ * Clear the bloom_filter pointer. It lives in hashCxt, so it gets freed by
+ * the ExecHashTableDestroy call.
*/
if (node->hj_HashTable)
{
ExecHashTableDestroy(node->hj_HashTable);
node->hj_HashTable = NULL;
+ hashNode->bloom_filter = NULL;
}
/*
@@ -1737,6 +1770,12 @@ ExecReScanHashJoin(HashJoinState *node)
node->hj_HashTable = NULL;
node->hj_JoinState = HJ_BUILD_HASHTABLE;
+ /*
+ * Clear the bloom_filter pointer. It lives in hashCxt, so it gets
+ * freed by the ExecHashTableDestroy call.
+ */
+ hashNode->bloom_filter = NULL;
+
/*
* if chgParam of subnode is not null then plan will be re-scanned
* by first ExecProcNode.
@@ -1975,3 +2014,419 @@ ExecHashJoinInitializeWorker(HashJoinState *state,
ExecSetExecProcNode(&state->js.ps, ExecParallelHashJoin);
}
+
+/*
+ * BLOOM FILTER PUSHDOWN
+ *
+ * The pushdown decision is done in try_push_bloom_filter, when constructing
+ * the plan from the selected paths (see createplan.c). It decides which scan
+ * node should receive the bloom filter (if any), and what expressions it
+ * should use to calculate the hash value.
+ *
+ * Then at execution time:
+ *
+ * - ExecInitHashJoin registers itself in EState.es_bloom_producers
+ * before recursing into child plans, so by the time a recipient's
+ * ExecInit runs, the producer is already discoverable by plan_node_id.
+ * This registration only happens when there's at least one consumer.
+ * It also sets want_bloom_filter for the Hash node.
+ *
+ * - ExecHashTableCreate (in nodeHash.c) builds the actual bloom_filter
+ * when HashState.want_bloom_filter is set (so no work happens when
+ * nobody will probe).
+ *
+ * - Nodes with non-NIL plan->bloom_filters (and supporting bloom
+ * filters) call ExecInitBloomFilters() during its own ExecInit,
+ * which looks up the producer node (in the EState), compiles
+ * ExprStates for the hash expressions, etc. The filter state
+ * (BloomFilterState) gets added to ps->bloom_filters (a node may
+ * have multiple bloom filters from different hash joins).
+ *
+ * - The per-tuple loop of the scan node calls ExecBloomFilters() (much
+ * like ExecQual) to test the tuple against every attached filter,
+ * dropping it on the first filter that excludes it. For scan nodes
+ * this call happens in ExecScanExtended.
+ *
+ * The scan nodes reach the bloom filter via the HashJoinState pointer
+ * added to EState.es_bloom_producers, so that the rescans etc. (filter
+ * freed + recreated when the hash table is destroyed and rebuilt) are
+ * transparent to the consumer. The bloom filter gets reallocated after
+ * a rescan, so the pointer to it may change.
+ *
+ * XXX It's possible the bloom filter gets pushed down to a node that
+ * fails to initialize/use it. It'll be added to the bloom_filters list,
+ * but if the node does not call ExecInitBloomFilters, the filter will
+ * be unused.
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * Lookup the HashJoinState for a producer by plan_node_id in the
+ * EState's es_bloom_producers list. Returns NULL if no matching
+ * producer has registered yet (which can happen for filters attached to
+ * recipients in trees where the producer hasn't ExecInit'd yet -- in
+ * normal execution we always register first).
+ */
+static HashJoinState *
+LookupBloomFilterProducer(EState *estate, int bloom_filter_id)
+{
+ ListCell *lc;
+
+ foreach(lc, estate->es_bloom_producers)
+ {
+ HashJoinState *hjstate = (HashJoinState *) lfirst(lc);
+ HashJoin *plan = (HashJoin *) hjstate->js.ps.plan;
+
+ if (plan->bloom_filter_id == bloom_filter_id)
+ return hjstate;
+ }
+ return NULL;
+}
+
+/*
+ * ExecBloomFilterHash
+ * Calculate the hash value for a tuple in the recipient node.
+ *
+ * Uses the per-key ExprStates initialized by ExecInitBloomFilters. Mirrors the
+ * scheme used by the Hash node, so that it matches what was inserted into the
+ * hashtable (and filter).
+ *
+ * Returns false if a strict key is NULL: such a tuple can't match anything in
+ * the bloom filter, but we still must let it pass through to the upstream join
+ * so the join (rather than us) decides what to do with it (e.g. emit
+ * NULL-extended for an outer join).
+ *
+ * XXX I'm not sure about this strict/NULL business.
+ */
+static inline bool
+ExecBloomFilterHash(BloomFilterState *bfs, ExprContext *econtext,
+ uint32 *hashvalue)
+{
+ bool isnull;
+ uint32 hash;
+
+ hash = DatumGetUInt32(ExecEvalExpr(bfs->keys, econtext, &isnull));
+
+ if (isnull)
+ return 0; /* XXX correct? do we care about NULL values?*/
+
+ *hashvalue = hash;
+ return true;
+}
+
+/*
+ * ADAPTIVE BEHAVIOR
+ *
+ * If the bloom filter lets through most (or all) tuples, it becomes somewhat
+ * useless - we're just wasting CPU cycles, getting nothing in return. We could
+ * simply stop using such filter. But we've already paid quite a bit to build
+ * it, and maybe the data set is not uniform and we'll get into a part where
+ * fewer tuples pass.
+ *
+ * So we're evaluating the match rate for windows of 1000 probes. If more than
+ * 90% match, we start sampling 1% of the probes (i.e. 99% it treated as a
+ * match without looking at the filter). And if the match rate drops below 80%,
+ * we stop the sampling and all probes go to the filter.
+ *
+ * XXX These are empirical values, picked based on experiments. "Perfect"
+ * values depend on hardware, number of keys, data types, ... and maybe even
+ * on how many hash joins / pushed-down filters there are, and how deep (the
+ * deeper the bigger the benefit).
+ *
+ * XXX Maybe we should sample more probes, or maybe the window should be a bit
+ * smaller? With 1% and 1000 probes per window, it'll take 100k probes to
+ * enable the filter again. That seems like a lot.
+ *
+ * XXX We should probably track the number of times we "disabled" the filter,
+ * and what fraction of entries were "let through" during sampling periods.
+ *
+ * XXX There's an intentional gap between low/high thresholds, to add a bit
+ * of hysteresis into the behavior, so it does not flap all the time.
+ */
+#define BLOOM_ADAPTIVE_WINDOW_SIZE 1000
+#define BLOOM_ADAPTIVE_HIGH_MATCH_PERCENT 90
+#define BLOOM_ADAPTIVE_LOW_MATCH_PERCENT 80
+#define BLOOM_ADAPTIVE_SAMPLE_RATE 100
+
+/*
+ * ExecBloomFilterShouldProbe
+ * Decide if the next tuple should probe the bloom filter.
+ *
+ * Returns true if the next value should actually probe the bloom filter
+ * We sample 1/100 (1/BLOOM_ADAPTIVE_SAMPLE_RATE) probes, i.e. 1%.
+ */
+static inline bool
+ExecBloomFilterShouldProbe(BloomFilterState *bfs)
+{
+ if (!bfs->adaptiveSampling)
+ return true;
+
+ bfs->adaptiveSampleCounter++;
+
+ if (bfs->adaptiveSampleCounter >= BLOOM_ADAPTIVE_SAMPLE_RATE)
+ {
+ bfs->adaptiveSampleCounter = 0;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * ExecBloomFilterUpdateAdaptiveState
+ * Update the adaptive state for sampling the probes.
+ *
+ * Adjust the adaptive behavior every 1000 probes. If too many probes match,
+ * stop using the filter (and just sample 1% of probes instead). If we were
+ * sampling, and the fraction of matches drops enough, stop the sampling.
+ */
+static inline void
+ExecBloomFilterUpdateAdaptiveState(BloomFilterState *bfs, bool match)
+{
+ bfs->adaptiveWindowProbes++;
+ if (match)
+ bfs->adaptiveWindowMatches++;
+
+ /* have we done enough probes in this window? */
+ if (bfs->adaptiveWindowProbes >= BLOOM_ADAPTIVE_WINDOW_SIZE)
+ {
+ uint64 match_percent;
+
+ /* fraction of matches */
+ match_percent = (bfs->adaptiveWindowMatches * 100) /
+ bfs->adaptiveWindowProbes;
+
+ if (!bfs->adaptiveSampling &&
+ match_percent > BLOOM_ADAPTIVE_HIGH_MATCH_PERCENT)
+ {
+ /* Too many matches - start sampling. */
+ bfs->adaptiveSampling = true;
+ bfs->adaptiveSampleCounter = 0;
+ }
+ else if (bfs->adaptiveSampling &&
+ match_percent < BLOOM_ADAPTIVE_LOW_MATCH_PERCENT)
+ {
+ /* Stop sampling if the match fraction got low enough. */
+ bfs->adaptiveSampling = false;
+ bfs->adaptiveSampleCounter = 0;
+ }
+
+ /* in any case, start a new window of probes */
+ bfs->adaptiveWindowProbes = 0;
+ bfs->adaptiveWindowMatches = 0;
+ }
+}
+
+/*
+ * ExecBloomFilters
+ * Probe bloom filters for the current slot.
+ *
+ * Test the slot in the expression context (set by the scan node) against
+ * every bloom filter attached to the node. Returns true if the tuple matches
+ * all filters (some of which may have been skipped, because the hash table
+ * isn't built yet); false if at least one filter conclusively excludes it.
+ *
+ * 'filters' is a list of BloomFilterState for each filter, pushed to the
+ * node (stored in planstate->bloom_filters). It may be NIL, which means
+ * are no filters, and the function simply returns NULL.
+ *
+ * The caller is responsible for having set econtext->ecxt_scantuple to
+ * 'slot' first. We do not reset the per-tuple context here (it's up to the
+ * scan node).
+ *
+ * Designed to be called like ExecQual from the recipient's per-tuple
+ * loop. See ExecScanExtended for the scan-node integration point.
+ *
+ * XXX We're pushing filters to scan nodes, which set the scan slot. And
+ * setrefs.c is currently wired to do fix_scan_bloom_filters, called from
+ * set_plan_refs. If we decide to push filters to other nodes (e.g. joins),
+ * this may need some rework.
+ */
+bool
+ExecBloomFilters(List *filters, ExprContext *econtext)
+{
+ ListCell *lc;
+
+ /* bail out if no filters */
+ if (filters == NIL)
+ return true;
+
+ foreach(lc, filters)
+ {
+ BloomFilterState *bfs = (BloomFilterState *) lfirst(lc);
+ HashJoinState *producer = bfs->producer;
+ HashState *hashNode;
+ bloom_filter *bf;
+ uint32 hashvalue;
+
+ /* Producer should always exist (resolved at init time). */
+ Assert(producer != NULL);
+
+ /*
+ * The hashtable (and the bloom filter) is built lazily the first
+ * time it needs to do a lookup. Until then, assume everything
+ * matches everything through. Once the filter is in place, start
+ * probing it.
+ *
+ * XXX It should only take a couple tuples (maybe just a single one)
+ * from the scan node before the filter is available.
+ */
+ hashNode = castNode(HashState, innerPlanState(&producer->js.ps));
+ bf = hashNode->bloom_filter;
+ if (bf == NULL)
+ continue;
+
+ /*
+ * When recent bloom probes mostly pass through, probe only a sample of
+ * values to avoid spending work on an ineffective filter. Sampled
+ * probes keep updating the recent match fraction, so filtering resumes
+ * for every value once the filter becomes selective again.
+ */
+ if (!ExecBloomFilterShouldProbe(bfs))
+ continue;
+
+ /* NULL strict key: tuple cannot be in the filter, pass through. */
+ if (!ExecBloomFilterHash(bfs, econtext, &hashvalue))
+ continue;
+
+ /*
+ * XXX It's a bit silly the counters are in two places. We should
+ * keep just the hashNode counters, and get rid of bfs counters.
+ */
+ bfs->checked++;
+ hashNode->bloomFilterChecked++;
+
+ /* If not matching, we're done - reject the tuple. */
+ if (bloom_lacks_element(bf,
+ (unsigned char *) &hashvalue,
+ sizeof(hashvalue)))
+ {
+ bfs->rejected++;
+ hashNode->bloomFilterRejected++;
+ ExecBloomFilterUpdateAdaptiveState(bfs, false);
+ return false;
+ }
+
+ ExecBloomFilterUpdateAdaptiveState(bfs, true);
+ }
+
+ return true;
+}
+
+/*
+ * ExecInitBloomFilters
+ * Initialize state for pushed-down bloom filters.
+ *
+ * Called by nodes that want to act as a recipient of pushed-down filters,
+ * after the node's projection / scan-tuple slot are set up, just like
+ * for regular quals.
+ *
+ * Walks the plan's bloom_filters list and produces a list of BloomFilterState
+ * nodes, stored in planstate->bloom_filters. The producer HashJoinState node
+ * is resolved here, once, via EState.es_bloom_producers; so that no lookup is
+ * needed at probe time (the bloom_filter pointer may change on rescan, but
+ * that's not what we store).
+ *
+ * 'output_slot' is the slot whose values the filter expressions will be
+ * evaluated against (i.e. the same slot the surrounding qual evaluates
+ * against, post-setrefs).
+ *
+ * XXX For now this has to be the scan slot. See the comment about setrefs
+ * a bit earlier. Could be relaxed later, if we support to pushdown to
+ * other node types.
+ *
+ * XXX The filter states are initialized in es_query_cxt, but maybe that's
+ * too long-lived. The states live only as long as the recipient node.
+ */
+void
+ExecInitBloomFilters(PlanState *planstate, TupleTableSlot *output_slot)
+{
+ Plan *plan = planstate->plan;
+ EState *estate = planstate->state;
+ List *result = NIL;
+ ListCell *lc;
+ MemoryContext oldctx;
+
+ /* bail out if there are no pushed-down filters */
+ if (plan->bloom_filters == NIL)
+ return;
+
+ oldctx = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ foreach(lc, plan->bloom_filters)
+ {
+ BloomFilter *bf = lfirst_node(BloomFilter, lc);
+ BloomFilterState *bfs;
+ int nkeys;
+
+ nkeys = list_length(bf->filter_exprs);
+ Assert(nkeys > 0);
+ Assert(nkeys == list_length(bf->hashops));
+ Assert(nkeys == list_length(bf->hashcollations));
+
+ bfs = makeNode(BloomFilterState);
+
+ /* XXX some of this is redundant */
+ bfs->filter = bf;
+ bfs->producer_id = bf->producer_id;
+ bfs->producer = LookupBloomFilterProducer(estate, bf->producer_id);
+
+ /* initialize the expression state for the hashvalue calculation */
+ {
+ Oid *outer_hashfuncid = palloc_array(Oid, nkeys);
+ Oid *inner_hashfuncid = palloc_array(Oid, nkeys);
+ bool *hash_strict = palloc_array(bool, nkeys);
+ ListCell *lc2;
+
+ /*
+ * Determine the hash function for each side of the join for the given
+ * join operator, and detect whether the join operator is strict.
+ */
+ foreach(lc2, bf->hashops)
+ {
+ Oid hashop = lfirst_oid(lc2);
+ int i = foreach_current_index(lc2);
+
+ if (!get_op_hash_functions(hashop,
+ &outer_hashfuncid[i],
+ &inner_hashfuncid[i]))
+ elog(ERROR,
+ "could not find hash function for hash operator %u",
+ hashop);
+ hash_strict[i] = op_strict(hashop);
+ }
+
+ /* state for the hash value calculation */
+ bfs->keys = ExecBuildHash32Expr(output_slot->tts_tupleDescriptor,
+ output_slot->tts_ops,
+ outer_hashfuncid,
+ bf->hashcollations,
+ bf->filter_exprs,
+ hash_strict,
+ planstate,
+ 0);
+ }
+
+ result = lappend(result, bfs);
+ }
+
+ planstate->bloom_filters = result;
+
+ MemoryContextSwitchTo(oldctx);
+}
+
+/*
+ * ExecRegisterBloomFilterProducer
+ * Register the pushed downn bloom filter.
+ *
+ * Called by ExecInitHashJoin (before recursing into the outer subtree)
+ * to register this HashJoinState as a producer for the pushed-down filter.
+ * Recipients in the outer subtree will look us up here by plan_node_id.
+ */
+void
+ExecRegisterBloomFilterProducer(HashJoinState *hjstate)
+{
+ EState *estate = hjstate->js.ps.state;
+
+ estate->es_bloom_producers = lappend(estate->es_bloom_producers, hjstate);
+}
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index d52012e8a69..bd7e5966017 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -600,6 +600,9 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
indexstate->recheckqual =
ExecInitQual(node->recheckqual, (PlanState *) indexstate);
+ ExecInitBloomFilters((PlanState *) indexstate,
+ indexstate->ss.ss_ScanTupleSlot);
+
/*
* If we are just doing EXPLAIN (ie, aren't going to run the plan), stop
* here. This allows an index-advisor plugin to EXPLAIN a plan containing
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 39f6691ee35..ef56522fbc5 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -970,6 +970,9 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
indexstate->indexorderbyorig =
ExecInitExprList(node->indexorderbyorig, (PlanState *) indexstate);
+ ExecInitBloomFilters((PlanState *) indexstate,
+ indexstate->ss.ss_ScanTupleSlot);
+
/*
* If we are just doing EXPLAIN (ie, aren't going to run the plan), stop
* here. This allows an index-advisor plugin to EXPLAIN a plan containing
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index f3d273e1c5e..738502433b4 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -147,6 +147,9 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags)
scanstate->repeatable =
ExecInitExpr(tsc->repeatable, (PlanState *) scanstate);
+ ExecInitBloomFilters((PlanState *) scanstate,
+ scanstate->ss.ss_ScanTupleSlot);
+
/*
* If we don't have a REPEATABLE clause, select a random seed. We want to
* do this just once, since the seed shouldn't change over rescans.
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 5bcb0a861d7..4d3d7ba10a9 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -268,6 +268,9 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
scanstate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+ ExecInitBloomFilters((PlanState *) scanstate,
+ scanstate->ss.ss_ScanTupleSlot);
+
/*
* When EvalPlanQual() is not in use, assign ExecProcNode for this node
* based on the presence of qual and projection. Each ExecSeqScan*()
diff --git a/src/backend/executor/nodeTidrangescan.c b/src/backend/executor/nodeTidrangescan.c
index b387ed6c308..b00a3736b99 100644
--- a/src/backend/executor/nodeTidrangescan.c
+++ b/src/backend/executor/nodeTidrangescan.c
@@ -434,6 +434,9 @@ ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags)
tidrangestate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, (PlanState *) tidrangestate);
+ ExecInitBloomFilters((PlanState *) tidrangestate,
+ tidrangestate->ss.ss_ScanTupleSlot);
+
TidExprListCreate(tidrangestate);
/*
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 6641df10999..f7ba78a63fc 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -551,6 +551,9 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
tidstate->ss.ps.qual =
ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate);
+ ExecInitBloomFilters((PlanState *) tidstate,
+ tidstate->ss.ss_ScanTupleSlot);
+
TidExprListCreate(tidstate);
/*
diff --git a/src/backend/lib/bloomfilter.c b/src/backend/lib/bloomfilter.c
index 73b3768a172..fbcf788e271 100644
--- a/src/backend/lib/bloomfilter.c
+++ b/src/backend/lib/bloomfilter.c
@@ -192,6 +192,25 @@ bloom_prop_bits_set(bloom_filter *filter)
return bits_set / (double) filter->m;
}
+/*
+ * Total bitset size, in bits. Useful for EXPLAIN instrumentation:
+ * divide by 8 to get the bitset's memory footprint in bytes.
+ */
+uint64
+bloom_total_bits(bloom_filter *filter)
+{
+ return filter->m;
+}
+
+/*
+ * Number of hash functions in use.
+ */
+int
+bloom_hash_funcs(bloom_filter *filter)
+{
+ return filter->k_hash_funcs;
+}
+
/*
* Which element in the sequence of powers of two is less than or equal to
* target_bitset_bits?
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1c575e56ff6..c3072a29ccc 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -156,6 +156,7 @@ bool enable_material = true;
bool enable_memoize = true;
bool enable_mergejoin = true;
bool enable_hashjoin = true;
+bool enable_hashjoin_bloom = true;
bool enable_gathermerge = true;
bool enable_partitionwise_join = false;
bool enable_partitionwise_aggregate = false;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index de6a183da79..7ecb551aae6 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4689,6 +4689,298 @@ create_mergejoin_plan(PlannerInfo *root,
return join_plan;
}
+/*
+ * BLOOM FILTER PUSHDOWN
+ *
+ * When creating a hash join plan, consider building a bloom filter and
+ * pushing it down to the outer subtree. For now we only push filters to
+ * scan nodes containing all the join keys. When we find such scan node,
+ * we append the BloomFilter ID to the node's bloom_filters list, and
+ * increment the bloom_consumer_count for the hashjoin.
+ *
+ * As setrefs hashn't run yet, the join keys are still the raw Vars.
+ * So it's safe to compare var->varno against the scanrelid, and copy
+ * the keys verbatim onto the recipient. setrefs will rewrite the Vars
+ * later as usual, just like for the recipient's qual.
+ *
+ * XXX In most cases there'll be only a single consumer node. To get
+ * multiple consumers, we'd need either joins on the same keys, or
+ * ability to produce filters for subsets of the join keys (for cases
+ * where the join is more complex, and does not map to a single scan
+ * node directly). Seems like a possible future improvement.
+ *
+ * XXX Actually, we could have multiple consumer nodes for partitioned
+ * tables, where each partition gets a separate scan. Which seems like
+ * something we should support.
+ *
+ * XXX For simplicity, all outer join keys have to be bare Vars (from
+ * the same RTE). We could relax this later, and allow joins on more
+ * complex expressions. Not sure if that'll erase some of the benefits,
+ * which relies on filter probes being much cheaper hashtable probes.
+ * It also doesn't seem like a very common case.
+ *
+ * XXX The recipient node must be one of a small set of scan nodes. We
+ * could relax this, and allow pushing to other nodes (e.g. joins or
+ * aggregates). Future improvement.
+ *
+ * XXX We don't currently push the same HashJoin to multiple recipients,
+ * but multiple HashJoins may attach a filter to the same scan node.
+ * --------------------------------------------------------------------------
+ */
+
+/*
+ * bloom_join_side_preserved
+ * Decide if we can push filter to inner/outer side of a join.
+ *
+ * Outer joins that emit unmatched outer tuples (LEFT/ANTI/FULL) are
+ * skipped: dropping outer tuples there would be incorrect.
+ */
+static bool
+bloom_join_side_preserved(JoinType jointype, bool to_outer)
+{
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ return true;
+ case JOIN_LEFT:
+ case JOIN_SEMI:
+ case JOIN_ANTI:
+ return to_outer;
+ case JOIN_RIGHT:
+ case JOIN_RIGHT_SEMI:
+ case JOIN_RIGHT_ANTI:
+ return !to_outer;
+ case JOIN_FULL:
+ return false;
+ default:
+ return false;
+ }
+}
+
+/*
+ * find_bloom_filter_recipient
+ * Try to find a scan node to push filter to.
+ *
+ * We support pushing filter to a subset of scan nodes (could be extended
+ * later). We support pushing filters through intermediate nodes (joins,
+ * sorts, ...). See bloom_join_side_preserved for joins.
+ *
+ * XXX We could push filters through more nodes - e.g. aggregates if the
+ * hash keys match GROUP BY keys (are a subset of).
+ *
+ * XXX We could do pushdown to parallel parts of a query. But we'd need
+ * a different way to communicate if a filter is built etc. (the worker
+ * won't have access to the hashjoin state).
+ */
+static Plan *
+find_bloom_filter_recipient(Plan *plan, Index target_relid)
+{
+ /* XXX shouldn't really happen, I think */
+ if (plan == NULL)
+ return NULL;
+
+ /* XXX no pushdown to parallel parts of a query */
+ if (plan->parallel_aware)
+ return NULL;
+
+ switch (nodeTag(plan))
+ {
+ case T_SeqScan:
+ case T_SampleScan:
+ case T_IndexScan:
+ case T_IndexOnlyScan:
+ case T_BitmapHeapScan:
+ case T_TidScan:
+ case T_TidRangeScan:
+ {
+ Scan *scan = (Scan *) plan;
+
+ if (scan->scanrelid == target_relid)
+ return plan;
+ return NULL;
+ }
+ case T_Sort:
+ case T_IncrementalSort:
+ case T_Material:
+ case T_Memoize:
+ case T_Unique:
+ case T_Limit:
+ return find_bloom_filter_recipient(outerPlan(plan), target_relid);
+ case T_HashJoin:
+ case T_NestLoop:
+ case T_MergeJoin:
+ {
+ JoinType jt = ((Join *) plan)->jointype;
+ Plan *res;
+
+ /*
+ * We can only push to one node, but we don't know on which
+ * side of the join it is (e.g. for inner joins it can be on
+ * both sides). So make sure to check both, if compatible
+ * with the join type (per bloom_join_side_preserved).
+ */
+ if (bloom_join_side_preserved(jt, true))
+ {
+ res = find_bloom_filter_recipient(outerPlan(plan),
+ target_relid);
+ if (res != NULL)
+ return res;
+ }
+ if (bloom_join_side_preserved(jt, false))
+ {
+ res = find_bloom_filter_recipient(innerPlan(plan),
+ target_relid);
+ if (res != NULL)
+ return res;
+ }
+ return NULL;
+ }
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * try_push_bloom_filter
+ * Attempt to pushdown a bloom filter for the current hashjoin.
+ *
+ * The filter pushdown happens during plan creation, i.e. after the plan was
+ * already selected. That is not entirely optimal, and it has a couple of
+ * annoying consequences.
+ *
+ * The main disadvantage is that injecting the filter to a scan node may
+ * significantly alter the number of tuples produced by that scan node. If a
+ * filter eliminates 99% of the rows, the scan produces 1/100 of the rows it
+ * was planned with. It would not affect the scan itself, but if there are
+ * other nodes (between the scan and the join), maybe we'd have planned them
+ * differently if we knew about the lower cardinality?
+ *
+ * Similarly, it's confusing in the explain. That is, we'll get "rows=N"
+ * with the planner cardinality (before the filter was pushed down), but
+ * then in EXPLAIN ANALYZE it'll get much lower values. It'd be easy to
+ * confuse with inaccurate estimates.
+ *
+ * It'd be better to know about the filter earlier, when constructing the scan
+ * path. But that's not quite feasible with our bottom-up planner. When planing
+ * the scan, we don't know which of the joins above it will be hashjoins, or
+ * if it can pushdown the filter. We'd have to speculate, or maybe build more
+ * paths with/without expectation of the bloom filter pushdown. But that seems
+ * not great, as it'd add overhead for everyone.
+ */
+static void
+try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan)
+{
+ List *hashkeys = hj->hashkeys;
+ List *hashops = hj->hashoperators;
+ List *hashcolls = hj->hashcollations;
+ ListCell *lc;
+ Index target_relid = 0;
+ Plan *recipient;
+ BloomFilter *bf;
+
+ /* bail out if feature disabled. */
+ if (!enable_hashjoin_bloom)
+ return;
+
+ /* XXX shouldn't really happen, I think */
+ if (hashkeys == NIL)
+ return;
+
+ Assert(list_length(hashkeys) == list_length(hashops));
+ Assert(list_length(hashkeys) == list_length(hashcolls));
+
+ /*
+ * Pushdown is unsafe for join types that emit unmatched outer tuples
+ * (LEFT/ANTI/FULL): we'd risk dropping outer tuples the join would
+ * otherwise have emitted (possibly NULL-extended).
+ */
+ switch (hj->join.jointype)
+ {
+ case JOIN_INNER:
+ case JOIN_RIGHT:
+ case JOIN_SEMI:
+ case JOIN_RIGHT_SEMI:
+ case JOIN_RIGHT_ANTI:
+ /* these join types are OK */
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * All hashkeys must be bare Vars referencing the same base RTE.
+ *
+ * XXX We could be a bit less strict, and check that at least some of the
+ * hashkeys are bare Vars, not all of them. So with joins on multiple
+ * expressions we'd have better chance to push a filter down. Doesn't
+ * seem worth it, at least for now.
+ */
+ foreach(lc, hashkeys)
+ {
+ Node *k = (Node *) lfirst(lc);
+ Var *var;
+
+ /* not a plain Var */
+ if (!IsA(k, Var))
+ return;
+
+ var = (Var *) k;
+
+ /*
+ * Reject outer references, whole-row or system columns, and
+ * special varnos (not sure we can get them here, though).
+ */
+ if ((var->varlevelsup != 0) ||
+ (var->varattno <= 0) ||
+ IS_SPECIAL_VARNO(var->varno))
+ return;
+
+ /* make sure all the vars are for the same relid */
+ if (target_relid == 0)
+ target_relid = var->varno;
+ else if (var->varno != target_relid)
+ return;
+ }
+
+ /* should have found at least one var */
+ Assert(target_relid != 0);
+
+ /*
+ * See if we can find the scan node for target_relid. It certainly is
+ * in the plan somewhere, but it may not be able to pushdown the filter
+ * to it (because of a join or so).
+ */
+ recipient = find_bloom_filter_recipient(outer_plan, target_relid);
+ if (recipient == NULL)
+ return;
+
+ /*
+ * If we found a recipient, assign the filter an ID. We'll use it to
+ * register the filter in ExecRegisterBloomFilterProducer, and then
+ * for lookups in LookupBloomFilterProducer during execution.
+ *
+ * XXX We can't use plan_node_id, as it's not assigned yet, that only
+ * happens in set_plan_refs. Also, if we ever allow multiple filters
+ * per hashtable (e.g. for different subsets of keys), it's not work.
+ */
+ hj->bloom_filter_id = ++root->glob->lastBloomFilterId;
+
+ bf = makeNode(BloomFilter);
+ bf->filter_exprs = (List *) copyObject(hashkeys);
+ bf->hashops = list_copy(hashops);
+ bf->hashcollations = list_copy(hashcolls);
+ bf->producer_id = hj->bloom_filter_id;
+
+ recipient->bloom_filters = lappend(recipient->bloom_filters, bf);
+
+ /*
+ * XXX We've manged to push the filter to the scan node, but maybe
+ * we should wait with updating bloom_consumer_count when it actually
+ * initializes the filters in ExecInit()?
+ */
+ hj->bloom_consumer_count++;
+}
+
static HashJoin *
create_hashjoin_plan(PlannerInfo *root,
HashPath *best_path)
@@ -4859,6 +5151,13 @@ create_hashjoin_plan(PlannerInfo *root,
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
+ /*
+ * Try to push the bloom filter for the hashtable down to nodes in the outer
+ * subtree. If a suitable scan node exists, add the filter to bloom_filters,
+ * and bump our bloom_consumer_count.
+ */
+ try_push_bloom_filter(root, join_plan, outer_plan);
+
return join_plan;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 846bd7c1fbe..e7ca3472d7c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -389,6 +389,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
glob->lastPHId = 0;
glob->lastRowMarkId = 0;
glob->lastPlanNodeId = 0;
+ glob->lastBloomFilterId = 0;
glob->transientPlan = false;
glob->dependsOnRole = false;
glob->partition_directory = NULL;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index ff0e875f2a2..0059acfccbe 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -161,6 +161,7 @@ static Node *fix_scan_expr(PlannerInfo *root, Node *node,
int rtoffset, double num_exec);
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
+static void fix_scan_bloom_filters(PlannerInfo *root, Plan *plan, int rtoffset);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
static void set_param_references(PlannerInfo *root, Plan *plan);
@@ -665,6 +666,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->scan.plan.qual =
fix_scan_list(root, splan->scan.plan.qual,
rtoffset, NUM_EXEC_QUAL(plan));
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_SampleScan:
@@ -681,6 +685,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->tablesample = (TableSampleClause *)
fix_scan_expr(root, (Node *) splan->tablesample,
rtoffset, 1);
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_IndexScan:
@@ -706,6 +713,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->indexorderbyorig =
fix_scan_list(root, splan->indexorderbyorig,
rtoffset, NUM_EXEC_QUAL(plan));
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_IndexOnlyScan:
@@ -744,6 +754,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->bitmapqualorig =
fix_scan_list(root, splan->bitmapqualorig,
rtoffset, NUM_EXEC_QUAL(plan));
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_TidScan:
@@ -760,6 +773,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->tidquals =
fix_scan_list(root, splan->tidquals,
rtoffset, 1);
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_TidRangeScan:
@@ -776,6 +792,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->tidrangequals =
fix_scan_list(root, splan->tidrangequals,
rtoffset, 1);
+
+ /* pushed-down bloom filters */
+ fix_scan_bloom_filters(root, plan, rtoffset);
}
break;
case T_SubqueryScan:
@@ -1426,6 +1445,30 @@ set_indexonlyscan_references(PlannerInfo *root,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) plan));
+ /*
+ * Bloom filter pushdown: any BloomFilter recipient lists also need
+ * their key expressions rewritten to reference the index tuple, since
+ * that's what the recipient scan returns and what ExecBloomFilters
+ * will evaluate against.
+ */
+ if (plan->scan.plan.bloom_filters != NIL)
+ {
+ ListCell *lc2;
+
+ foreach(lc2, plan->scan.plan.bloom_filters)
+ {
+ BloomFilter *bf = lfirst_node(BloomFilter, lc2);
+
+ bf->filter_exprs = (List *)
+ fix_upper_expr(root,
+ (Node *) bf->filter_exprs,
+ index_itlist,
+ INDEX_VAR,
+ rtoffset,
+ NRM_EQUAL,
+ NUM_EXEC_QUAL((Plan *) plan));
+ }
+ }
/* indexqual is already transformed to reference index columns */
plan->indexqual = fix_scan_list(root, plan->indexqual,
rtoffset, 1);
@@ -2398,6 +2441,26 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
return expression_tree_walker(node, fix_scan_expr_walker, context);
}
+/*
+ * Fix references in hashkey expressions of bloom filters pushed down.
+ */
+static void
+fix_scan_bloom_filters(PlannerInfo *root, Plan *plan, int rtoffset)
+{
+ ListCell *lc;
+
+ if (plan->bloom_filters == NIL)
+ return;
+
+ foreach(lc, plan->bloom_filters)
+ {
+ BloomFilter *bf = lfirst_node(BloomFilter, lc);
+
+ bf->filter_exprs = fix_scan_list(root, bf->filter_exprs, rtoffset,
+ NUM_EXEC_QUAL(plan));
+ }
+}
+
/*
* set_join_references
* Modify the target list and quals of a join node to reference its
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3c1e6b31bf8..c9dcb294d4d 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -919,6 +919,13 @@
boot_val => 'true',
},
+{ name => 'enable_hashjoin_bloom', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
+ short_desc => 'Enables hash join bloom filter pushdown to the outer side.',
+ flags => 'GUC_EXPLAIN',
+ variable => 'enable_hashjoin_bloom',
+ boot_val => 'true',
+},
+
{ name => 'enable_incremental_sort', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
short_desc => 'Enables the planner\'s use of incremental sort steps.',
flags => 'GUC_EXPLAIN',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 86f2e16eba0..34f98b42ff6 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -448,6 +448,7 @@
#enable_distinct_reordering = on
#enable_self_join_elimination = on
#enable_eager_aggregate = on
+#enable_hashjoin_bloom = on
# - Planner Cost Constants -
diff --git a/src/include/executor/execScan.h b/src/include/executor/execScan.h
index 18b03235c3c..8cfe9b03c3e 100644
--- a/src/include/executor/execScan.h
+++ b/src/include/executor/execScan.h
@@ -170,10 +170,11 @@ ExecScanExtended(ScanState *node,
/* interrupt checks are in ExecScanFetch */
/*
- * If we have neither a qual to check nor a projection to do, just skip
- * all the overhead and return the raw scan tuple.
+ * If we have neither a qual to check nor a projection to do nor any
+ * pushed-down bloom filter to probe, just skip all the overhead and
+ * return the raw scan tuple.
*/
- if (!qual && !projInfo)
+ if (!qual && !projInfo && node->ps.bloom_filters == NIL)
{
ResetExprContext(econtext);
return ExecScanFetch(node, epqstate, accessMtd, recheckMtd);
@@ -214,6 +215,21 @@ ExecScanExtended(ScanState *node,
*/
econtext->ecxt_scantuple = slot;
+ /*
+ * If runtime bloom filters have been pushed down to this scan,
+ * check them first. A rejected tuple is dropped silently (no
+ * "Rows Removed by Filter" instrumentation -- the per-filter
+ * counters in BloomFilterState already capture this).
+ *
+ * XXX Maybe we should include this in "Rows Removed by Filter"?
+ */
+ if (node->ps.bloom_filters != NIL &&
+ !ExecBloomFilters(node->ps.bloom_filters, econtext))
+ {
+ ResetExprContext(econtext);
+ continue;
+ }
+
/*
* check that the current tuple satisfies the qual-clause
*
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1798e6027d4..12b0b2faedf 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -514,6 +514,18 @@ ExecProject(ProjectionInfo *projInfo)
}
#endif
+/*
+ * Bloom filter pushdown (see nodeHashjoin.c). Declared here so that
+ * scan nodes that act as recipients don't need to pull in hashjoin
+ * internals just to call these helpers from their ExecInit.
+ *
+ * XXX There's probably a better place for this. It should live in
+ * the executor somewhere, not in nodeHashjoin.c?
+ */
+extern bool ExecBloomFilters(List *filters, ExprContext *econtext);
+extern void ExecInitBloomFilters(PlanState *planstate,
+ TupleTableSlot *output_slot);
+
/*
* ExecQual - evaluate a qual prepared with ExecInitQual (possibly via
* ExecPrepareQual). Returns true if qual is satisfied, else false.
diff --git a/src/include/executor/nodeHashjoin.h b/src/include/executor/nodeHashjoin.h
index aebd39be8b5..08efefae209 100644
--- a/src/include/executor/nodeHashjoin.h
+++ b/src/include/executor/nodeHashjoin.h
@@ -31,4 +31,13 @@ extern void ExecHashJoinInitializeWorker(HashJoinState *state,
extern void ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue,
BufFile **fileptr, HashJoinTable hashtable);
+/*
+ * Bloom filter pushdown producer-side helper (see nodeHashjoin.c).
+ *
+ * ExecBloomFilters and ExecInitBloomFilters live in executor.h so that
+ * scan nodes can call them from ExecInit without having to pull in
+ * hashjoin internals.
+ */
+extern void ExecRegisterBloomFilterProducer(HashJoinState *hjstate);
+
#endif /* NODEHASHJOIN_H */
diff --git a/src/include/lib/bloomfilter.h b/src/include/lib/bloomfilter.h
index 860ee9bdc72..6b0026c8c45 100644
--- a/src/include/lib/bloomfilter.h
+++ b/src/include/lib/bloomfilter.h
@@ -23,5 +23,7 @@ extern void bloom_add_element(bloom_filter *filter, unsigned char *elem,
extern bool bloom_lacks_element(bloom_filter *filter, unsigned char *elem,
size_t len);
extern double bloom_prop_bits_set(bloom_filter *filter);
+extern uint64 bloom_total_bits(bloom_filter *filter);
+extern int bloom_hash_funcs(bloom_filter *filter);
#endif /* BLOOMFILTER_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e64fd8c7ea3..aad721f3421 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -763,6 +763,14 @@ typedef struct EState
List *es_auxmodifytables; /* List of secondary ModifyTableStates */
+ /*
+ * List of nodes producing pushed-down bloom filters. Each list element
+ * is a HashJoinState with at least one filter pushed down to a scan node.
+ * Scans look up their producer in this list (by plan_node_id) lazily on
+ * first probe (and cache the result).
+ */
+ List *es_bloom_producers;
+
/*
* this ExprContext is for per-output-tuple operations, such as constraint
* checks and index-value computations. It will be reset for each output
@@ -1287,8 +1295,58 @@ typedef struct PlanState
bool outeropsset;
bool inneropsset;
bool resultopsset;
+
+ /*
+ * Bloom filters (BloomFilterState) pushed to this node (NIL if no
+ * filters were pushed down to this node). Right now only some scan
+ * nodes expect filters, but the list is in PlanState so that we can
+ * expand the feature to more nodes in the future.
+ */
+ List *bloom_filters;
} PlanState;
+/*
+ * BloomFilterState
+ *
+ * State for executing (evaluating) a BloomFilter, pushed to a scan node.
+ *
+ * The producer is the HashJoinState this bloom filter is for. It's resolved
+ * at executor-init time using EState.es_bloom_producers (the producer always
+ * runs ExecInit before its recipients).
+ *
+ * 'checked' and 'rejected' are per-recipient counters reported by EXPLAIN
+ * ANALYZE. It's a bit redundant with the fields we have in the producer
+ * (see HashState). But if we ever end up with multiple consumers per filter,
+ * it'd be useful.
+ *
+ * The adaptive fields track the match fraction in recently checked probes.
+ * When most checks are passing through, the executor starts probing less
+ * frequently (and sampling probes instead), until the fraction of matches
+ * drops below some threshold.
+ */
+typedef struct BloomFilterState
+{
+ NodeTag type;
+
+ /* producer HJ node and the filter */
+ int producer_id;
+ struct HashJoinState *producer;
+ BloomFilter *filter;
+
+ int nkeys; /* number of hash keys */
+ ExprState *keys; /* ExprState for the hash calculation */
+
+ /* probe counters */
+ uint64 checked;
+ uint64 rejected;
+
+ /* adaptive probing */
+ uint64 adaptiveWindowProbes;
+ uint64 adaptiveWindowMatches;
+ uint64 adaptiveSampleCounter;
+ bool adaptiveSampling;
+} BloomFilterState;
+
/* ----------------
* these are defined to avoid confusion problems with "left"
* and "right" and "inner" and "outer". The convention is that
@@ -2703,6 +2761,39 @@ typedef struct HashState
Tuplestorestate *null_tuple_store; /* where to put null-keyed tuples */
bool keep_null_tuples; /* do we need to save such tuples? */
+ /*
+ * True iff at we managed to push down the bloom filter to at least one
+ * node on the HashJoin's outer side. It tells the Hash node to also build
+ * a bloom filter next to the hash table.
+ */
+ bool want_bloom_filter;
+
+ /*
+ * Mirror of the parent HashJoin's bloom_filter_id, copied here at
+ * ExecInitHashJoin time so EXPLAIN's show_hash_info can label the
+ * filter without traversing back up the plan-state tree (which is
+ * not easy, as there's no parent in PlanState).
+ */
+ int bloom_filter_id;
+
+ /*
+ * Bloom filter on hash values during the build phase. Probed by recipient
+ * nodes (typically scans in the outer subtree). NULL when pushdown is
+ * disabled or no recipient was identified, and (transiently) before
+ * ExecHashTableCreate runs (on the first outer tuple).
+ *
+ * Allocated in hashCxt, just like the hashtable itself. Reset on rescans.
+ */
+ struct bloom_filter *bloom_filter;
+
+ /*
+ * Counters with total per-filter instrumentation. Separate from the
+ * per-recipient counters in BloomFilterState. Redundant, but will be
+ * needed if we end up allowing multiple recipients.
+ */
+ uint64 bloomFilterChecked;
+ uint64 bloomFilterRejected;
+
/*
* In a parallelized hash join, the leader retains a pointer to the
* shared-memory stats area in its shared_info field, and then copies the
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c6815b7..69f9ad2d5e3 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -244,6 +244,9 @@ typedef struct PlannerGlobal
/* highest plan node ID assigned */
int lastPlanNodeId;
+ /* highest bloom-filter-producer ID assigned (see plannodes.h) */
+ int lastBloomFilterId;
+
/* redo plan when TransactionXmin changes? */
bool transientPlan;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 14a1dfed2b9..4e35d77cc49 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -254,8 +254,40 @@ typedef struct Plan
*/
Bitmapset *extParam;
Bitmapset *allParam;
+
+ /*
+ * List of BloomFilter nodes (or NIL). When non-empty, this plan node is
+ * a recipient of one or more runtime bloom filters pushed down at plan
+ * time by some HashJoin ancestor; see nodeHashjoin.c. Living on Plan
+ * (rather than on a specific subtype like Scan) allows pushdown to any
+ * node type that's prepared to call ExecBloomFilters on its output.
+ */
+ List *bloom_filters;
} Plan;
+/*
+ * BloomFilter -
+ * One pushed-down bloom filter, attached to a recipient Plan node.
+ *
+ * 'filter_exprs', 'hashops' and 'hashcollations' are parallel lists, one
+ * entry per join key: the expression to hash, its hash operator (OID),
+ * and its input collation (OID).
+ *
+ * 'producer_id' is the bloom_filter_id of the producing HashJoin (resolved at
+ * executor init time via EState.es_bloom_producers).
+ * ----------------
+ */
+typedef struct BloomFilter
+{
+ pg_node_attr(no_query_jumble)
+
+ NodeTag type;
+ List *filter_exprs;
+ List *hashops;
+ List *hashcollations;
+ int producer_id;
+} BloomFilter;
+
/* ----------------
* these are defined to avoid confusion problems with "left"
* and "right" and "inner" and "outer". The convention is that
@@ -1073,6 +1105,25 @@ typedef struct HashJoin
* perform lookups in the hashtable over the inner plan.
*/
List *hashkeys;
+
+ /*
+ * Number of plan nodes that consume this HashJoin's bloom filter via
+ * pushdown. Set at plan time by the bloom filter pushdown pass.
+ *
+ * At execution time, the HashJoin builds the bloom filter only when this
+ * is non-zero (and the enable_hashjoin_bloom GUC is on).
+ */
+ int bloom_consumer_count;
+
+ /*
+ * Identifier used by recipient nodes to find this producer at execution
+ * time, via EState.es_bloom_producers. Assigned during create_hashjoin_plan
+ * from PlannerGlobal.lastBloomFilterId. Each BloomFilter on a recipient stores
+ * a copy in its producer_id field for convenience.
+ *
+ * Zero when this HashJoin has no consumers.
+ */
+ int bloom_filter_id;
} HashJoin;
/* ----------------
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index f2fd5d31507..7339979c008 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -62,6 +62,7 @@ extern PGDLLIMPORT bool enable_material;
extern PGDLLIMPORT bool enable_memoize;
extern PGDLLIMPORT bool enable_mergejoin;
extern PGDLLIMPORT bool enable_hashjoin;
+extern PGDLLIMPORT bool enable_hashjoin_bloom;
extern PGDLLIMPORT bool enable_gathermerge;
extern PGDLLIMPORT bool enable_partitionwise_join;
extern PGDLLIMPORT bool enable_partitionwise_aggregate;
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 89e051ee824..a08d2556981 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1539,9 +1539,11 @@ group by t1.a,t1.b,t1.c,t1.d,t2.x,t2.y,t2.z;
-> Hash Join
Hash Cond: ((t2.x = t1.a) AND (t2.y = t1.b))
-> Seq Scan on t2
+ Bloom Filter 1: keys=(x, y)
-> Hash
+ Bloom Filter 1
-> Seq Scan on t1
-(7 rows)
+(9 rows)
-- Test case where t1 can be optimized but not t2
explain (costs off) select t1.*,t2.x,t2.z
@@ -1554,9 +1556,11 @@ group by t1.a,t1.b,t1.c,t1.d,t2.x,t2.z;
-> Hash Join
Hash Cond: ((t2.x = t1.a) AND (t2.y = t1.b))
-> Seq Scan on t2
+ Bloom Filter 1: keys=(x, y)
-> Hash
+ Bloom Filter 1
-> Seq Scan on t1
-(7 rows)
+(9 rows)
-- Cannot optimize when PK is deferrable
explain (costs off) select * from t3 group by a,b,c;
diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out
index 091ae48a92b..bbdb9526471 100644
--- a/src/test/regress/expected/eager_aggregate.out
+++ b/src/test/regress/expected/eager_aggregate.out
@@ -34,14 +34,16 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(18 rows)
+(20 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -80,8 +82,10 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
+ Bloom Filter 1
-> Partial GroupAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
@@ -90,7 +94,7 @@ GROUP BY t1.a ORDER BY t1.a;
Sort Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.c, t2.b
-(21 rows)
+(23 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -134,8 +138,10 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 2: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg((t2.c + t3.c)))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2.b, PARTIAL avg((t2.c + t3.c))
Group Key: t2.b
@@ -144,11 +150,13 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t3.a = t2.a)
-> Seq Scan on public.eager_agg_t3 t3
Output: t3.a, t3.b, t3.c
+ Bloom Filter 1: keys=(t3.a)
-> Hash
Output: t2.c, t2.b, t2.a
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.c, t2.b, t2.a
-(25 rows)
+(29 rows)
SELECT t1.a, avg(t2.c + t3.c)
FROM eager_agg_t1 t1
@@ -189,8 +197,10 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 2: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg((t2.c + t3.c)))
+ Bloom Filter 2
-> Partial GroupAggregate
Output: t2.b, PARTIAL avg((t2.c + t3.c))
Group Key: t2.b
@@ -202,11 +212,13 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t3.a = t2.a)
-> Seq Scan on public.eager_agg_t3 t3
Output: t3.a, t3.b, t3.c
+ Bloom Filter 1: keys=(t3.a)
-> Hash
Output: t2.c, t2.b, t2.a
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.c, t2.b, t2.a
-(28 rows)
+(32 rows)
SELECT t1.a, avg(t2.c + t3.c)
FROM eager_agg_t1 t1
@@ -249,14 +261,16 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(18 rows)
+(20 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -295,11 +309,13 @@ GROUP BY t2.b ORDER BY t2.b;
Hash Cond: (t2.b = t1.b)
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
+ Bloom Filter 1: keys=(t2.b)
-> Hash
Output: t1.b
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.b
-(15 rows)
+(17 rows)
SELECT t2.b, avg(t2.c)
FROM eager_agg_t1 t1
@@ -400,14 +416,16 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(18 rows)
+(20 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -444,9 +462,11 @@ GROUP BY t1.a ORDER BY t1.a;
-> Hash Join
Hash Cond: (t2.b = t1.b)
-> Seq Scan on eager_agg_t2 t2
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on eager_agg_t1 t1
-(9 rows)
+(11 rows)
EXPLAIN (COSTS OFF)
SELECT t1.a, avg(t2.c) FILTER (WHERE random() > 0.5)
@@ -462,9 +482,11 @@ GROUP BY t1.a ORDER BY t1.a;
-> Hash Join
Hash Cond: (t2.b = t1.b)
-> Seq Scan on eager_agg_t2 t2
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on eager_agg_t1 t1
-(9 rows)
+(11 rows)
-- Eager aggregation must not push a partial aggregate onto the inner side of a
-- SEMI or ANTI join
@@ -599,8 +621,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
+ Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -614,8 +638,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
+ Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -629,14 +655,16 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
+ Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x, t1_2.y
-(49 rows)
+(55 rows)
SELECT t1.x, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -681,8 +709,10 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
+ Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -696,8 +726,10 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
+ Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -711,14 +743,16 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
+ Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.y, t1_2.x
-(49 rows)
+(55 rows)
SELECT t2.y, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -765,8 +799,10 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.x, t2.y
+ Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.x)), (PARTIAL count(*)), (PARTIAL avg(t1.x))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.x), PARTIAL count(*), PARTIAL avg(t1.x)
Group Key: t1.x
@@ -777,8 +813,10 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.x, t2_1.y
+ Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.x)), (PARTIAL count(*)), (PARTIAL avg(t1_1.x))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.x), PARTIAL count(*), PARTIAL avg(t1_1.x)
Group Key: t1_1.x
@@ -789,14 +827,16 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.x, t2_2.y
+ Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.x)), (PARTIAL count(*)), (PARTIAL avg(t1_2.x))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.x), PARTIAL count(*), PARTIAL avg(t1_2.x)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x
-(44 rows)
+(50 rows)
SELECT t2.x, sum(t1.x), count(*)
FROM eager_agg_tab1 t1
@@ -838,8 +878,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab1_p1 t1
Output: t1.x
+ Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y)))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y))
Group Key: t2.x
@@ -848,8 +890,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab1_p1 t2
Output: t2.y, t2.x
+ Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_tab1_p1 t3
Output: t3.y, t3.x
-> Finalize HashAggregate
@@ -860,8 +904,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab1_p2 t1_1
Output: t1_1.x
+ Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y)))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y))
Group Key: t2_1.x
@@ -870,8 +916,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab1_p2 t2_1
Output: t2_1.y, t2_1.x
+ Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
+ Bloom Filter 3
-> Seq Scan on public.eager_agg_tab1_p2 t3_1
Output: t3_1.y, t3_1.x
-> Finalize HashAggregate
@@ -882,8 +930,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x
+ Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y)))
+ Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y))
Group Key: t2_2.x
@@ -892,11 +942,13 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab1_p3 t2_2
Output: t2_2.y, t2_2.x
+ Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
+ Bloom Filter 5
-> Seq Scan on public.eager_agg_tab1_p3 t3_2
Output: t3_2.y, t3_2.x
-(70 rows)
+(82 rows)
SELECT t1.x, sum(t2.y + t3.y)
FROM eager_agg_tab1 t1
@@ -1066,8 +1118,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
+ Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -1081,8 +1135,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
+ Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -1096,14 +1152,16 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
+ Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x, t1_2.y
-(49 rows)
+(55 rows)
SELECT t1.x, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -1166,8 +1224,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
+ Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1181,8 +1241,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
+ Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1196,8 +1258,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
+ Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1211,8 +1275,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
+ Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1226,14 +1292,16 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
+ Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
+ Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(79 rows)
+(89 rows)
SELECT t1.x, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1294,8 +1362,10 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.y, t1.x
+ Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1306,8 +1376,10 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.y, t1_1.x
+ Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1318,8 +1390,10 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.y, t1_2.x
+ Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1330,8 +1404,10 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.y, t1_3.x
+ Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1342,14 +1418,16 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.y, t1_4.x
+ Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
+ Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(67 rows)
+(77 rows)
SELECT t1.y, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1411,8 +1489,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
+ Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*)
Group Key: t2.x
@@ -1421,8 +1501,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t2
Output: t2.y, t2.x
+ Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_tab_ml_p1 t3
Output: t3.y, t3.x
-> Finalize HashAggregate
@@ -1433,8 +1515,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
+ Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*)
Group Key: t2_1.x
@@ -1443,8 +1527,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1
Output: t2_1.y, t2_1.x
+ Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
+ Bloom Filter 3
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1
Output: t3_1.y, t3_1.x
-> Finalize HashAggregate
@@ -1455,8 +1541,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
+ Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*))
+ Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*)
Group Key: t2_2.x
@@ -1465,8 +1553,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2
Output: t2_2.y, t2_2.x
+ Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
+ Bloom Filter 5
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2
Output: t3_2.y, t3_2.x
-> Finalize HashAggregate
@@ -1477,8 +1567,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
+ Bloom Filter 8: keys=(t1_3.x)
-> Hash
Output: t2_3.x, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*))
+ Bloom Filter 8
-> Partial HashAggregate
Output: t2_3.x, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*)
Group Key: t2_3.x
@@ -1487,8 +1579,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_3.x = t3_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3
Output: t2_3.y, t2_3.x
+ Bloom Filter 7: keys=(t2_3.x)
-> Hash
Output: t3_3.y, t3_3.x
+ Bloom Filter 7
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3
Output: t3_3.y, t3_3.x
-> Finalize HashAggregate
@@ -1499,8 +1593,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
+ Bloom Filter 10: keys=(t1_4.x)
-> Hash
Output: t2_4.x, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*))
+ Bloom Filter 10
-> Partial HashAggregate
Output: t2_4.x, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*)
Group Key: t2_4.x
@@ -1509,11 +1605,13 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_4.x = t3_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
+ Bloom Filter 9: keys=(t2_4.x)
-> Hash
Output: t3_4.y, t3_4.x
+ Bloom Filter 9
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4
Output: t3_4.y, t3_4.x
-(114 rows)
+(134 rows)
SELECT t1.x, sum(t2.y + t3.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1575,8 +1673,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
+ Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.y, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.y, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*)
Group Key: t2.x, t3.y, t3.x
@@ -1585,8 +1685,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t2
Output: t2.y, t2.x
+ Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
+ Bloom Filter 1
-> Seq Scan on public.eager_agg_tab_ml_p1 t3
Output: t3.y, t3.x
-> Hash Join
@@ -1594,8 +1696,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
+ Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.y, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.y, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*)
Group Key: t2_1.x, t3_1.y, t3_1.x
@@ -1604,8 +1708,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1
Output: t2_1.y, t2_1.x
+ Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
+ Bloom Filter 3
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1
Output: t3_1.y, t3_1.x
-> Hash Join
@@ -1613,8 +1719,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
+ Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.y, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*))
+ Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.y, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*)
Group Key: t2_2.x, t3_2.y, t3_2.x
@@ -1623,8 +1731,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2
Output: t2_2.y, t2_2.x
+ Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
+ Bloom Filter 5
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2
Output: t3_2.y, t3_2.x
-> Hash Join
@@ -1632,8 +1742,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
+ Bloom Filter 8: keys=(t1_3.x)
-> Hash
Output: t2_3.x, t3_3.y, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*))
+ Bloom Filter 8
-> Partial HashAggregate
Output: t2_3.x, t3_3.y, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*)
Group Key: t2_3.x, t3_3.y, t3_3.x
@@ -1642,8 +1754,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_3.x = t3_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3
Output: t2_3.y, t2_3.x
+ Bloom Filter 7: keys=(t2_3.x)
-> Hash
Output: t3_3.y, t3_3.x
+ Bloom Filter 7
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3
Output: t3_3.y, t3_3.x
-> Hash Join
@@ -1651,8 +1765,10 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
+ Bloom Filter 10: keys=(t1_4.x)
-> Hash
Output: t2_4.x, t3_4.y, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*))
+ Bloom Filter 10
-> Partial HashAggregate
Output: t2_4.x, t3_4.y, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*)
Group Key: t2_4.x, t3_4.y, t3_4.x
@@ -1661,11 +1777,13 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_4.x = t3_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
+ Bloom Filter 9: keys=(t2_4.x)
-> Hash
Output: t3_4.y, t3_4.x
+ Bloom Filter 9
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4
Output: t3_4.y, t3_4.x
-(102 rows)
+(122 rows)
SELECT t3.y, sum(t2.y + t3.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1728,8 +1846,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
+ Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1743,8 +1863,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
+ Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
+ Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1758,8 +1880,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
+ Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
+ Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1773,8 +1897,10 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
+ Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
+ Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1788,14 +1914,16 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
+ Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
+ Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(79 rows)
+(89 rows)
SELECT t1.x, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index ed946abed7f..4fccc7c4057 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1909,20 +1909,24 @@ select * from tenk1 a, tenk1 b
where exists(select * from tenk1 c
where b.twothousand = c.twothousand and b.fivethous <> c.fivethous)
and a.tenthous = b.tenthous and a.tenthous < 5000;
- QUERY PLAN
------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------
Hash Semi Join
Hash Cond: (b.twothousand = c.twothousand)
Join Filter: (b.fivethous <> c.fivethous)
-> Hash Join
Hash Cond: (b.tenthous = a.tenthous)
-> Seq Scan on tenk1 b
+ Bloom Filter 1: keys=(tenthous)
+ Bloom Filter 2: keys=(twothousand)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 a
Filter: (tenthous < 5000)
-> Hash
+ Bloom Filter 2
-> Seq Scan on tenk1 c
-(11 rows)
+(15 rows)
--
-- More complicated constructs
@@ -2378,9 +2382,11 @@ order by t1.unique1;
Hash Cond: ((t1.two = t2.two) AND (t1.unique1 = (SubPlan expr_1)))
-> Bitmap Heap Scan on tenk1 t1
Recheck Cond: (unique1 < 10)
+ Bloom Filter 1: keys=(two, unique1)
-> Bitmap Index Scan on tenk1_unique1
Index Cond: (unique1 < 10)
-> Hash
+ Bloom Filter 1
-> Bitmap Heap Scan on tenk1 t2
Recheck Cond: (unique1 < 10)
-> Bitmap Index Scan on tenk1_unique1
@@ -2392,7 +2398,7 @@ order by t1.unique1;
-> Limit
-> Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 IS NOT NULL) AND (unique1 = t2.unique1))
-(20 rows)
+(22 rows)
-- Ensure we get the expected result
select t1.unique1,t2.unique1 from tenk1 t1
@@ -2598,12 +2604,14 @@ select * from int4_tbl t1
Join Filter: (t2.f1 > 0)
Filter: (t3.f1 IS NULL)
-> Seq Scan on int4_tbl t2
+ Bloom Filter 1: keys=(f1)
-> Materialize
-> Seq Scan on int4_tbl t3
-> Seq Scan on tenk1 t4
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl t1
-(13 rows)
+(15 rows)
explain (costs off)
select * from int4_tbl t1
@@ -2622,13 +2630,15 @@ select * from int4_tbl t1
Join Filter: (t2.f1 > 0)
Filter: (t2.f1 <> COALESCE(t3.f1, '-1'::integer))
-> Seq Scan on int4_tbl t2
+ Bloom Filter 1: keys=(f1)
-> Materialize
-> Seq Scan on int4_tbl t3
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl t1
-> Materialize
-> Seq Scan on tenk1 t4
-(14 rows)
+(16 rows)
explain (costs off)
select * from onek t1
@@ -3122,10 +3132,12 @@ select count(*) from tenk1 a, tenk1 b
-> Hash Join
Hash Cond: (a.hundred = b.thousand)
-> Index Only Scan using tenk1_hundred on tenk1 a
+ Bloom Filter 1: keys=(hundred)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 b
Filter: ((fivethous % 10) < 10)
-(7 rows)
+(9 rows)
select count(*) from tenk1 a, tenk1 b
where a.hundred = b.thousand and (b.fivethous % 10) < 10;
@@ -3168,11 +3180,13 @@ ORDER BY 1;
Hash Cond: (b.f1 = c.f1)
Filter: (COALESCE(c.f1, 0) = 0)
-> Seq Scan on tt3 b
+ Bloom Filter 1: keys=(f1)
-> Hash
-> Seq Scan on tt3 c
-> Hash
+ Bloom Filter 1
-> Seq Scan on tt4 a
-(13 rows)
+(15 rows)
SELECT a.f1
FROM tt4 a
@@ -3210,10 +3224,12 @@ where t1.filt = 5;
Hash Join
Hash Cond: (t2.val = t1.val)
-> Seq Scan on skewedtable t2
+ Bloom Filter 1: keys=(val)
-> Hash
+ Bloom Filter 1
-> Seq Scan on skewedtable t1
Filter: (filt = 5)
-(6 rows)
+(8 rows)
drop table skewedtable;
--
@@ -3227,9 +3243,11 @@ where unique1 in (select unique2 from tenk1 b);
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(5 rows)
+(7 rows)
-- sadly, this is not an antijoin
explain (costs off)
@@ -3251,9 +3269,11 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2);
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(5 rows)
+(7 rows)
explain (costs off)
select a.* from tenk1 a
@@ -3290,11 +3310,13 @@ where (hundred, thousand) in (select twothousand, twothousand from onek);
Hash Cond: (tenk1.hundred = onek.twothousand)
-> Seq Scan on tenk1
Filter: (hundred = thousand)
+ Bloom Filter 1: keys=(hundred)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: onek.twothousand
-> Seq Scan on onek
-(8 rows)
+(10 rows)
reset enable_memoize;
--
@@ -3311,17 +3333,19 @@ where t2.a is null;
Hash Right Anti Join
Hash Cond: (t2.b = t1.unique1)
-> Seq Scan on tbl_anti t2
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 t1
-(5 rows)
+(7 rows)
-- this is an antijoin, as t2.a is non-null for any matching row
explain (costs off)
select * from tenk1 t1 left join
(tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b
where t2.a is null;
- QUERY PLAN
--------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Right Anti Join
Hash Cond: (t2.b = t1.unique1)
-> Merge Left Join
@@ -3329,20 +3353,22 @@ where t2.a is null;
-> Sort
Sort Key: t2.c
-> Seq Scan on tbl_anti t2
+ Bloom Filter 1: keys=(b)
-> Sort
Sort Key: t3.c
-> Seq Scan on tbl_anti t3
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 t1
-(12 rows)
+(14 rows)
-- this is not an antijoin, as t3.a can be nulled by t2/t3 join
explain (costs off)
select * from tenk1 t1 left join
(tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b
where t3.a is null;
- QUERY PLAN
--------------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Right Join
Hash Cond: (t2.b = t1.unique1)
Filter: (t3.a IS NULL)
@@ -3351,12 +3377,14 @@ where t3.a is null;
-> Sort
Sort Key: t2.c
-> Seq Scan on tbl_anti t2
+ Bloom Filter 1: keys=(b)
-> Sort
Sort Key: t3.c
-> Seq Scan on tbl_anti t3
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 t1
-(13 rows)
+(15 rows)
rollback;
--
@@ -3370,9 +3398,11 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2 group by b.uniqu
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(5 rows)
+(7 rows)
--
-- regression test for proper handling of outer joins within antijoins
@@ -3989,11 +4019,13 @@ where q1 = thousand or q2 = thousand;
-> Seq Scan on q2
-> Bitmap Heap Scan on tenk1
Recheck Cond: ((q1.q1 = thousand) OR (q2.q2 = thousand))
+ Bloom Filter 1: keys=(twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = ANY (ARRAY[q1.q1, q2.q2]))
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl
-(12 rows)
+(14 rows)
explain (costs off)
select * from
@@ -4010,11 +4042,13 @@ where thousand = (q1 + q2);
-> Seq Scan on q2
-> Bitmap Heap Scan on tenk1
Recheck Cond: (thousand = (q1.q1 + q2.q2))
+ Bloom Filter 1: keys=(twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = (q1.q1 + q2.q2))
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl
-(12 rows)
+(14 rows)
--
-- test ability to generate a suitable plan for a star-schema query
@@ -4120,8 +4154,10 @@ where t1.unique1 < i4.f1;
Hash Cond: (t2.ten = t1.tenthous)
-> Seq Scan on public.tenk1 t2
Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4
+ Bloom Filter 1: keys=(t2.ten)
-> Hash
Output: t1.tenthous, t1.unique1
+ Bloom Filter 1
-> Nested Loop
Output: t1.tenthous, t1.unique1
-> Subquery Scan on ss0
@@ -4137,7 +4173,7 @@ where t1.unique1 < i4.f1;
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
Filter: (i8.q1 = ((64)::information_schema.cardinal_number)::integer)
-(33 rows)
+(35 rows)
select ss1.d1 from
tenk1 as t1
@@ -5234,6 +5270,7 @@ order by i0.f1, x;
Output: i1.f1, i2.q1, i2.q2, '123'::bigint
-> Seq Scan on public.int4_tbl i1
Output: i1.f1
+ Bloom Filter 1: keys=(i1.f1)
-> Materialize
Output: i2.q1, i2.q2
-> Seq Scan on public.int8_tbl i2
@@ -5241,9 +5278,10 @@ order by i0.f1, x;
Filter: (123 = i2.q2)
-> Hash
Output: i0.f1
+ Bloom Filter 1
-> Seq Scan on public.int4_tbl i0
Output: i0.f1
-(19 rows)
+(21 rows)
select * from
int4_tbl i0 left join
@@ -5297,8 +5335,10 @@ select t1.* from
Hash Cond: (i8.q1 = i8b2.q1)
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
+ Bloom Filter 1: keys=(i8.q1)
-> Hash
Output: i8b2.q1, (NULL::integer)
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, NULL::integer
-> Hash
@@ -5309,7 +5349,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(30 rows)
+(32 rows)
select t1.* from
text_tbl t1
@@ -5360,10 +5400,12 @@ select t1.* from
Output: i8b2.q1, NULL::integer
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, i8b2.q2
+ Bloom Filter 1: keys=(i8b2.q1)
-> Materialize
-> Seq Scan on public.int4_tbl i4b2
-> Hash
Output: i8.q1, i8.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Hash
@@ -5374,7 +5416,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(34 rows)
+(36 rows)
select t1.* from
text_tbl t1
@@ -5427,12 +5469,16 @@ select t1.* from
Hash Cond: (i8b2.q1 = i4b2.f1)
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, i8b2.q2
+ Bloom Filter 1: keys=(i8b2.q1)
+ Bloom Filter 2: keys=(i8b2.q1)
-> Hash
Output: i4b2.f1
+ Bloom Filter 1
-> Seq Scan on public.int4_tbl i4b2
Output: i4b2.f1
-> Hash
Output: i8.q1, i8.q2
+ Bloom Filter 2
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Hash
@@ -5443,7 +5489,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(37 rows)
+(41 rows)
select t1.* from
text_tbl t1
@@ -5794,15 +5840,17 @@ where ss1.c2 = 0;
Filter: (i43.f1 = 0)
-> Seq Scan on public.int4_tbl i41
Output: i41.f1
+ Bloom Filter 1: keys=(i41.f1)
-> Hash
Output: i42.f1
+ Bloom Filter 1
-> Seq Scan on public.int4_tbl i42
Output: i42.f1
-> Limit
Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42))
-> Seq Scan on public.text_tbl
Output: i41.f1, i8.q1, i8.q2, i42.f1, i43.f1, (42)
-(25 rows)
+(27 rows)
select ss2.* from
int4_tbl i41
@@ -5934,12 +5982,14 @@ select a.unique1, b.unique2
Hash Cond: (b.unique2 = a.unique1)
-> Seq Scan on onek b
Filter: (ANY ((unique2 = (SubPlan any_1).col1) AND ((random() > '0'::double precision) = (SubPlan any_1).col2)))
+ Bloom Filter 1: keys=(unique2)
SubPlan any_1
-> Seq Scan on int8_tbl c
Filter: (q1 < b.unique1)
-> Hash
+ Bloom Filter 1
-> Index Only Scan using onek_unique1 on onek a
-(9 rows)
+(11 rows)
select a.unique1, b.unique2
from onek a left join onek b on a.unique1 = b.unique2
@@ -6092,14 +6142,16 @@ explain (costs off)
select id from a where id in (
select b.id from b left join c on b.id = c.id
);
- QUERY PLAN
-----------------------------
+ QUERY PLAN
+-----------------------------------
Hash Join
Hash Cond: (a.id = b.id)
-> Seq Scan on a
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on b
-(5 rows)
+(7 rows)
-- check optimization with oddly-nested outer joins
explain (costs off)
@@ -6522,16 +6574,18 @@ explain (costs off)
select c.id, ss.a from c
left join (select d.a from onerow, d left join b on d.a = b.id) ss
on c.id = ss.a;
- QUERY PLAN
---------------------------------
+ QUERY PLAN
+----------------------------------------
Hash Right Join
Hash Cond: (d.a = c.id)
-> Nested Loop
-> Seq Scan on onerow
-> Seq Scan on d
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on c
-(7 rows)
+(9 rows)
-- check the case when the placeholder relates to an outer join and its
-- inner in the press field but actually uses only the outer side of the join
@@ -6883,23 +6937,27 @@ where exists (select 1 from t t4
Hash Cond: (t6.b = t4.b)
-> Seq Scan on pg_temp.t t6
Output: t6.a, t6.b
+ Bloom Filter 2: keys=(t6.b)
-> Hash
Output: t4.b, t5.b, t5.a
+ Bloom Filter 2
-> Hash Join
Output: t4.b, t5.b, t5.a
Inner Unique: true
Hash Cond: (t5.b = t4.b)
-> Seq Scan on pg_temp.t t5
Output: t5.a, t5.b
+ Bloom Filter 1: keys=(t5.b)
-> Hash
Output: t4.b, t4.a
+ Bloom Filter 1
-> Index Scan using t_a_key on pg_temp.t t4
Output: t4.b, t4.a
Index Cond: (t4.a = 1)
-> Index Only Scan using t_a_key on pg_temp.t t3
Output: t3.a
Index Cond: (t3.a = t5.a)
-(32 rows)
+(36 rows)
select t1.a from t t1
left join t t2 on t1.a = t2.a
@@ -9085,13 +9143,15 @@ select * from
Output: b.q1, COALESCE(b.q2, '42'::bigint)
-> Seq Scan on public.int8_tbl d
Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)
+ Bloom Filter 1: keys=(d.q1)
-> Hash
Output: c.q1, c.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Result
Output: (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))
-(24 rows)
+(26 rows)
-- another case requiring nested PlaceHolderVars
explain (verbose, costs off)
@@ -9150,25 +9210,29 @@ select c.*,a.*,ss1.q1,ss2.q1,ss3.* from
Join Filter: (b.q1 < b2.f1)
-> Seq Scan on public.int8_tbl b
Output: b.q1, b.q2
+ Bloom Filter 1: keys=(b.q1)
-> Materialize
Output: b2.f1
-> Seq Scan on public.int4_tbl b2
Output: b2.f1
-> Hash
Output: a.q1, a.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl a
Output: a.q1, a.q2
-> Seq Scan on public.int8_tbl d
Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
+ Bloom Filter 2: keys=(d.q1)
-> Hash
Output: c.q1, c.q2
+ Bloom Filter 2
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Materialize
Output: i.f1
-> Seq Scan on public.int4_tbl i
Output: i.f1
-(34 rows)
+(38 rows)
-- check processing of postponed quals (bug #9041)
explain (verbose, costs off)
@@ -9475,8 +9539,10 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral
Hash Cond: (t3.b = t2.a)
-> Seq Scan on public.join_ut1 t3
Output: t3.a, t3.b, t3.c
+ Bloom Filter 1: keys=(t3.b)
-> Hash
Output: t2.a
+ Bloom Filter 1
-> Append
-> Seq Scan on public.join_pt1p1p1 t2_1
Output: t2_1.a
@@ -9484,7 +9550,7 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral
-> Seq Scan on public.join_pt1p2 t2_2
Output: t2_2.a
Filter: (t1.a = t2_2.a)
-(21 rows)
+(23 rows)
select t1.b, ss.phv from join_ut1 t1 left join lateral
(select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv
@@ -9518,12 +9584,14 @@ select * from fkest f1
Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10))
-> Seq Scan on fkest f2
Filter: (x100 = 2)
+ Bloom Filter 1: keys=(x, x10b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on fkest f1
Filter: (x100 = 2)
-> Index Scan using fkest_x_x10_x100_idx on fkest f3
Index Cond: (x = f1.x)
-(10 rows)
+(12 rows)
alter table fkest add constraint fk
foreign key (x, x10b, x100) references fkest (x, x10, x100);
@@ -9539,13 +9607,15 @@ select * from fkest f1
-> Hash Join
Hash Cond: (f3.x = f2.x)
-> Seq Scan on fkest f3
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on fkest f2
Filter: (x100 = 2)
-> Hash
-> Seq Scan on fkest f1
Filter: (x100 = 2)
-(11 rows)
+(13 rows)
rollback;
--
@@ -9598,19 +9668,21 @@ analyze j3;
-- ensure join is properly marked as unique
explain (verbose, costs off)
select * from j1 inner join j2 on j1.id = j2.id;
- QUERY PLAN
------------------------------------
+ QUERY PLAN
+--------------------------------------
Hash Join
Output: j1.id, j2.id
Inner Unique: true
Hash Cond: (j1.id = j2.id)
-> Seq Scan on public.j1
Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
-> Hash
Output: j2.id
+ Bloom Filter 1
-> Seq Scan on public.j2
Output: j2.id
-(10 rows)
+(12 rows)
-- ensure join is not unique when not an equi-join
explain (verbose, costs off)
@@ -9631,19 +9703,21 @@ 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
Inner Unique: true
Hash Cond: (j3.id = j1.id)
-> Seq Scan on public.j3
Output: j3.id
+ Bloom Filter 1: keys=(j3.id)
-> Hash
Output: j1.id
+ Bloom Filter 1
-> Seq Scan on public.j1
Output: j1.id
-(10 rows)
+(12 rows)
-- ensure left join is marked as unique
explain (verbose, costs off)
@@ -9714,19 +9788,21 @@ select * from j1 cross join j2;
-- ensure a natural join is marked as unique
explain (verbose, costs off)
select * from j1 natural join j2;
- QUERY PLAN
------------------------------------
+ QUERY PLAN
+--------------------------------------
Hash Join
Output: j1.id
Inner Unique: true
Hash Cond: (j1.id = j2.id)
-> Seq Scan on public.j1
Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
-> Hash
Output: j2.id
+ Bloom Filter 1
-> Seq Scan on public.j2
Output: j2.id
-(10 rows)
+(12 rows)
-- ensure a distinct clause allows the inner to become unique
explain (verbose, costs off)
diff --git a/src/test/regress/expected/join_hash.out b/src/test/regress/expected/join_hash.out
index 75009e29720..0a8ade8b961 100644
--- a/src/test/regress/expected/join_hash.out
+++ b/src/test/regress/expected/join_hash.out
@@ -90,15 +90,17 @@ set local work_mem = '4MB';
set local hash_mem_multiplier = 1.0;
explain (costs off)
select count(*) from simple r join simple s using (id);
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+-----------------------------------------
Aggregate
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on simple s
-(6 rows)
+(8 rows)
select count(*) from simple r join simple s using (id);
count
@@ -203,15 +205,17 @@ set local work_mem = '128kB';
set local hash_mem_multiplier = 1.0;
explain (costs off)
select count(*) from simple r join simple s using (id);
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+-----------------------------------------
Aggregate
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on simple s
-(6 rows)
+(8 rows)
select count(*) from simple r join simple s using (id);
count
@@ -330,9 +334,11 @@ explain (costs off)
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on bigger_than_it_looks s
-(6 rows)
+(8 rows)
select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id);
count
@@ -445,9 +451,11 @@ explain (costs off)
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
+ Bloom Filter 1: keys=(id)
-> Hash
+ Bloom Filter 1
-> Seq Scan on extremely_skewed s
-(6 rows)
+(8 rows)
select count(*) from simple r join extremely_skewed s using (id);
count
@@ -1149,9 +1157,11 @@ lateral (select t1.fivethous, i4.f1 from tenk1 t1 join int4_tbl i4
-> Hash Join
Hash Cond: (t1.fivethous = (i4.f1 + i8.q2))
-> Seq Scan on tenk1 t1
+ Bloom Filter 1: keys=(fivethous)
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl i4
-(9 rows)
+(11 rows)
select i8.q2, ss.* from
int8_tbl i8,
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index 9cb1d87066a..c5aa11cd249 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -323,15 +323,17 @@ USING source AS s
ON t.tid = s.sid
WHEN MATCHED THEN
UPDATE SET balance = 0;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+------------------------------------------
Merge on target t
-> Hash Join
Hash Cond: (s.sid = t.tid)
-> Seq Scan on source s
+ Bloom Filter 1: keys=(sid)
-> Hash
+ Bloom Filter 1
-> Seq Scan on target t
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF)
MERGE INTO target t
@@ -339,15 +341,17 @@ USING source AS s
ON t.tid = s.sid
WHEN MATCHED THEN
DELETE;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+------------------------------------------
Merge on target t
-> Hash Join
Hash Cond: (s.sid = t.tid)
-> Seq Scan on source s
+ Bloom Filter 1: keys=(sid)
-> Hash
+ Bloom Filter 1
-> Seq Scan on target t
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF)
MERGE INTO target t
@@ -1831,8 +1835,10 @@ WHEN MATCHED AND t.c > s.cnt THEN
Join Filter: (t.b < (SubPlan expr_1))
-> Seq Scan on public.tgt t
Output: t.ctid, t.a, t.b
+ Bloom Filter 1: keys=(t.a)
-> Hash
Output: s.a, s.b, s.c, s.d, s.ctid
+ Bloom Filter 1
-> Seq Scan on public.src s
Output: s.a, s.b, s.c, s.d, s.ctid
SubPlan expr_1
@@ -1856,7 +1862,7 @@ WHEN MATCHED AND t.c > s.cnt THEN
-> Seq Scan on public.ref r_1
Output: r_1.ab, r_1.cd
Filter: ((r_1.ab = (s.a + s.b)) AND (r_1.cd = (s.c - s.d)))
-(32 rows)
+(34 rows)
DROP TABLE src, tgt, ref;
-- Subqueries
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index c3261bff209..b52528870ef 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -582,10 +582,12 @@ WHERE my_int_eq(a.unique2, 42);
Hash Join
Hash Cond: (b.unique1 = a.unique1)
-> Seq Scan on tenk1 b
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 a
Filter: my_int_eq(unique2, 42)
-(6 rows)
+(8 rows)
-- With support function that knows it's int4eq, we get a different plan
CREATE FUNCTION test_support_func(internal)
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index c30304b99c7..be56036461b 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -460,23 +460,29 @@ SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> HashAggregate
Group Key: t1_1.x
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
+ Bloom Filter 2: keys=(x)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> HashAggregate
Group Key: t1_2.x
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
+ Bloom Filter 3: keys=(y)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(24 rows)
+(30 rows)
SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3;
x | sum | count
@@ -533,23 +539,29 @@ SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> HashAggregate
Group Key: t2_1.y
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
+ Bloom Filter 2: keys=(x)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> HashAggregate
Group Key: t2_2.y
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
+ Bloom Filter 3: keys=(y)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(24 rows)
+(30 rows)
-- When GROUP BY clause does not match; partial aggregation is performed for each partition.
-- Also test GroupAggregate paths by disabling hash aggregates.
@@ -572,7 +584,9 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> Partial GroupAggregate
Group Key: t1_1.y
@@ -581,7 +595,9 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
+ Bloom Filter 2: keys=(x)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> Partial GroupAggregate
Group Key: t1_2.y
@@ -590,9 +606,11 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
+ Bloom Filter 3: keys=(y)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(34 rows)
+(40 rows)
SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3;
y | sum | count
@@ -638,9 +656,11 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP B
-> Hash Right Join
Hash Cond: (b_2.y = a_2.x)
-> Seq Scan on pagg_tab2_p3 b_2
+ Bloom Filter 1: keys=(y)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab1_p3 a_2
-(26 rows)
+(28 rows)
SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
y | sum
@@ -667,14 +687,18 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
-> Hash Right Join
Hash Cond: (a.x = b.y)
-> Seq Scan on pagg_tab1_p1 a
+ Bloom Filter 1: keys=(x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 b
-> HashAggregate
Group Key: b_1.y
-> Hash Right Join
Hash Cond: (a_1.x = b_1.y)
-> Seq Scan on pagg_tab1_p2 a_1
+ Bloom Filter 2: keys=(x)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 b_1
-> HashAggregate
Group Key: b_2.y
@@ -683,7 +707,7 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
-> Seq Scan on pagg_tab2_p3 b_2
-> Hash
-> Seq Scan on pagg_tab1_p3 a_2
-(24 rows)
+(28 rows)
SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
y | sum
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 38643d41fd7..1906b3641a3 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -36,22 +36,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -77,22 +83,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a =
-> Hash Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = b)
-> Hash Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = b)
-> Hash Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_p3 t2_3
Filter: (a = b)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a = t2.b ORDER BY t1.a, t2.b;
a | c | b | c
@@ -202,13 +214,17 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE
-> Hash Right Join
Hash Cond: (t1_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = 0)
-> Hash Right Join
Hash Cond: (t1_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Left Join
@@ -216,7 +232,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE
Filter: (a = 0)
-> Index Scan using iprt1_p3_a on prt1_p3 t1_3
Index Cond: (a = t2_3.b)
-(20 rows)
+(24 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -283,10 +299,12 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a <
Hash Cond: (t2.b = t1.a)
-> Seq Scan on prt2_p2 t2
Filter: (b > 250)
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p2 t1
Filter: ((a < 450) AND (b = 0))
-(9 rows)
+(11 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -382,14 +400,18 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
Hash Cond: (t1_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = 0)
-> Hash Semi Join
Hash Cond: (t1_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Semi Join
@@ -399,7 +421,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
-> Materialize
-> Seq Scan on prt2_p3 t2_3
Filter: (a = 0)
-(24 rows)
+(28 rows)
SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -515,19 +537,25 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
-> Hash Join
Hash Cond: (t2_1.a = t3_1.b)
-> Seq Scan on prt1_p1 t2_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t3_1
-> Hash Join
Hash Cond: (t2_2.a = t3_2.b)
-> Seq Scan on prt1_p2 t2_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t3_2
-> Hash Join
Hash Cond: (t2_3.a = t3_3.b)
-> Seq Scan on prt1_p3 t2_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_p3 t3_3
-(26 rows)
+(32 rows)
SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
@@ -728,29 +756,41 @@ SELECT * FROM prt1 t1 JOIN prt1 t2 ON t1.a = t2.a WHERE t1.a IN (SELECT a FROM p
-> Hash Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t2_1
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p1 t3_1
-> Hash Semi Join
Hash Cond: (t1_2.a = t3_2.a)
-> Hash Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 3: keys=(a)
+ Bloom Filter 4: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p2 t2_2
-> Hash
+ Bloom Filter 4
-> Seq Scan on prt1_p2 t3_2
-> Hash Semi Join
Hash Cond: (t1_3.a = t3_3.a)
-> Hash Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
+ Bloom Filter 5: keys=(a)
+ Bloom Filter 6: keys=(a)
-> Hash
+ Bloom Filter 5
-> Seq Scan on prt1_p3 t2_3
-> Hash
+ Bloom Filter 6
-> Seq Scan on prt1_p3 t3_3
-(28 rows)
+(40 rows)
--
-- partitioned by expression
@@ -821,7 +861,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3_1
@@ -831,7 +873,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2
@@ -841,12 +885,14 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3
Index Cond: (((a + b) / 2) = t2_3.b)
-(33 rows)
+(39 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c | ?column? | c
@@ -871,7 +917,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
@@ -881,7 +929,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
@@ -891,10 +941,12 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(33 rows)
+(39 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
a | c | b | c | ?column? | c
@@ -924,7 +976,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_1.a = ((t3_1.a + t3_1.b) / 2))
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_e_p1 t3_1
Filter: (c = 0)
-> Index Scan using iprt2_p1_b on prt2_p1 t2_1
@@ -933,7 +987,9 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_2.a = ((t3_2.a + t3_2.b) / 2))
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_e_p2 t3_2
Filter: (c = 0)
-> Index Scan using iprt2_p2_b on prt2_p2 t2_2
@@ -942,12 +998,14 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_3.a = ((t3_3.a + t3_3.b) / 2))
-> Seq Scan on prt1_p3 t1_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_e_p3 t3_3
Filter: (c = 0)
-> Index Scan using iprt2_p3_b on prt2_p3 t2_3
Index Cond: (b = t1_3.a)
-(30 rows)
+(36 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
a | c | b | c | ?column? | c
@@ -1205,7 +1263,9 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_6.b = ((t1_9.a + t1_9.b) / 2))
-> Seq Scan on prt2_p1 t1_6
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_e_p1 t1_9
Filter: (c = 0)
-> Index Scan using iprt1_p1_a on prt1_p1 t1_3
@@ -1218,7 +1278,9 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_7.b = ((t1_10.a + t1_10.b) / 2))
-> Seq Scan on prt2_p2 t1_7
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_e_p2 t1_10
Filter: (c = 0)
-> Index Scan using iprt1_p2_a on prt1_p2 t1_4
@@ -1231,13 +1293,15 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2))
-> Seq Scan on prt2_p3 t1_8
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_e_p3 t1_11
Filter: (c = 0)
-> Index Scan using iprt1_p3_a on prt1_p3 t1_5
Index Cond: (a = t1_8.b)
Filter: (b = 0)
-(41 rows)
+(47 rows)
SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -1567,29 +1631,41 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, pl
-> Hash Join
Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on plt1_p1 t1_1
+ Bloom Filter 1: keys=(b, c)
+ Bloom Filter 2: keys=(c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt2_p1 t2_1
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_e_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on plt1_p2 t1_2
+ Bloom Filter 3: keys=(b, c)
+ Bloom Filter 4: keys=(c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt2_p2 t2_2
-> Hash
+ Bloom Filter 4
-> Seq Scan on plt1_e_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
-> Seq Scan on plt1_p3 t1_3
+ Bloom Filter 5: keys=(b, c)
+ Bloom Filter 6: keys=(c)
-> Hash
+ Bloom Filter 5
-> Seq Scan on plt2_p3 t2_3
-> Hash
+ Bloom Filter 6
-> Seq Scan on plt1_e_p3 t3_3
-(32 rows)
+(44 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
avg | avg | avg | c | c | c
@@ -1637,23 +1713,29 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1
-> Hash Join
Hash Cond: (t3_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t3_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
-> Hash Join
Hash Cond: (t3_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t3_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
-> Hash Join
Hash Cond: (t3_3.a = t2_3.b)
-> Seq Scan on prt1_p3 t3_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_p3 t2_3
-> Hash
-> Result
Replaces: Scan on prt1
One-Time Filter: false
-(22 rows)
+(28 rows)
EXPLAIN (COSTS OFF)
SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
@@ -1715,29 +1797,41 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, ph
-> Hash Join
Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on pht1_p1 t1_1
+ Bloom Filter 1: keys=(b, c)
+ Bloom Filter 2: keys=(c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pht2_p1 t2_1
-> Hash
+ Bloom Filter 2
-> Seq Scan on pht1_e_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on pht1_p2 t1_2
+ Bloom Filter 3: keys=(b, c)
+ Bloom Filter 4: keys=(c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pht2_p2 t2_2
-> Hash
+ Bloom Filter 4
-> Seq Scan on pht1_e_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
-> Seq Scan on pht1_p3 t1_3
+ Bloom Filter 5: keys=(b, c)
+ Bloom Filter 6: keys=(c)
-> Hash
+ Bloom Filter 5
-> Seq Scan on pht2_p3 t2_3
-> Hash
+ Bloom Filter 6
-> Seq Scan on pht1_e_p3 t3_3
-(32 rows)
+(44 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
avg | avg | avg | c | c | c
@@ -1767,22 +1861,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
-- test default partition behavior for list
ALTER TABLE plt1 DETACH PARTITION plt1_p3;
@@ -1803,22 +1903,28 @@ SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c
-> Hash Join
Hash Cond: (t2_1.c = t1_1.c)
-> Seq Scan on plt2_p1 t2_1
+ Bloom Filter 1: keys=(c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_p1 t1_1
Filter: ((a % 25) = 0)
-> Hash Join
Hash Cond: (t2_2.c = t1_2.c)
-> Seq Scan on plt2_p2 t2_2
+ Bloom Filter 2: keys=(c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_p2 t1_2
Filter: ((a % 25) = 0)
-> Hash Join
Hash Cond: (t2_3.c = t1_3.c)
-> Seq Scan on plt2_p3 t2_3
+ Bloom Filter 3: keys=(c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_p3 t1_3
Filter: ((a % 25) = 0)
-(23 rows)
+(29 rows)
--
-- multiple levels of partitioning
@@ -1854,7 +1960,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_l_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_l_p1 t1_1
Filter: (b = 0)
-> Hash Join
@@ -1876,7 +1984,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Hash
-> Seq Scan on prt1_l_p3_p1 t1_5
Filter: (b = 0)
-(28 rows)
+(30 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2355,19 +2463,25 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a
-> Hash Join
Hash Cond: (t1_1.a = t3_1.b)
-> Seq Scan on prt1_p1 t1_1
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.a = t3_2.b)
-> Seq Scan on prt1_p2 t1_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt2_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.a = t3_3.b)
-> Seq Scan on prt1_p3 t1_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_p3 t3_3
-(23 rows)
+(29 rows)
-- partitionwise join can not be applied if there are no equi-join conditions
-- between partition keys
@@ -2639,22 +2753,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2680,22 +2800,28 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2721,22 +2847,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2766,22 +2898,28 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2848,22 +2986,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2889,22 +3033,28 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2930,22 +3080,28 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3001,22 +3157,28 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(21 rows)
+(27 rows)
SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -3102,24 +3264,32 @@ SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2
-> Hash Right Join
Hash Cond: (t2_2.a = t1_2.b)
-> Seq Scan on prt1_adv_p2 t2_2
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Hash Join
Hash Cond: (t3_2.a = t1_2.b)
-> Seq Scan on prt1_adv_p2 t3_2
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt2_adv_p2 t1_2
Filter: (a = 0)
-> Hash Right Join
Hash Cond: (t2_3.a = t1_3.b)
-> Seq Scan on prt1_adv_p3 t2_3
+ Bloom Filter 4: keys=(a)
-> Hash
+ Bloom Filter 4
-> Hash Join
Hash Cond: (t3_3.a = t1_3.b)
-> Seq Scan on prt1_adv_p3 t3_3
+ Bloom Filter 3: keys=(a)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt2_adv_p3 t1_3
Filter: (a = 0)
-(31 rows)
+(39 rows)
SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a;
b | c | a | c | a | c
@@ -3289,16 +3459,20 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_2.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_1.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_1
Filter: (b = 0)
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3390,24 +3564,32 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2
-> Hash Right Join
Hash Cond: (t3_1.a = t1_1.a)
-> Seq Scan on prt3_adv_p1 t3_1
+ Bloom Filter 2: keys=(a)
-> Hash
+ Bloom Filter 2
-> Hash Right Join
Hash Cond: (t2_2.b = t1_1.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p2 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t3_2.a = t1_2.a)
-> Seq Scan on prt3_adv_p2 t3_2
+ Bloom Filter 4: keys=(a)
-> Hash
+ Bloom Filter 4
-> Hash Right Join
Hash Cond: (t2_1.b = t1_2.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 3: keys=(b)
-> Hash
+ Bloom Filter 3
-> Seq Scan on prt1_adv_p1 t1_2
Filter: (b = 0)
-(23 rows)
+(31 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a;
a | c | b | c | a | c
@@ -3449,16 +3631,20 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: ((a < 300) AND (b = 0))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: ((a < 300) AND (b = 0))
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3488,16 +3674,20 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3538,22 +3728,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3575,22 +3771,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3612,22 +3814,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3651,22 +3859,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3731,22 +3945,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3768,22 +3988,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3805,22 +4031,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3870,22 +4102,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4098,22 +4336,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4135,22 +4379,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4172,22 +4422,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4212,22 +4468,28 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4303,22 +4565,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4394,22 +4662,28 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(21 rows)
+(27 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4431,19 +4705,25 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-> Nested Loop Left Join
@@ -4451,7 +4731,7 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Seq Scan on plt1_adv_extra t1_4
Filter: (b < 10)
-> Seq Scan on plt2_adv_extra t2_4
-(26 rows)
+(32 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4525,31 +4805,43 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
-> Hash Right Join
Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
-> Seq Scan on plt1_adv_p1 t3_1
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
-> Seq Scan on plt1_adv_p2 t3_2
+ Bloom Filter 4: keys=(a, c)
-> Hash
+ Bloom Filter 4
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c))
-> Seq Scan on plt1_adv_p3 t3_3
+ Bloom Filter 6: keys=(a, c)
-> Hash
+ Bloom Filter 6
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
+ Bloom Filter 5: keys=(a, c)
-> Hash
+ Bloom Filter 5
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-> Nested Loop Left Join
@@ -4560,7 +4852,7 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
Filter: (b < 10)
-> Seq Scan on plt2_adv_extra t2_4
-> Seq Scan on plt1_adv_extra t3_4
-(41 rows)
+(53 rows)
SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c | a | c
@@ -4596,16 +4888,20 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4687,24 +4983,32 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
-> Hash Right Join
Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
-> Seq Scan on plt3_adv_p1 t3_1
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
-> Seq Scan on plt3_adv_p2 t3_2
+ Bloom Filter 4: keys=(a, c)
-> Hash
+ Bloom Filter 4
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1 t2_1
+ Bloom Filter 3: keys=(a, c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-(23 rows)
+(31 rows)
SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c | a | c
@@ -4733,16 +5037,20 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1_null t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4767,10 +5075,12 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p2 t2
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p2 t1
Filter: (b < 10)
-(8 rows)
+(10 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4809,16 +5119,20 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p3 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p3 t1_1
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p4 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p4 t1_2
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4837,10 +5151,12 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p4 t2
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p4 t1
Filter: ((c IS NULL) AND (b < 10))
-(8 rows)
+(10 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4862,16 +5178,20 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p3 t2_1
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p3 t1_1
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p4 t2_2
+ Bloom Filter 2: keys=(a, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on plt1_adv_p4 t1_2
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(15 rows)
+(19 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4890,10 +5210,12 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p4 t2
+ Bloom Filter 1: keys=(a, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on plt1_adv_p4 t1
Filter: ((c IS NULL) AND (b < 10))
-(8 rows)
+(10 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5015,12 +5337,16 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b))
-> Seq Scan on alpha_neg_p1 t1_1
Filter: ((b >= 125) AND (b < 225))
+ Bloom Filter 1: keys=(a, b)
-> Hash
+ Bloom Filter 1
-> Seq Scan on beta_neg_p1 t2_1
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-> Seq Scan on beta_neg_p2 t2_2
+ Bloom Filter 2: keys=(a, b)
-> Hash
+ Bloom Filter 2
-> Seq Scan on alpha_neg_p2 t1_2
Filter: ((b >= 125) AND (b < 225))
-> Hash Join
@@ -5037,7 +5363,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Filter: ((b >= 125) AND (b < 225))
-> Seq Scan on alpha_pos_p3 t1_6
Filter: ((b >= 125) AND (b < 225))
-(29 rows)
+(33 rows)
SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
a | b | c | a | b | c
@@ -5150,14 +5476,18 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on alpha_neg_p1 t1_1
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ Bloom Filter 1: keys=(a, b, c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on beta_neg_p1 t2_1
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-> Hash Join
Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on alpha_neg_p2 t1_2
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+ Bloom Filter 2: keys=(a, b, c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on beta_neg_p2 t2_2
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-> Nested Loop
@@ -5172,7 +5502,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
-> Seq Scan on beta_pos_p3 t2_4
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-(29 rows)
+(33 rows)
SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b;
a | b | c | a | b | c
@@ -5316,19 +5646,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM pht1 p1 JOIN pht1 p2 USING (c) LIMIT 1000;
-> Hash Join
Hash Cond: (p1_1.c = p2_1.c)
-> Seq Scan on pht1_p1 p1_1
+ Bloom Filter 1: keys=(c)
-> Hash
+ Bloom Filter 1
-> Seq Scan on pht1_p1 p2_1
-> Hash Join
Hash Cond: (p1_2.c = p2_2.c)
-> Seq Scan on pht1_p2 p1_2
+ Bloom Filter 2: keys=(c)
-> Hash
+ Bloom Filter 2
-> Seq Scan on pht1_p2 p2_2
-> Hash Join
Hash Cond: (p1_3.c = p2_3.c)
-> Seq Scan on pht1_p3 p1_3
+ Bloom Filter 3: keys=(c)
-> Hash
+ Bloom Filter 3
-> Seq Scan on pht1_p3 p2_3
-(17 rows)
+(23 rows)
RESET enable_mergejoin;
SET max_parallel_workers_per_gather = 1;
diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out
index feae77cb840..079f6422fdc 100644
--- a/src/test/regress/expected/predicate.out
+++ b/src/test/regress/expected/predicate.out
@@ -748,14 +748,16 @@ SELECT id FROM dist_tab WHERE row_nn IS DISTINCT FROM ROW(1, 5)::dist_row_t;
SET enable_nestloop TO off;
EXPLAIN (COSTS OFF)
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
- QUERY PLAN
---------------------------------------
+ QUERY PLAN
+---------------------------------------
Hash Join
Hash Cond: (t1.val_nn = t2.val_nn)
-> Seq Scan on dist_tab t1
+ Bloom Filter 1: keys=(val_nn)
-> Hash
+ Bloom Filter 1
-> Seq Scan on dist_tab t2
-(5 rows)
+(7 rows)
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
id | val_nn | val_null | row_nn | id | val_nn | val_null | row_nn
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index f6cc1a1029c..cb03966e79b 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -618,10 +618,12 @@ EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y
Hash Join
Hash Cond: (x.a = y.b)
-> Seq Scan on atest12 x
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on atest12 y
Filter: (abs(a) <<< 5)
-(6 rows)
+(8 rows)
-- clean up (regress_priv_user1's objects are all dropped later)
DROP FUNCTION leak2(integer, integer) CASCADE;
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index ca83d9fcc09..dc44871b682 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -719,8 +719,10 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
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
@@ -730,12 +732,14 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
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)
-> Hash
Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+ 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)
-(27 rows)
+(31 rows)
UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3;
@@ -793,12 +797,14 @@ 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)
-> Hash
Output: foo.f3, foo.f1, foo.f2, foo.f4, foo.ctid, foo.tableoid
+ Bloom Filter 1
-> Seq Scan on pg_temp.foo
Output: foo.f3, foo.f1, foo.f2, foo.f4, foo.ctid, foo.tableoid
Filter: (foo.f3 = 58)
-(12 rows)
+(14 rows)
UPDATE joinview SET f3 = f3 + 1, f4 = 7 WHERE f3 = 58
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3; -- should succeed
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 3a5e82c35bd..4d69d1cd84b 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1,6 +1,8 @@
--
-- Test of Row-level security feature
--
+-- disable bloom filter pushdown, to not interfere with calls to functions
+SET enable_hashjoin_bloom = off;
-- Clean up in case a prior regression run failed
-- Suppress NOTICE messages when users/groups don't exist
SET client_min_messages TO 'warning';
diff --git a/src/test/regress/expected/select_views.out b/src/test/regress/expected/select_views.out
index 1aeed8452bd..fe7be9891d5 100644
--- a/src/test/regress/expected/select_views.out
+++ b/src/test/regress/expected/select_views.out
@@ -2,6 +2,8 @@
-- SELECT_VIEWS
-- test the views defined in CREATE_VIEWS
--
+-- disable bloom filter pushdown, to not interfere with calls to functions
+SET enable_hashjoin_bloom = off;
SELECT * FROM street;
name | thepath | cname
------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 37070c1a896..11cf56fdba4 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3633,9 +3633,11 @@ SELECT * FROM sb_1 a, sb_2 b WHERE a.x = b.x AND a.y = b.y AND a.z = b.z;
Hash Join
Hash Cond: ((a.x = b.x) AND (a.y = b.y) AND (a.z = b.z))
-> Seq Scan on sb_1 a
+ Bloom Filter 1: keys=(x, y, z)
-> Hash
+ Bloom Filter 1
-> Seq Scan on sb_2 b
-(5 rows)
+(7 rows)
-- Check that the Hash Join bucket size estimator detects equal clauses correctly.
SET enable_nestloop = 'off';
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index a3778c23c34..5c7d49050db 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -406,9 +406,11 @@ select * from int4_tbl o where exists
Hash Semi Join
Hash Cond: (o.f1 = i.f1)
-> Seq Scan on int4_tbl o
+ Bloom Filter 1: keys=(f1)
-> Hash
+ Bloom Filter 1
-> Seq Scan on int4_tbl i
-(5 rows)
+(7 rows)
explain (costs off)
select * from int4_tbl o where not exists
@@ -783,14 +785,16 @@ order by t1.a, t2.a;
Hash Cond: (t2.a = (t3.b + 1))
-> Seq Scan on public.semijoin_unique_tbl t2
Output: t2.a, t2.b
+ Bloom Filter 1: keys=(t2.a)
-> Hash
Output: t3.a, t3.b
+ Bloom Filter 1
-> HashAggregate
Output: t3.a, t3.b
Group Key: (t3.a + 1), (t3.b + 1)
-> Seq Scan on public.semijoin_unique_tbl t3
Output: t3.a, t3.b, (t3.a + 1), (t3.b + 1)
-(24 rows)
+(26 rows)
-- encourage use of parallel plans
set parallel_setup_cost=0;
@@ -2176,11 +2180,13 @@ order by t1.ten;
Hash Cond: (t2.fivethous = t1.unique1)
-> Seq Scan on public.tenk1 t2
Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4
+ Bloom Filter 1: keys=(t2.fivethous)
-> Hash
Output: t1.ten, t1.unique1
+ Bloom Filter 1
-> Seq Scan on public.tenk1 t1
Output: t1.ten, t1.unique1
-(15 rows)
+(17 rows)
select t1.ten, sum(x) from
tenk1 t1 left join lateral (
@@ -2275,15 +2281,19 @@ order by 1, 2;
Hash Cond: (t2.q2 = t3.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q2)
+ Bloom Filter 2: keys=(t2.q2)
-> Hash
Output: t3.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t3
Output: t3.q2
-> Hash
Output: t1.q1, t1.q2
+ Bloom Filter 2
-> Seq Scan on public.int8_tbl t1
Output: t1.q1, t1.q2
-(19 rows)
+(23 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2326,14 +2336,16 @@ order by 1, 2;
Output: t2.q2, ((t2.q1 + 1))
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q2)
-> Seq Scan on public.int8_tbl t3
Output: t3.q2, (t2.q1 + 1)
Filter: (t2.q2 = t3.q2)
-> Hash
Output: t1.q1, t1.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q1, t1.q2
-(17 rows)
+(19 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2378,15 +2390,19 @@ order by 1, 2;
Hash Cond: (t2.q2 = t3.q1)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q2)
+ Bloom Filter 2: keys=(t2.q1)
-> Hash
Output: t3.q1
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t3
Output: t3.q1
-> Hash
Output: t1.q1
+ Bloom Filter 2
-> Seq Scan on public.int8_tbl t1
Output: t1.q1
-(19 rows)
+(23 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2439,14 +2455,16 @@ order by 1, 2;
Output: t2.q1, (t2.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q1)
-> Seq Scan on public.int8_tbl t3
Output: t3.q1, t2.q2
Filter: (t2.q2 = t3.q1)
-> Hash
Output: t1.q1
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q1
-(17 rows)
+(19 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2510,19 +2528,21 @@ order by 1, 2, 3;
Hash Cond: (t2.q1 = t3.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
+ Bloom Filter 1: keys=(t2.q2)
-> Hash
Output: t3.q2, (COALESCE(t3.q1, t3.q1))
-> Seq Scan on public.int8_tbl t3
Output: t3.q2, COALESCE(t3.q1, t3.q1)
-> Hash
Output: t4.q1, t4.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t4
Output: t4.q1, t4.q2
-> Hash
Output: t1.q2
-> Seq Scan on public.int8_tbl t1
Output: t1.q2
-(26 rows)
+(28 rows)
select ss2.* from
int8_tbl t1 left join
@@ -2590,11 +2610,13 @@ order by 1, 2, 3;
Output: t3.q2, COALESCE(t3.q1, t3.q1)
-> Seq Scan on public.int8_tbl t4
Output: t4.q1, t4.q2, (COALESCE(t3.q1, t3.q1))
+ Bloom Filter 1: keys=(t4.q1)
-> Hash
Output: t1.q2
+ Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q2
-(24 rows)
+(26 rows)
select ss2.* from
int8_tbl t1 left join
@@ -2895,11 +2917,13 @@ select * from tenk1 A where hundred in (select hundred from tenk2 B where B.odd
Hash Join
Hash Cond: ((a.odd = b.odd) AND (a.hundred = b.hundred))
-> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(odd, hundred)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: b.odd, b.hundred
-> Seq Scan on tenk2 b
-(7 rows)
+(9 rows)
explain (costs off)
select * from tenk1 A where exists
@@ -2964,11 +2988,13 @@ ON B.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = b.odd);
-> Hash Join
Hash Cond: ((b.odd = c.odd) AND (b.hundred = c.hundred))
-> Seq Scan on tenk2 b
+ Bloom Filter 1: keys=(odd, hundred)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: c.odd, c.hundred
-> Seq Scan on tenk2 c
-(10 rows)
+(12 rows)
-- we can pull up the sublink into the inner JoinExpr.
explain (costs off)
@@ -2983,13 +3009,15 @@ WHERE a.thousand < 750;
Hash Cond: (a.hundred = c.hundred)
-> Seq Scan on tenk1 a
Filter: (thousand < 750)
+ Bloom Filter 1: keys=(hundred)
-> Hash
+ Bloom Filter 1
-> HashAggregate
Group Key: c.odd, c.hundred
-> Seq Scan on tenk2 c
-> Hash
-> Seq Scan on tenk2 b
-(12 rows)
+(14 rows)
-- we can pull up the aggregate sublink into RHS of a left join.
explain (costs off)
@@ -3126,9 +3154,11 @@ WHERE a.ten IN (VALUES (1), (2));
Hash Cond: (a.ten = c.ten)
-> Seq Scan on onek a
Filter: (ten = ANY ('{1,2}'::integer[]))
+ Bloom Filter 1: keys=(ten)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 c
-(6 rows)
+(8 rows)
EXPLAIN (COSTS OFF)
SELECT c.unique1,c.ten FROM tenk1 c JOIN onek a USING (ten)
@@ -3139,9 +3169,11 @@ WHERE c.ten IN (VALUES (1), (2));
Hash Cond: (c.ten = a.ten)
-> Seq Scan on tenk1 c
Filter: (ten = ANY ('{1,2}'::integer[]))
+ Bloom Filter 1: keys=(ten)
-> Hash
+ Bloom Filter 1
-> Seq Scan on onek a
-(6 rows)
+(8 rows)
-- Constant expressions are simplified
EXPLAIN (COSTS OFF)
@@ -3311,9 +3343,11 @@ SELECT * FROM onek t1, lateral (SELECT * FROM onek t2 WHERE t2.ten IN (values (t
-> Hash Semi Join
Hash Cond: (t2.ten = "*VALUES*".column1)
-> Seq Scan on onek t2
+ Bloom Filter 1: keys=(ten)
-> Hash
+ Bloom Filter 1
-> Values Scan on "*VALUES*"
-(7 rows)
+(9 rows)
-- VtA causes the whole expression to be evaluated as a constant
EXPLAIN (COSTS OFF)
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 132b56a5864..a796e431415 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -163,6 +163,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_group_by_reordering | on
enable_hashagg | on
enable_hashjoin | on
+ enable_hashjoin_bloom | on
enable_incremental_sort | on
enable_indexonlyscan | on
enable_indexscan | on
@@ -180,7 +181,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(25 rows)
+(26 rows)
-- There are always wait event descriptions for various types. InjectionPoint
-- may be present or absent, depending on history since last postmaster start.
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 7b00c742776..7d4af80faf6 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -587,11 +587,13 @@ MERGE INTO rw_view1 t
Hash Cond: (base_tbl.a = generate_series.generate_series)
-> Bitmap Heap Scan on base_tbl
Recheck Cond: (a > 0)
+ Bloom Filter 1: keys=(a)
-> Bitmap Index Scan on base_tbl_pkey
Index Cond: (a > 0)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series
-(9 rows)
+(11 rows)
EXPLAIN (costs off)
MERGE INTO rw_view1 t
@@ -621,11 +623,13 @@ MERGE INTO rw_view1 t
Hash Cond: (base_tbl.a = generate_series.generate_series)
-> Bitmap Heap Scan on base_tbl
Recheck Cond: (a > 0)
+ Bloom Filter 1: keys=(a)
-> Bitmap Index Scan on base_tbl_pkey
Index Cond: (a > 0)
-> Hash
+ Bloom Filter 1
-> Function Scan on generate_series
-(9 rows)
+(11 rows)
-- it's still updatable if we add a DO ALSO rule
CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
@@ -2777,12 +2781,14 @@ EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
-> Hash Join
Hash Cond: (b.a = r.a)
-> Seq Scan on base_tbl b
+ Bloom Filter 1: keys=(a)
-> Hash
+ Bloom Filter 1
-> Seq Scan on ref_tbl r
SubPlan exists_1
-> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
Index Cond: (a = b.a)
-(9 rows)
+(11 rows)
DROP TABLE base_tbl, ref_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 90d9f953b81..5c86619f023 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -5474,10 +5474,12 @@ LIMIT 1;
-> Hash Join
Hash Cond: (t1.unique1 = t2.tenthous)
-> Index Only Scan using tenk1_unique1 on tenk1 t1
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> Seq Scan on tenk1 t2
Filter: (two = 1)
-(9 rows)
+(11 rows)
-- Ensure we get a cheap total plan. This time use UNBOUNDED FOLLOWING, which
-- needs to read all join rows to output the first WindowAgg row.
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 77ded01b046..db8c77721e7 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -686,9 +686,11 @@ select count(*) from tenk1 a
-> Hash Semi Join
Hash Cond: (a.unique1 = x.unique1)
-> Index Only Scan using tenk1_unique1 on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
+ Bloom Filter 1
-> CTE Scan on x
-(8 rows)
+(10 rows)
explain (costs off)
with x as materialized (insert into tenk1 default values returning unique1)
@@ -3246,8 +3248,10 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
Hash Cond: (m.k = o.k)
-> Seq Scan on public.m
Output: m.ctid, m.k
+ Bloom Filter 1: keys=(m.k)
-> Hash
Output: o.k, o.v, o.*
+ Bloom Filter 1
-> Subquery Scan on o
Output: o.k, o.v, o.*
-> Result
@@ -3258,7 +3262,7 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
-> CTE Scan on cte_basic
Output: (cte_basic.b || ' merge update'::text)
Filter: (cte_basic.a = m.k)
-(21 rows)
+(23 rows)
-- InitPlan
WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b)
@@ -3295,13 +3299,15 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
Hash Cond: (m.k = o.k)
-> Seq Scan on public.m
Output: m.ctid, m.k
+ Bloom Filter 1: keys=(m.k)
-> Hash
Output: o.k, o.v, o.*
+ Bloom Filter 1
-> Subquery Scan on o
Output: o.k, o.v, o.*
-> Result
Output: 1, 'merge source InitPlan'::text
-(21 rows)
+(23 rows)
-- MERGE source comes from CTE:
WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b)
@@ -3339,11 +3345,13 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text
Hash Cond: (m.k = merge_source_cte.a)
-> Seq Scan on public.m
Output: m.ctid, m.k
+ Bloom Filter 1: keys=(m.k)
-> Hash
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
+ Bloom Filter 1
-> CTE Scan on merge_source_cte
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
-(20 rows)
+(22 rows)
DROP TABLE m;
-- check that run to completion happens in proper ordering
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index 6b3566271df..390e79b04c3 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -2,6 +2,9 @@
-- Test of Row-level security feature
--
+-- disable bloom filter pushdown, to not interfere with calls to functions
+SET enable_hashjoin_bloom = off;
+
-- Clean up in case a prior regression run failed
-- Suppress NOTICE messages when users/groups don't exist
diff --git a/src/test/regress/sql/select_views.sql b/src/test/regress/sql/select_views.sql
index e742f136990..09f96b0b1ae 100644
--- a/src/test/regress/sql/select_views.sql
+++ b/src/test/regress/sql/select_views.sql
@@ -3,6 +3,9 @@
-- test the views defined in CREATE_VIEWS
--
+-- disable bloom filter pushdown, to not interfere with calls to functions
+SET enable_hashjoin_bloom = off;
+
SELECT * FROM street;
SELECT name, #thepath FROM iexit ORDER BY name COLLATE "C", 2;
--
2.50.1 (Apple Git-155)
[text/plain] v4-0002-PoC-Bloom-filter-pushdown-during-path-constructio.patch (291.2K, ../[email protected]/3-v4-0002-PoC-Bloom-filter-pushdown-during-path-constructio.patch)
download | inline diff:
From 6d4a45b3ff5bb25f8a14a1c2c3e34a92994a25a3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <[email protected]>
Date: Sat, 20 Jun 2026 20:20:08 +0000
Subject: [PATCH v4 2/4] PoC: Bloom filter pushdown during path construction
We can't decide which filters to push down during plan creation, it's
too late. At that point we've already created paths with expected
cardinalities, calculated the costs, etc.
If we inject new filters to the scan nodes, the row counts won't match
and it'll be confusing (effectively impossible to decide if a difference
is a misestimate or just do to a pushed-down filter).
It also means we can't consider the effect during the "bottom-up" phase,
e.g. when picking algorithms for the other joins, etc. We would likely
improve the current plan, but it prevents us from picking an alternative
cheaper plan.
To address this, add "expected filters" as a feature of a path, and
propage them during joins etc. This is similar concept to path keys,
tracking "interesting" orderings for a path.
At the scan level, figure out a list of "interesting" filters. This is
done by inspecting the joins the scanned relation participates in, and
picking a limited number of sufficiently selective filters, and then it
generates paths expecting different combinations of filters.
The number of combinations grows with 2^n, so with 3 filters we get 9
paths (8 new ones), with 4 it's 16, etc. And this is for each scan node.
For a query with many joins, it can grow pretty quick.
So we need to be conservative, in order to prevent an explosion of the
number of paths. The patch simply picks a limited number of the most
selective filters (e.g. 3 filters, each eliminating at least 30%
tuples). We could refine how this is decided, of course. For example,
CustomScan could/should have a say in the costing, somehow.
At the join level, we need to consider if a join is matching a filter
expected by the input paths or not.
* A join "matches" a filter of an input path, if it's the join from
which the filter was derived.
* A join can "satisfy" a filter expected by the outer innput path, if it
matches it, and if it's a hash join. A join can never satisfy filters
expected by the inner input.
* A join has to satisfy all filters matching the inner/outer path.
This implies that:
* NestLoop/MergeJoin can never satisfy any filters. The places creating
these joins have to ignore all paths with such filters. But these
joins can still propagate all other filters, in case one of the later
joins happens to be a hashjoin satisfying them.
* HashJois can satisfy filters expected by outer input path, or
propagate filters not matching the join.
Notes:
* We could also consider the "remaining row count" after a filter, and
only consider filters if it's above some limit. E.g. it there's less
than 1000 tuples, there's little point in pushing down additional
filters. This would help with reducing the impact of path explosion.
* When figuring out interesting filters, we need to be careful about
outer joins. Outer joins are not a good filter source, because we
can't eliminate any rows even if the join clause is very selective.
* We still do the pushdown when creating the plan, but it's very
mechanical - all the decisions have been made earlier. It's not
possible (allowed) to mismatch - we can't pushdown a filter not
expected by a scan, and the scan can't expect a filter that has not
been pushdown by the plan.
---
src/backend/commands/explain.c | 49 +-
src/backend/executor/nodeHashjoin.c | 19 +-
src/backend/optimizer/path/allpaths.c | 463 ++++++++++++++++
src/backend/optimizer/path/costsize.c | 17 +
src/backend/optimizer/path/equivclass.c | 179 ++++++
src/backend/optimizer/path/joinpath.c | 524 +++++++++++++++---
src/backend/optimizer/plan/createplan.c | 257 ++++-----
src/backend/optimizer/util/pathnode.c | 158 ++++++
src/backend/utils/misc/guc_parameters.dat | 20 +
src/backend/utils/misc/postgresql.conf.sample | 2 +
src/include/nodes/pathnodes.h | 66 +++
src/include/optimizer/cost.h | 2 +
src/include/optimizer/pathnode.h | 5 +
src/include/optimizer/paths.h | 6 +
src/test/regress/expected/eager_aggregate.out | 178 ++----
src/test/regress/expected/graph_table.out | 14 +-
src/test/regress/expected/join.out | 425 +++++++-------
src/test/regress/expected/join_hash.out | 16 +-
src/test/regress/expected/merge.out | 106 ++--
src/test/regress/expected/misc_functions.out | 14 +-
.../regress/expected/partition_aggregate.out | 34 +-
src/test/regress/expected/partition_join.out | 452 ++-------------
src/test/regress/expected/predicate.out | 8 +-
src/test/regress/expected/returning.out | 42 +-
src/test/regress/expected/stats_ext.out | 23 +-
src/test/regress/expected/subselect.out | 83 +--
src/test/regress/expected/updatable_views.out | 19 +-
src/test/regress/expected/window.out | 15 +-
src/test/regress/expected/with.out | 112 ++--
29 files changed, 2004 insertions(+), 1304 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8893e36c8aa..0ba1319d79f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2589,19 +2589,34 @@ show_upper_qual(List *qual, const char *qlabel,
/*
* show_bloom_filter_info
- * Show info about every bloom filter pushed down to a scan node.
+ * Show info about every Bloom filter pushed down to a scan node.
*
* In TEXT format each filter is rendered on a single line, e.g.
*
- * Bloom Filter N: (a, b) producer=3 checked=99999 rejected=99990
+ * Bloom Filter N: keys=(a, b) checked=99999 rejected=99990
*
- * The checked/rejected fields are omitted outside of ANALYZE). In
- * structured formats we emit a group per filter with the same fields
- * broken out as properties.
+ * The checked/rejected fields are omitted outside of ANALYZE). In structured
+ * formats we emit a group per filter with the same fields broken out as
+ * properties.
*
- * Called from the per-recipient cases in ExplainNode; the recipient is
- * expected to be a scan node in the current PoC, but nothing here
- * depends on that.
+ * Called from the per-recipient cases in ExplainNode; the recipient is expected
+ * to be a scan node, but nothing here depends on that. We may choose to push
+ * filters to other nodes in the plan.
+ *
+ * This only prints information about the keys, and the checked/rejected counts.
+ * Detailed information about the filter itself (number of bits, number of hash
+ * functions, ...) is available on the producer side.
+ *
+ * XXX It might be a good idea to show the expected filter selectivity too,
+ * not just the one actually observed during execution. That'd make it easier
+ * to reason about the decisions and review the plan changes.
+ *
+ * XXX If we choose other filter types, we'd need some general way to show the
+ * information. I don't know if we should have a "generic" information provided
+ * by all the filters, or if we would need a way to print custom information.
+ * Chances are we'd have a limited number of supported filter types, in which
+ * case we can have a show_ function for each type. Only if users could inject
+ * arbitrary filters, that'd be an issue. But that seems unlikely.
*/
static void
show_bloom_filter_info(PlanState *planstate, List *ancestors,
@@ -3599,9 +3614,21 @@ show_hash_info(HashState *hashstate, ExplainState *es)
}
/*
- * Show infromation about the bloom filter produced by this Hash node
- * (if any). For plain EXPLAIN, the filter is not initialized / built,
- * but we still show available plan-time metadata (at least the ID).
+ * Show infromation about the Bloom filter produced by this Hash node (if
+ * any). For plain EXPLAIN, the filter is not initialized / built, but we
+ * still show available plan-time metadata (at least the ID).
+ *
+ * Once the filter is built, we show the various filter parameters (number
+ * of bits, hash functions, ...). Note that even with EXPLAIN ANALYZE the
+ * filter may not be built, due to the hashjoin delaying the Hash build
+ * until on the first outer tuple.
+ *
+ * XXX We don't show the keys, because those are always the join keys.
+ *
+ * XXX I think it makes sense to show the checked/rejected counters both
+ * here and for the consumer. If/when we allow multiple consumers for the
+ * filter, then this would show the "summary" while the scan node would
+ * show just counters for that one consumer.
*/
if (hashstate->bloom_filter_id > 0)
{
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 8fa7af4cfef..e3467f14739 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -2019,15 +2019,16 @@ ExecHashJoinInitializeWorker(HashJoinState *state,
* BLOOM FILTER PUSHDOWN
*
* The pushdown decision is done in try_push_bloom_filter, when constructing
- * the plan from the selected paths (see createplan.c). It decides which scan
- * node should receive the bloom filter (if any), and what expressions it
- * should use to calculate the hash value.
+ * the plan from the selected paths. The input paths track filters "expected"
+ * by scan nodes included in that path (if any). The planner then ensures all
+ * expected filters are either satisfied (by matching joins) or propagated up.
+ * In a valid plan all expected filters are satisfied.
*
* Then at execution time:
*
* - ExecInitHashJoin registers itself in EState.es_bloom_producers
* before recursing into child plans, so by the time a recipient's
- * ExecInit runs, the producer is already discoverable by plan_node_id.
+ * ExecInit runs, the producer is already discoverable by filter ID.
* This registration only happens when there's at least one consumer.
* It also sets want_bloom_filter for the Hash node.
*
@@ -2035,25 +2036,25 @@ ExecHashJoinInitializeWorker(HashJoinState *state,
* when HashState.want_bloom_filter is set (so no work happens when
* nobody will probe).
*
- * - Nodes with non-NIL plan->bloom_filters (and supporting bloom
+ * - Nodes with non-NIL plan->bloom_filters (and supporting Bloom
* filters) call ExecInitBloomFilters() during its own ExecInit,
* which looks up the producer node (in the EState), compiles
* ExprStates for the hash expressions, etc. The filter state
* (BloomFilterState) gets added to ps->bloom_filters (a node may
- * have multiple bloom filters from different hash joins).
+ * have multiple Bloom filters from different hash joins).
*
* - The per-tuple loop of the scan node calls ExecBloomFilters() (much
* like ExecQual) to test the tuple against every attached filter,
* dropping it on the first filter that excludes it. For scan nodes
* this call happens in ExecScanExtended.
*
- * The scan nodes reach the bloom filter via the HashJoinState pointer
+ * The scan nodes reach the Bloom filter via the HashJoinState pointer
* added to EState.es_bloom_producers, so that the rescans etc. (filter
* freed + recreated when the hash table is destroyed and rebuilt) are
- * transparent to the consumer. The bloom filter gets reallocated after
+ * transparent to the consumer. The Bloom filter gets reallocated after
* a rescan, so the pointer to it may change.
*
- * XXX It's possible the bloom filter gets pushed down to a node that
+ * XXX It's possible the Bloom filter gets pushed down to a node that
* fails to initialize/use it. It'll be added to the bloom_filters list,
* but if the node does not call ExecInitBloomFilters, the filter will
* be unused.
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c134594a21a..8829c7c3108 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -107,6 +107,9 @@ static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
+static List *find_interesting_bloom_filters(PlannerInfo *root,
+ RelOptInfo *rel);
+static void generate_expected_filter_paths(PlannerInfo *root, RelOptInfo *rel);
static void set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -603,6 +606,16 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
!bms_equal(rel->relids, root->all_query_rels))
generate_useful_gather_paths(root, rel, false);
+ /*
+ * For plain base relations, consider generating additional scan paths
+ * that anticipate a Bloom filter being pushed down from a hash join above
+ * (see find_interesting_bloom_filters). These paths have reduced row
+ * estimates and are consumed by join path generation.
+ */
+ if (rel->reloptkind == RELOPT_BASEREL &&
+ rte->rtekind == RTE_RELATION)
+ generate_expected_filter_paths(root, rel);
+
/* Now find the cheapest of the paths for this rel */
set_cheapest(rel);
@@ -884,6 +897,456 @@ create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
}
+/*
+ * bloom_em_matches_anybarevar
+ * ec_matches_callback used by find_interesting_bloom_filters: accept any
+ * EquivalenceClass member that is a bare Var of the target relation. The
+ * Bloom filter pushdown logic (createplan.c) only supports recipients whose
+ * join keys are bare Vars, so we mirror that requirement here.
+ */
+static bool
+bloom_em_matches_anybarevar(PlannerInfo *root, RelOptInfo *rel,
+ EquivalenceClass *ec, EquivalenceMember *em,
+ void *arg)
+{
+ Var *var;
+
+ /* We're looking only for bare Var expressions. */
+ if (!IsA(em->em_expr, Var))
+ return false;
+
+ /*
+ * Is the Var referencing a normal (non-system) attribute in the relation
+ * we're processing (generating scans for)?
+ *
+ * FIXME Can we have (varlevelsup != 0) for baserels? I don't think we can
+ * have outer referecenses in that place.
+ */
+ var = (Var *) em->em_expr;
+ if (var->varno != rel->relid ||
+ var->varattno <= 0 ||
+ var->varlevelsup != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * bloom_filter_recipient_reachable
+ * Check that a Bloom filter owned by owner_relid and built from
+ * build_relids could actually be pushed to the owner's scan.
+ *
+ * A pushed-down filter removes owner tuples that have no match on the build
+ * side, so it can only be applied where the join that realizes it drops such
+ * unmatched owner (probe) tuples. If an outer join null-extends the owner
+ * before it can be joined to the build side, the filter would change the
+ * result and is therefore unusable: at plan time find_bloom_filter_recipient
+ * would refuse to descend into that side and find no recipient (see also
+ * bloom_join_side_preserved, which is checking for this situation).
+ *
+ * Picking such a filter only to throw it away later wastes planner effort,
+ * and we might also ignore some other filters because of that. It's better
+ * to eliminate it right away.
+ *
+ * This is primarily an optimization - we don't want to generate paths that
+ * would ultimately be useless, and possibly not generating paths for other
+ * filters. The correctness is still guaranteed by the propagation logic in
+ * compute_join_expected_filters(), which rejects cases that would carry a
+ * filter across a non-preserved join side. That guarantees we don't pick a
+ * plan with such filters, only to find about the issue in createplan.c.
+ */
+static bool
+bloom_filter_recipient_reachable(PlannerInfo *root, Index owner_relid,
+ Relids build_relids)
+{
+ ListCell *lc;
+
+ foreach(lc, root->join_info_list)
+ {
+ SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
+
+ switch (sjinfo->jointype)
+ {
+ case JOIN_LEFT:
+ case JOIN_ANTI:
+
+ /*
+ * The syntactic RHS is null-extended. If the owner is on it
+ * but the build side is reached from the preserved LHS, the
+ * owner must cross this outer join on its nullable side.
+ */
+ if (bms_is_member(owner_relid, sjinfo->syn_righthand) &&
+ !bms_is_subset(build_relids, sjinfo->syn_righthand))
+ return false;
+ break;
+ case JOIN_FULL:
+
+ /*
+ * Both sides are null-extended, so the filter is unusable
+ * whenever the owner and the build side sit on opposite sides
+ * of the join.
+ */
+ if (bms_is_member(owner_relid, sjinfo->syn_lefthand) &&
+ !bms_is_subset(build_relids, sjinfo->syn_lefthand))
+ return false;
+ if (bms_is_member(owner_relid, sjinfo->syn_righthand) &&
+ !bms_is_subset(build_relids, sjinfo->syn_righthand))
+ return false;
+ break;
+ default:
+ /* INNER and SEMI joins never null-extend the owner. */
+ break;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * find_interesting_bloom_filters
+ * Identify Bloom filters that a hash join above this scan could push down,
+ * and that are selective enough to be worth costing for.
+ *
+ * We look for hashjoinable equality join clauses where this rel's side is a
+ * bare Var, derived both from EquivalenceClasses and from non-EC joininfo
+ * clauses. Clauses are grouped by the relids on the other ("build") side of
+ * the join; each group becomes a candidate filter whose expected surviving
+ * fraction is estimated as the semijoin selectivity of those clauses.
+ *
+ * A candidate is "interesting" only if it is expected to eliminate at least
+ * bloom_filter_pushdown_threshold of the rel's tuples. We keep at most
+ * bloom_filter_pushdown_max of the most selective candidates, and return them
+ * as a list of ExpectedFilter nodes.
+ *
+ * XXX This needs to be careful to not interfere with the general selectivity
+ * estimation, performed by clauselist_selectivity(). We'll estimate the filter
+ * selectivity using a made-up sjinfo with JOIN_INNER, which may not match
+ * the actual join. The selectivities must not leak - this is why this function
+ * does not collect the RestrictInfos but only the clauses. If we used the
+ * RestrictInfos, the clauselist_selectivity would cache the incorrect result
+ * in them, and it'd affect the planning in weird ways.
+ *
+ * FIXME Maybe there's a better way to calculate the filter selectivity?
+ */
+static List *
+find_interesting_bloom_filters(PlannerInfo *root, RelOptInfo *rel)
+{
+ List *candidates;
+ List *group_relids = NIL; /* parallel: Relids per group */
+ List *group_clauses = NIL; /* parallel: List of clauses */
+ List *result = NIL;
+ ListCell *lc;
+
+ if (!enable_hashjoin_bloom)
+ return NIL;
+
+ if (bloom_filter_pushdown_max <= 0)
+ return NIL;
+
+ if (rel->reloptkind != RELOPT_BASEREL)
+ return NIL;
+
+ /* Collect candidate hashjoinable equality clauses for this rel. */
+ candidates = generate_implied_equalities_for_all_columns(root, rel,
+ bloom_em_matches_anybarevar,
+ NULL, NULL);
+
+ foreach(lc, rel->joininfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ /* EC-derived clauses are already covered above. */
+ if (rinfo->parent_ec != NULL)
+ continue;
+
+ candidates = lappend(candidates, rinfo->clause);
+ }
+
+ /* Group candidate clauses by their build-side relids. */
+ foreach(lc, candidates)
+ {
+ Node *clause = (Node *) lfirst(lc);
+ Node *left;
+ Node *right;
+ Node *ownerexpr;
+ Oid opno;
+ Relids clause_relids;
+ Relids build_relids;
+ int buildrel;
+ ListCell *lc2;
+ ListCell *lc3;
+ bool found;
+
+ /* strip RestrictInfo (see comment above) */
+ if (IsA(clause, RestrictInfo))
+ clause = (Node *) ((RestrictInfo *) clause)->clause;
+
+ /*
+ * Only care about (Expr op Expr) clauses. We know one side has to be
+ * a bare Var node, from the "owner" side (which is the scan node).
+ * The other side can be arbitrary expression on the other relation.
+ */
+ if (!is_opclause(clause) ||
+ list_length(((OpExpr *) clause)->args) != 2)
+ continue;
+
+ opno = ((OpExpr *) clause)->opno;
+ left = get_leftop(clause);
+ right = get_rightop(clause);
+
+ /* Identify which side is a bare Var of this rel (the owner side). */
+ /* XXX replace this with a macro shared with bloom_em_matches_anybarevar */
+ if (IsA(left, Var) && ((Var *) left)->varno == rel->relid &&
+ ((Var *) left)->varattno > 0 && ((Var *) left)->varlevelsup == 0)
+ ownerexpr = left;
+ else if (IsA(right, Var) && ((Var *) right)->varno == rel->relid &&
+ ((Var *) right)->varattno > 0 &&
+ ((Var *) right)->varlevelsup == 0)
+ ownerexpr = right;
+ else
+ continue;
+
+ /* Operator must be hashjoinable on the owner's input type. */
+ if (!op_hashjoinable(opno, exprType(ownerexpr)))
+ continue;
+
+ /*
+ * The build side must be a single base relation; that's what the
+ * recipient lookup and our selectivity estimate can handle.
+ *
+ * XXX I don't think this restriction is necessary. We can allow the
+ * build side to be a join. I don't see why that would be a problem.
+ */
+ clause_relids = pull_varnos(root, (Node *) clause);
+ build_relids = bms_difference(clause_relids, rel->relids);
+ if (!bms_get_singleton_member(build_relids, &buildrel) ||
+ buildrel >= root->simple_rel_array_size ||
+ root->simple_rel_array[buildrel] == NULL ||
+ root->simple_rel_array[buildrel]->reloptkind != RELOPT_BASEREL)
+ {
+ bms_free(build_relids);
+ continue;
+ }
+
+ /* Add to an existing group, or start a new one. */
+ /* XXX Maybe we sould have a HTAB with the relids as a key? But the
+ * lists should not be that long, I think. */
+ found = false;
+ forboth(lc2, group_relids, lc3, group_clauses)
+ {
+ Relids grelids = (Relids) lfirst(lc2);
+
+ if (bms_equal(grelids, build_relids))
+ {
+ lfirst(lc3) = lappend((List *) lfirst(lc3), clause);
+ found = true;
+
+ /* added to an existing group, don't keep the relids around */
+ bms_free(build_relids);
+
+ break;
+ }
+ }
+
+ if (!found) /* new group */
+ {
+ group_relids = lappend(group_relids, build_relids);
+ group_clauses = lappend(group_clauses, list_make1(clause));
+ }
+ }
+
+ /*
+ * We have collected all potentially intresting filters. Evaluate selectivity
+ * of each group and keep only the most interesting filters. Filters have to
+ * eliminate at least bloom_filter_pushdown_threshold tuples, and we keep
+ * only bloom_filter_pushdown_max most selective ones.
+ */
+ {
+ ListCell *lcr = list_head(group_relids);
+ ListCell *lcc = list_head(group_clauses);
+
+ while (lcr != NULL && lcc != NULL)
+ {
+ Relids build_relids = (Relids) lfirst(lcr);
+ List *clauses = (List *) lfirst(lcc);
+ SpecialJoinInfo sjinfo;
+ Selectivity sel;
+
+ init_dummy_sjinfo(&sjinfo, rel->relids, build_relids);
+ sjinfo.jointype = JOIN_SEMI;
+
+ sel = clauselist_selectivity(root, clauses, 0, JOIN_SEMI, &sjinfo);
+
+ if ((sel <= 1.0 - bloom_filter_pushdown_threshold) &&
+ (sel > 0.0) && /* XXX seems unnecessary */
+ bloom_filter_recipient_reachable(root, rel->relid, build_relids))
+ {
+ ExpectedFilter *f = makeNode(ExpectedFilter);
+
+ f->owner_relid = rel->relid;
+ f->build_relids = build_relids;
+ f->clauses = clauses;
+ f->selectivity = sel;
+ result = lappend(result, f);
+ }
+
+ lcr = lnext(group_relids, lcr);
+ lcc = lnext(group_clauses, lcc);
+ }
+ }
+
+ /*
+ * We only connsider a limited number of interesting filters, to prevent
+ * path explosion. If we found too many, keep only the most selective ones
+ * (with smallest surviving fraction of tuples), to bound the number of
+ * generated paths.
+ *
+ * XXX This also aligns with good join orders - those tend to perform the
+ * most selective joins first. So we get to build the filters soon, even
+ * if the hashjoin optimization is not disabled.
+ */
+ while (list_length(result) > bloom_filter_pushdown_max)
+ {
+ ExpectedFilter *worst = NULL;
+ ListCell *lcw;
+
+ foreach(lcw, result)
+ {
+ ExpectedFilter *f = (ExpectedFilter *) lfirst(lcw);
+
+ if (worst == NULL || f->selectivity > worst->selectivity)
+ worst = f;
+ }
+ result = list_delete_ptr(result, worst);
+ }
+
+ return result;
+}
+
+/*
+ * generate_expected_filter_paths
+ * Generate additional scan paths that anticipate one or more pushed-down
+ * Bloom filters.
+ *
+ * For each non-empty subset of the interesting filters, we clone every eligible
+ * existing scan path, reducing its row estimate by the combined selectivity and
+ * attaching the corresponding ExpectedFilter nodes.
+ *
+ * These paths are kept alongside the regular paths (add_path keeps paths with
+ * differing expected_filters) and are consumed by join path generation;
+ * set_cheapest never selects them.
+ *
+ * XXX We must not clone paths that already have expected filters.
+ *
+ * XXX The cloning is a rather dirty way to copy paths. It does not readjust the
+ * cost in a reasonable way. For example custom scans could do something smart
+ * with the filters, so it should have a chance to deal with that. A cleaner
+ * solution might be to actually pass the filters to the various "create"
+ * function, like create_seqscan_path/... For CustomScan nodes we can probably
+ * do most of this in the set_rel_pathlist_hook, somewhere. Maybe that needs
+ * some helper methods, though. And maybe it will need to pass some of the info
+ * through the callbacks? Not sure, someone has to try that.
+ *
+ * XXX This may need some major changes to work with custom scans. Right now we
+ * only consider filters exactly matching the hash keys, so if the hashjoin is
+ * on (t1.a = t2.a AND t1.b = t2.b), then the filter will be on (a,b). But a
+ * custom scan may prefer "split" filters on each column independently. We'd
+ * need a way for the custom scan to indicate that, and we'd need to apply this
+ * only to the "matching" scan paths (and not to any other scan paths). But
+ * we only look at the paths after selecting the "interesting" filters, so we'd
+ * need to rethink that - we'd need to make the "interesting" filters specific
+ * to a path, or something like that.
+ */
+static void
+generate_expected_filter_paths(PlannerInfo *root, RelOptInfo *rel)
+{
+ List *filters;
+ List *basepaths = NIL;
+ int nfilters;
+ uint32 combo;
+ ListCell *lc;
+
+ filters = find_interesting_bloom_filters(root, rel);
+ if (filters == NIL)
+ return;
+
+ nfilters = list_length(filters);
+
+ /*
+ * Snapshot the existing unparameterized, non-partial scan paths of a
+ * supported type. We must snapshot before calling add_path(), which
+ * mutates rel->pathlist.
+ */
+ foreach(lc, rel->pathlist)
+ {
+ Path *path = (Path *) lfirst(lc);
+
+ /* XXX Is parameterization really a problem? Always? */
+ if (path->param_info != NULL || path->expected_filters != NIL)
+ continue;
+
+ switch (nodeTag(path))
+ {
+ case T_Path:
+ case T_IndexPath:
+ case T_BitmapHeapPath:
+ case T_TidPath:
+ case T_TidRangePath:
+ basepaths = lappend(basepaths, path);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (basepaths == NIL)
+ return;
+
+ /*
+ * Generate all combinations of the interesting filters. We do that by
+ * iterating 1 to (2^n-1), which generates all bitmask in between. Those
+ * are the subsets.
+ *
+ * XXX This is a good demonstration why we need to keep the number of
+ * filters low
+ *
+ * XXX Maybe we should also stop adding filters once the other filters
+ * already eliminate enought tuples. Say, we know F1 alone eliminates 99%
+ * tuples. Does it make sense to also consider [F1,F2]? Probably not. We
+ * could track "maximum" sets, and reject combinations containing one
+ * of those. We'd need to generate sets of increasing size, the iteration
+ * does not do that. But that's not hard.
+ */
+ for (combo = 1; combo < ((uint32) 1 << nfilters); combo++)
+ {
+ List *subset = NIL;
+ int i = 0;
+ ListCell *lcf;
+
+ foreach(lcf, filters)
+ {
+ if (combo & ((uint32) 1 << i))
+ subset = lappend(subset, lfirst(lcf));
+ i++;
+ }
+
+ /*
+ * All filtered paths for this combo share the same expected_filters
+ * list. That's safe: the list is never modified, and add_path() only
+ * ever frees the Path node itself, not its expected_filters.
+ */
+ foreach(lc, basepaths)
+ {
+ Path *base = (Path *) lfirst(lc);
+ Path *newpath;
+
+ newpath = create_filtered_scan_path(root, base, subset);
+ if (newpath != NULL)
+ add_path(rel, newpath);
+ }
+ }
+}
+
/*
* set_tablesample_rel_size
* Set size estimates for a sampled relation
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index c3072a29ccc..8740889094f 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -166,6 +166,23 @@ bool enable_partition_pruning = true;
bool enable_presorted_aggregate = true;
bool enable_async_append = true;
+/*
+ * Minimum fraction of outer tuples a pushed-down hash-join Bloom filter must
+ * be expected to eliminate for the planner to treat it as "interesting" and
+ * generate filter-aware scan paths. A value of 0.3 means a filter is only
+ * considered if it is expected to discard at least 30% of the scanned tuples.
+ */
+double bloom_filter_pushdown_threshold = 0.3;
+
+/*
+ * Upper bound on the number of distinct interesting Bloom filters considered
+ * for a single scan relation. This bounds the number of additional paths
+ * generated per scan (the planner enumerates non-empty subsets of the
+ * interesting filters, i.e. up to 2^bloom_filter_pushdown_max - 1 extra
+ * paths per base scan path).
+ */
+int bloom_filter_pushdown_max = 3;
+
typedef struct
{
PlannerInfo *root;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index e3697df51a2..6722b74f401 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -75,6 +75,15 @@ static RestrictInfo *create_join_clause(PlannerInfo *root,
EquivalenceMember *leftem,
EquivalenceMember *rightem,
EquivalenceClass *parent_ec);
+static int generate_implied_equalities_for_column_ec(PlannerInfo *root,
+ RelOptInfo *rel,
+ EquivalenceClass *cur_ec,
+ ec_matches_callback_type callback,
+ void *callback_arg,
+ Relids prohibited_rels,
+ Relids parent_relids,
+ bool is_child_rel,
+ List **result);
static bool reconsider_outer_join_clause(PlannerInfo *root,
OuterJoinClauseInfo *ojcinfo,
bool outer_on_left);
@@ -3211,6 +3220,107 @@ eclass_member_iterator_next(EquivalenceMemberIterator *it)
return NULL;
}
+/*
+ * generate_implied_equalities_for_column_ec
+ * Workhorse for generate_implied_equalities_for_column() and
+ * generate_implied_equalities_for_all_columns(). Considers a single
+ * EquivalenceClass cur_ec: if it has a member matching the target column
+ * (as identified by the callback), generate EC-derived joinclauses
+ * equating that member to each other-relation member, appending them to
+ * *result. Returns the number of clauses generated.
+ */
+static int
+generate_implied_equalities_for_column_ec(PlannerInfo *root,
+ RelOptInfo *rel,
+ EquivalenceClass *cur_ec,
+ ec_matches_callback_type callback,
+ void *callback_arg,
+ Relids prohibited_rels,
+ Relids parent_relids,
+ bool is_child_rel,
+ List **result)
+{
+ EquivalenceMemberIterator it;
+ EquivalenceMember *cur_em;
+ ListCell *lc2;
+ int ngenerated = 0;
+
+ /*
+ * Won't generate joinclauses if const or single-member (the latter test
+ * covers the volatile case too)
+ */
+ if (cur_ec->ec_has_const || list_length(cur_ec->ec_members) <= 1)
+ return 0;
+
+ /*
+ * Scan members, looking for a match to the target column. Note that
+ * child EC members are considered, but only when they belong to the
+ * target relation. (Unlike regular members, the same expression could be
+ * a child member of more than one EC. Therefore, it's potentially
+ * order-dependent which EC a child relation's target column gets matched
+ * to. This is annoying but it only happens in corner cases, so for now we
+ * live with just reporting the first match. See also
+ * get_eclass_for_sort_expr.)
+ */
+ setup_eclass_member_iterator(&it, cur_ec, rel->relids);
+ while ((cur_em = eclass_member_iterator_next(&it)) != NULL)
+ {
+ if (bms_equal(cur_em->em_relids, rel->relids) &&
+ callback(root, rel, cur_ec, cur_em, callback_arg))
+ break;
+ }
+
+ if (!cur_em)
+ return 0;
+
+ /*
+ * Found our match. Scan the other EC members and attempt to generate
+ * joinclauses. Ignore children here.
+ */
+ foreach(lc2, cur_ec->ec_members)
+ {
+ EquivalenceMember *other_em = (EquivalenceMember *) lfirst(lc2);
+ Oid eq_op;
+ RestrictInfo *rinfo;
+
+ /* Child members should not exist in ec_members */
+ Assert(!other_em->em_is_child);
+
+ /* Make sure it'll be a join to a different rel */
+ if (other_em == cur_em ||
+ bms_overlap(other_em->em_relids, rel->relids))
+ continue;
+
+ /* Forget it if caller doesn't want joins to this rel */
+ if (bms_overlap(other_em->em_relids, prohibited_rels))
+ continue;
+
+ /*
+ * Also, if this is a child rel, avoid generating a useless join to its
+ * parent rel(s).
+ */
+ if (is_child_rel &&
+ bms_overlap(parent_relids, other_em->em_relids))
+ continue;
+
+ eq_op = select_equality_operator(cur_ec,
+ cur_em->em_datatype,
+ other_em->em_datatype);
+ if (!OidIsValid(eq_op))
+ continue;
+
+ /* set parent_ec to mark as redundant with other joinclauses */
+ rinfo = create_join_clause(root, cur_ec, eq_op,
+ cur_em, other_em,
+ cur_ec);
+
+ *result = lappend(*result, rinfo);
+ ngenerated++;
+ }
+
+ return ngenerated;
+}
+
/*
* generate_implied_equalities_for_column
* Create EC-derived joinclauses usable with a specific column.
@@ -3233,6 +3343,10 @@ eclass_member_iterator_next(EquivalenceMemberIterator *it)
*
* The caller can pass a Relids set of rels we aren't interested in joining
* to, so as to save the work of creating useless clauses.
+ *
+ * XXX This could reuse generate_implied_equalities_for_column_ec for the
+ * inner loop, similarly to generate_implied_equalities_for_all_columns, but I
+ * chose to not do that for now. Better keep this as is.
*/
List *
generate_implied_equalities_for_column(PlannerInfo *root,
@@ -3353,6 +3467,71 @@ generate_implied_equalities_for_column(PlannerInfo *root,
return result;
}
+/*
+ * generate_implied_equalities_for_all_columns
+ * Like generate_implied_equalities_for_column, but returns EC-derived
+ * joinclauses for *every* column of the relation, rather than stopping at
+ * the first column (EquivalenceClass) that yields any clauses.
+ *
+ * generate_implied_equalities_for_column() is designed for parameterized-path
+ * generation, where the goal is to find a single usable joinclause per column
+ * and there is no value in returning clauses for more than one column at a
+ * time. Some callers, however, are interested in joinclauses on all of the
+ * relation's columns simultaneously (for example, the Bloom filter pushdown
+ * logic, which may push down filters derived from several different columns at
+ * once). This variant therefore visits all of the relation's
+ * EquivalenceClasses and accumulates clauses from each.
+ *
+ * As with generate_implied_equalities_for_column(), the result for any single
+ * column is a redundant set of clauses equating that column to each of the
+ * other-relation values it is known to be equal to.
+ *
+ * XXX We don't really need the last two arguments, but we keep this as close
+ * to generate_implied_equalities_for_column as possible.
+ */
+List *
+generate_implied_equalities_for_all_columns(PlannerInfo *root,
+ RelOptInfo *rel,
+ ec_matches_callback_type callback,
+ void *callback_arg,
+ Relids prohibited_rels)
+{
+ List *result = NIL;
+ bool is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ Relids parent_relids;
+ int i;
+
+ /* Should be OK to rely on eclass_indexes */
+ Assert(root->ec_merging_done);
+
+ /* Indexes are available only on base or "other" member relations. */
+ Assert(IS_SIMPLE_REL(rel));
+
+ /* If it's a child rel, we'll need to know what its parent(s) are */
+ if (is_child_rel)
+ parent_relids = find_childrel_parents(root, rel);
+ else
+ parent_relids = NULL; /* not used, but keep compiler quiet */
+
+ i = -1;
+ while ((i = bms_next_member(rel->eclass_indexes, i)) >= 0)
+ {
+ EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i);
+
+ /* Sanity check eclass_indexes only contain ECs for rel */
+ Assert(is_child_rel || bms_is_subset(rel->relids, cur_ec->ec_relids));
+
+ (void) generate_implied_equalities_for_column_ec(root, rel, cur_ec,
+ callback, callback_arg,
+ prohibited_rels,
+ parent_relids,
+ is_child_rel,
+ &result);
+ }
+
+ return result;
+}
+
/*
* have_relevant_eclass_joinclause
* Detect whether there is an EquivalenceClass that could produce
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 713283a73aa..5e65fda1419 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -869,6 +869,271 @@ get_memoize_path(PlannerInfo *root, RelOptInfo *innerrel,
return NULL;
}
+/*
+ * bloom_join_side_preserved
+ * Can a pushed-down Bloom filter be applied below the given side of a
+ * join of this type without changing the join's result?
+ *
+ * A Bloom filter removes tuples from the scan it is pushed to. That is only
+ * safe on a side whose unmatched tuples the join would drop anyway: dropping
+ * a tuple early then matches the join's behaviour. On a null-extended
+ * (preserved-other-side) input it is unsafe, because removing a tuple there
+ * could suppress, or spuriously emit, null-extended rows.
+ *
+ * This is the path-time counterpart of find_bloom_filter_recipient() in
+ * createplan.c, which descends a plan tree toward the recipient scan: it may
+ * only descend into a join's outer (resp. inner) child when this function
+ * returns true for to_outer = true (resp. false). Keeping the two in sync is
+ * what guarantees that every filter we realize at path time has a reachable
+ * recipient at plan time.
+ */
+bool
+bloom_join_side_preserved(JoinType jointype, bool to_outer)
+{
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ return true;
+ case JOIN_LEFT:
+ case JOIN_SEMI:
+ case JOIN_ANTI:
+ return to_outer;
+ case JOIN_RIGHT:
+ case JOIN_RIGHT_SEMI:
+ case JOIN_RIGHT_ANTI:
+ return !to_outer;
+ case JOIN_FULL:
+ return false;
+ default:
+ return false;
+ }
+}
+
+/*
+ * jointype_realizes_bloom_filter
+ * Can a hash join of this type build and push a Bloom filter to its
+ * outer (probe) side?
+ *
+ * This must match the join-type check in try_push_bloom_filter(), so that a
+ * filter we cost for is actually realized at plan time. Only join types that
+ * drop unmatched outer (probe) tuples are safe, since the filter eliminates
+ * probe tuples lacking an inner match.
+ *
+ * Note JOIN_RIGHT qualifies: it preserves unmatched tuples of the inner (build)
+ * side, not the outer (probe) side, so dropping unmatched probe tuples is still
+ * correct.
+ */
+static bool
+jointype_realizes_bloom_filter(JoinType jointype)
+{
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ case JOIN_RIGHT:
+ case JOIN_SEMI:
+ case JOIN_RIGHT_SEMI:
+ case JOIN_RIGHT_ANTI:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * hashjoin_pushes_filter_to
+ * Would create_hashjoin_plan() be able to push a Bloom filter built from
+ * these hash clauses down to the scan of owner_relid?
+ *
+ * This mirrors try_push_bloom_filter()'s requirement that every hash key on
+ * the outer side be a bare Var of a single base relation. 'outer_relids' is
+ * the set of relids on the outer (probe) side of the join.
+ */
+static bool
+hashjoin_pushes_filter_to(List *hashclauses, Relids outer_relids,
+ Index owner_relid)
+{
+ ListCell *lc;
+
+ if (hashclauses == NIL)
+ return false;
+
+ foreach(lc, hashclauses)
+ {
+ RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+ Node *outerside;
+ Var *var;
+
+ if (!is_opclause(ri->clause) ||
+ list_length(((OpExpr *) ri->clause)->args) != 2)
+ return false;
+
+ /* Pick the side that belongs to the outer relation. */
+ if (bms_is_subset(ri->left_relids, outer_relids))
+ outerside = get_leftop(ri->clause);
+ else if (bms_is_subset(ri->right_relids, outer_relids))
+ outerside = get_rightop(ri->clause);
+ else
+ return false;
+
+ if (!IsA(outerside, Var))
+ return false;
+ var = (Var *) outerside;
+ if (var->varno != owner_relid ||
+ var->varattno <= 0 ||
+ var->varlevelsup != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * compute_join_expected_filters
+ * Determine the expected Bloom filters a prospective join path should
+ * carry, applying the propagation and contradiction rules.
+ *
+ * Each filter expected by an input path is either:
+ *
+ * - propagated upward (its build side is not yet fully joined in), or
+ *
+ * - realized by this join (a hash join that is the filter's source and can
+ * push the filter to its outer side): it is dropped from the result, since
+ * it has now been applied. When 'realized' is non-NULL, such filters are
+ * appended to *realized so the caller can record them on the join path and
+ * later propagate the pushdown decision to the plan, or
+ *
+ * - contradicted: the join is the filter's source but cannot push it (it is
+ * not a suitable hash join). In that case the input path cannot be used
+ * for this join, so *contradicted is set true and NIL is returned.
+ *
+ * 'is_hashjoin'/'hashclauses' describe the join method; hashclauses is only
+ * meaningful for hash joins.
+ */
+static List *
+compute_join_expected_filters(PlannerInfo *root,
+ Path *outer_path, Path *inner_path,
+ JoinType jointype, bool is_hashjoin,
+ List *hashclauses, bool *contradicted,
+ List **realized)
+{
+ List *result = NIL;
+ Relids outer_relids = outer_path->parent->relids;
+ Relids inner_relids = inner_path->parent->relids;
+ Relids join_relids;
+ int pass;
+
+ *contradicted = false;
+ if (realized != NULL)
+ *realized = NIL;
+
+ /* Fast path: neither input expects any filter. */
+ if (outer_path->expected_filters == NIL &&
+ inner_path->expected_filters == NIL)
+ return NIL;
+
+ join_relids = bms_union(outer_relids, inner_relids);
+
+ /* Examine the filters from both inputs. */
+ for (pass = 0; pass < 2; pass++)
+ {
+ List *filters = (pass == 0) ? outer_path->expected_filters
+ : inner_path->expected_filters;
+ ListCell *lc;
+
+ foreach(lc, filters)
+ {
+ ExpectedFilter *f = (ExpectedFilter *) lfirst(lc);
+ bool owner_in_outer;
+ Relids owner_relids;
+ Relids other_relids;
+
+ owner_in_outer = bms_is_member(f->owner_relid, outer_relids);
+ owner_relids = owner_in_outer ? outer_relids : inner_relids;
+ other_relids = owner_in_outer ? inner_relids : outer_relids;
+
+ if (bms_is_subset(f->build_relids, owner_relids))
+ {
+ /*
+ * Build side already sits with the owner; this shouldn't
+ * normally happen (such a filter would have been resolved at a
+ * lower join), but if it does, just propagate it unchanged.
+ *
+ * We still must be able to reach the owner's scan from above,
+ * so the owner has to be on a side this join preserves (see
+ * bloom_join_side_preserved); otherwise the filter could not
+ * be pushed to a recipient and this path must be rejected.
+ */
+ if (!bloom_join_side_preserved(jointype, owner_in_outer))
+ goto contradiction;
+ result = lappend(result, f);
+ }
+ else if (bms_is_subset(f->build_relids, join_relids))
+ {
+ /* This join is the source of the filter. */
+ if (is_hashjoin &&
+ owner_in_outer &&
+ bms_is_subset(f->build_relids, other_relids) &&
+ jointype_realizes_bloom_filter(jointype) &&
+ hashjoin_pushes_filter_to(hashclauses, outer_relids,
+ f->owner_relid))
+ {
+ /* Realized by this hash join; drop from propagation. */
+ if (realized != NULL)
+ *realized = lappend(*realized, f);
+ continue;
+ }
+ else
+ {
+ /* Cannot realize the filter here: reject this path. */
+ goto contradiction;
+ }
+ }
+ else
+ {
+ /*
+ * Build side not yet available; propagate. As above, the
+ * filter can only reach its recipient scan if the owner stays
+ * on a side this join preserves; if not, reject this path so
+ * we never realize a filter with no recipient at plan time.
+ */
+ if (!bloom_join_side_preserved(jointype, owner_in_outer))
+ goto contradiction;
+ result = lappend(result, f);
+ }
+ }
+ }
+
+ bms_free(join_relids);
+ return result;
+
+contradiction:
+ *contradicted = true;
+ if (realized != NULL)
+ {
+ list_free(*realized);
+ *realized = NIL;
+ }
+ bms_free(join_relids);
+ list_free(result);
+ return NIL;
+}
+
+/*
+ * set_join_path_expected_filters
+ * Attach the propagated expected filters to a freshly created join path
+ * and reduce its row estimate to reflect their combined selectivity.
+ */
+static void
+set_join_path_expected_filters(Path *path, List *filters)
+{
+ if (filters == NIL)
+ return;
+
+ path->expected_filters = filters;
+ path->rows = clamp_row_est(path->rows *
+ expected_filters_selectivity(filters));
+}
+
/*
* try_nestloop_path
* Consider a nestloop join path; if it appears useful, push it into
@@ -967,26 +1232,71 @@ try_nestloop_path(PlannerInfo *root,
nestloop_subtype | PGS_CONSIDER_NONPARTIAL,
outer_path, inner_path, extra);
- if (add_path_precheck(joinrel, workspace.disabled_nodes,
- workspace.startup_cost, workspace.total_cost,
- pathkeys, required_outer))
- {
- add_path(joinrel, (Path *)
- create_nestloop_path(root,
- joinrel,
- jointype,
- &workspace,
- extra,
- outer_path,
- inner_path,
- extra->restrictlist,
- pathkeys,
- required_outer));
- }
- else
+ /*
+ * Account for expected Bloom filters carried by the input paths. A
+ * nestloop never builds a Bloom filter, so if it is the source of any
+ * expected filter the path is contradicted and must be rejected;
+ * otherwise the filters propagate to the resulting path.
+ */
{
- /* Waste no memory when we reject a path here */
- bms_free(required_outer);
+ bool contradicted;
+ List *jfilters;
+
+ jfilters = compute_join_expected_filters(root, outer_path, inner_path,
+ jointype, false, NIL,
+ &contradicted, NULL);
+
+ /*
+ * Contradicted means the inner/outer paths expect this join to realize
+ * one of the expected filters, but a nestloop can't do that. So these
+ * input paths are incompatible with a nestloop.
+ */
+ if (contradicted)
+ {
+ bms_free(required_outer);
+ return;
+ }
+
+ /*
+ * If the path expects any filters, it's excluded from the cost pruning
+ * performed by add_path (so don't bother with add_path_precheck either).
+ * Once a path has all filters satisfied (or there were no filters), do
+ * the pruning as usual.
+ *
+ * XXX We don't want the "regular" paths without filters to get removed,
+ * because we need the option to pick from join algorithms. Paths with
+ * filters would likely win (simply because there are fewer rows), but
+ * they only work with hashjoins. However, maybe the hashjoin won't work
+ * for some reason (e.g. it wouldn't fit into work_mem).
+ *
+ * XXX Maybe it'd be cleaner to do this in add_path_precheck (i.e. make
+ * it return true for paths with expected filters).
+ */
+ if (jfilters != NIL ||
+ add_path_precheck(joinrel, workspace.disabled_nodes,
+ workspace.startup_cost, workspace.total_cost,
+ pathkeys, required_outer))
+ {
+ Path *nlpath;
+
+ nlpath = (Path *) create_nestloop_path(root,
+ joinrel,
+ jointype,
+ &workspace,
+ extra,
+ outer_path,
+ inner_path,
+ extra->restrictlist,
+ pathkeys,
+ required_outer);
+ set_join_path_expected_filters(nlpath, jfilters);
+ add_path(joinrel, nlpath);
+ }
+ else
+ {
+ /* Waste no memory when we reject a path here */
+ bms_free(required_outer);
+ }
}
}
@@ -1160,30 +1470,58 @@ try_mergejoin_path(PlannerInfo *root,
outer_presorted_keys,
extra);
- if (add_path_precheck(joinrel, workspace.disabled_nodes,
- workspace.startup_cost, workspace.total_cost,
- pathkeys, required_outer))
- {
- add_path(joinrel, (Path *)
- create_mergejoin_path(root,
- joinrel,
- jointype,
- &workspace,
- extra,
- outer_path,
- inner_path,
- extra->restrictlist,
- pathkeys,
- required_outer,
- mergeclauses,
- outersortkeys,
- innersortkeys,
- outer_presorted_keys));
- }
- else
+ /*
+ * Account for expected Bloom filters carried by the input paths. A
+ * mergejoin never builds a Bloom filter, so it contradicts (and cannot
+ * use) any input path for which it would be the filter's source.
+ * Filter-bearing paths bypass the precheck, since their reduced cost
+ * isn't comparable to ordinary paths.
+ *
+ * XXX see the comments in try_nestloop_path
+ */
{
- /* Waste no memory when we reject a path here */
- bms_free(required_outer);
+ bool contradicted;
+ List *jfilters;
+
+ jfilters = compute_join_expected_filters(root, outer_path, inner_path,
+ jointype, false, NIL,
+ &contradicted, NULL);
+ if (contradicted)
+ {
+ bms_free(required_outer);
+ return;
+ }
+
+ if (jfilters != NIL ||
+ add_path_precheck(joinrel, workspace.disabled_nodes,
+ workspace.startup_cost, workspace.total_cost,
+ pathkeys, required_outer))
+ {
+ Path *mjpath;
+
+ mjpath = (Path *) create_mergejoin_path(root,
+ joinrel,
+ jointype,
+ &workspace,
+ extra,
+ outer_path,
+ inner_path,
+ extra->restrictlist,
+ pathkeys,
+ required_outer,
+ mergeclauses,
+ outersortkeys,
+ innersortkeys,
+ outer_presorted_keys);
+ set_join_path_expected_filters(mjpath, jfilters);
+ add_path(joinrel, mjpath);
+ }
+ else
+ {
+ /* Waste no memory when we reject a path here */
+ bms_free(required_outer);
+ }
+
}
}
@@ -1314,27 +1652,62 @@ try_hashjoin_path(PlannerInfo *root,
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra, false);
- if (add_path_precheck(joinrel, workspace.disabled_nodes,
- workspace.startup_cost, workspace.total_cost,
- NIL, required_outer))
- {
- add_path(joinrel, (Path *)
- create_hashjoin_path(root,
- joinrel,
- jointype,
- &workspace,
- extra,
- outer_path,
- inner_path,
- false, /* parallel_hash */
- extra->restrictlist,
- required_outer,
- hashclauses));
- }
- else
+ /*
+ * Account for expected Bloom filters carried by the input paths. A hash
+ * join builds and pushes down a Bloom filter, so it realizes (and removes
+ * from propagation) any expected filter for which it is the source; other
+ * filters propagate upward. Filter-bearing paths bypass the precheck.
+ */
{
- /* Waste no memory when we reject a path here */
- bms_free(required_outer);
+ bool contradicted;
+ List *jfilters;
+ List *realized;
+
+ jfilters = compute_join_expected_filters(root, outer_path, inner_path,
+ jointype, true, hashclauses,
+ &contradicted, &realized);
+
+ /* XXX Can a hashjoin contradict a filter? Probably not. */
+ if (contradicted)
+ {
+ bms_free(required_outer);
+ return;
+ }
+
+ if (jfilters != NIL ||
+ add_path_precheck(joinrel, workspace.disabled_nodes,
+ workspace.startup_cost, workspace.total_cost,
+ NIL, required_outer))
+ {
+ Path *hjpath;
+
+ hjpath = (Path *) create_hashjoin_path(root,
+ joinrel,
+ jointype,
+ &workspace,
+ extra,
+ outer_path,
+ inner_path,
+ false, /* parallel_hash */
+ extra->restrictlist,
+ required_outer,
+ hashclauses);
+ set_join_path_expected_filters(hjpath, jfilters);
+
+ /*
+ * Record the filters this hash join realizes, so create_hashjoin_plan
+ * can push exactly those down (and no others) at plan-creation time.
+ */
+ ((HashPath *) hjpath)->realized_filters = realized;
+
+ add_path(joinrel, hjpath);
+ }
+ else
+ {
+ /* Waste no memory when we reject a path here */
+ bms_free(required_outer);
+ return;
+ }
}
}
@@ -2316,6 +2689,33 @@ hash_inner_and_outer(PlannerInfo *root,
}
}
+ /*
+ * Also consider outer paths that carry expected Bloom filters. These
+ * are deliberately excluded from cheapest_startup/total_path and from
+ * cheapest_parameterized_paths (see set_cheapest), so we must iterate
+ * the full outer pathlist to find them. A hash join is able to build
+ * and push down the filters, so these paths are useful here even when
+ * they would be contradicted at a non-hash join.
+ */
+ foreach(lc1, outerrel->pathlist)
+ {
+ Path *outerpath = (Path *) lfirst(lc1);
+
+ if (outerpath->expected_filters == NIL)
+ continue;
+
+ if (PATH_PARAM_BY_REL(outerpath, innerrel))
+ continue;
+
+ try_hashjoin_path(root,
+ joinrel,
+ outerpath,
+ cheapest_total_inner,
+ hashclauses,
+ jointype,
+ extra);
+ }
+
/*
* If the joinrel is parallel-safe, we may be able to consider a
* partial hash join.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 7ecb551aae6..51990b98419 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4692,78 +4692,54 @@ create_mergejoin_plan(PlannerInfo *root,
/*
* BLOOM FILTER PUSHDOWN
*
- * When creating a hash join plan, consider building a bloom filter and
- * pushing it down to the outer subtree. For now we only push filters to
- * scan nodes containing all the join keys. When we find such scan node,
- * we append the BloomFilter ID to the node's bloom_filters list, and
- * increment the bloom_consumer_count for the hashjoin.
+ * When a hash join is created as a path, we decide whether it should build a
+ * Bloom filter and push it down to a scan on its outer (probe) side. That
+ * decision - which filters are selective enough to be worth building, and
+ * which scan they can be pushed to - is made entirely while creating paths
+ * (see find_interesting_bloom_filters and compute_join_expected_filters in the
+ * optimizer); and the chosen filters are recorded on the HashPath as its
+ * realized_filters. Here we merely propagate that decision into the plan: we
+ * never reconsider whether a filter is worthwhile, and in particular we never
+ * push a filter that was not selected as interesting when creating paths.
*
- * As setrefs hashn't run yet, the join keys are still the raw Vars.
- * So it's safe to compare var->varno against the scanrelid, and copy
- * the keys verbatim onto the recipient. setrefs will rewrite the Vars
- * later as usual, just like for the recipient's qual.
+ * For each realized filter we locate the scan node for its owner relation in
+ * the outer subtree, append a BloomFilter (built from the hash keys belonging
+ * to that filter) to the scan's bloom_filters list, and increment the
+ * hashjoin's bloom_consumer_count.
*
- * XXX In most cases there'll be only a single consumer node. To get
- * multiple consumers, we'd need either joins on the same keys, or
- * ability to produce filters for subsets of the join keys (for cases
- * where the join is more complex, and does not map to a single scan
- * node directly). Seems like a possible future improvement.
+ * As setrefs hasn't run yet, the hash keys are still the raw Vars. So it's
+ * safe to compare var->varno against the scanrelid, and copy the keys verbatim
+ * onto the recipient. setrefs will rewrite the Vars later as usual, just like
+ * for the recipient's qual.
*
- * XXX Actually, we could have multiple consumer nodes for partitioned
- * tables, where each partition gets a separate scan. Which seems like
- * something we should support.
+ * XXX In most cases there'll be only a single consumer node. To get multiple
+ * consumers, we'd need either joins on the same keys, or ability to produce
+ * filters for subsets of the join keys (for cases where the join is more
+ * complex, and does not map to a single scan node directly). Seems like a
+ * possible future improvement.
*
- * XXX For simplicity, all outer join keys have to be bare Vars (from
- * the same RTE). We could relax this later, and allow joins on more
- * complex expressions. Not sure if that'll erase some of the benefits,
- * which relies on filter probes being much cheaper hashtable probes.
- * It also doesn't seem like a very common case.
+ * XXX Actually, we could have multiple consumer nodes for partitioned tables,
+ * where each partition gets a separate scan. Which seems like something we
+ * should support.
*
- * XXX The recipient node must be one of a small set of scan nodes. We
- * could relax this, and allow pushing to other nodes (e.g. joins or
- * aggregates). Future improvement.
+ * XXX The recipient node must be one of a small set of scan nodes. We could
+ * relax this, and allow pushing to other nodes (e.g. joins or aggregates).
+ * Future improvement.
*
- * XXX We don't currently push the same HashJoin to multiple recipients,
- * but multiple HashJoins may attach a filter to the same scan node.
+ * XXX We don't currently push the same HashJoin to multiple recipients, but
+ * multiple HashJoins may attach a filter to the same scan node.
* --------------------------------------------------------------------------
*/
-/*
- * bloom_join_side_preserved
- * Decide if we can push filter to inner/outer side of a join.
- *
- * Outer joins that emit unmatched outer tuples (LEFT/ANTI/FULL) are
- * skipped: dropping outer tuples there would be incorrect.
- */
-static bool
-bloom_join_side_preserved(JoinType jointype, bool to_outer)
-{
- switch (jointype)
- {
- case JOIN_INNER:
- return true;
- case JOIN_LEFT:
- case JOIN_SEMI:
- case JOIN_ANTI:
- return to_outer;
- case JOIN_RIGHT:
- case JOIN_RIGHT_SEMI:
- case JOIN_RIGHT_ANTI:
- return !to_outer;
- case JOIN_FULL:
- return false;
- default:
- return false;
- }
-}
-
/*
* find_bloom_filter_recipient
* Try to find a scan node to push filter to.
*
* We support pushing filter to a subset of scan nodes (could be extended
* later). We support pushing filters through intermediate nodes (joins,
- * sorts, ...). See bloom_join_side_preserved for joins.
+ * sorts, ...). See bloom_join_side_preserved (joinpath.c) for joins; the
+ * path-time propagation uses the same predicate, so any filter realized while
+ * costing paths is guaranteed a reachable recipient here.
*
* XXX We could push filters through more nodes - e.g. aggregates if the
* hash keys match GROUP BY keys (are a subset of).
@@ -4771,6 +4747,12 @@ bloom_join_side_preserved(JoinType jointype, bool to_outer)
* XXX We could do pushdown to parallel parts of a query. But we'd need
* a different way to communicate if a filter is built etc. (the worker
* won't have access to the hashjoin state).
+ *
+ * XXX Not sure this handles partitioned tables correctly. Those will be below
+ * Append node, and we don't push through those. But the scans will still expect
+ * the filter, I think. Even if we pushed through Append node, it probably won't
+ * work because we expect a single consumer. But we'll have one consumer per
+ * scan of a partition.
*/
static Plan *
find_bloom_filter_recipient(Plan *plan, Index target_relid)
@@ -4842,142 +4824,92 @@ find_bloom_filter_recipient(Plan *plan, Index target_relid)
/*
* try_push_bloom_filter
- * Attempt to pushdown a bloom filter for the current hashjoin.
+ * Push down the bloom filter the planner decided this hashjoin should
+ * build, recording it on the recipient scan node.
*
- * The filter pushdown happens during plan creation, i.e. after the plan was
- * already selected. That is not entirely optimal, and it has a couple of
- * annoying consequences.
+ * 'realized_filters' is the list of ExpectedFilter nodes the HashPath was
+ * found to realize while creating paths. We do not reconsider that decision
+ * here; if it is empty, this hashjoin pushes nothing. Otherwise we turn it
+ * into a concrete BloomFilter attached to the scan of the owner relation.
*
- * The main disadvantage is that injecting the filter to a scan node may
- * significantly alter the number of tuples produced by that scan node. If a
- * filter eliminates 99% of the rows, the scan produces 1/100 of the rows it
- * was planned with. It would not affect the scan itself, but if there are
- * other nodes (between the scan and the join), maybe we'd have planned them
- * differently if we knew about the lower cardinality?
+ * A hash join builds a single bloom filter, populated with the combined hash
+ * value of all of its hash keys (see ExecHashTableInsert / bloom_add_element
+ * in nodeHash.c). The recipient must therefore probe with the matching full
+ * set of outer hash keys, so we build the filter from all of them. All
+ * realized filters of one hash join share the same owner relation (the outer
+ * side of every hash clause is a bare Var of that relation), so a single
+ * pushed-down filter covers them all.
*
- * Similarly, it's confusing in the explain. That is, we'll get "rows=N"
- * with the planner cardinality (before the filter was pushed down), but
- * then in EXPLAIN ANALYZE it'll get much lower values. It'd be easy to
- * confuse with inaccurate estimates.
- *
- * It'd be better to know about the filter earlier, when constructing the scan
- * path. But that's not quite feasible with our bottom-up planner. When planing
- * the scan, we don't know which of the joins above it will be hashjoins, or
- * if it can pushdown the filter. We'd have to speculate, or maybe build more
- * paths with/without expectation of the bloom filter pushdown. But that seems
- * not great, as it'd add overhead for everyone.
+ * Note the pushdown still happens during plan creation, i.e. after the plan was
+ * already selected. The selectivity of the filter was, however, accounted for
+ * while creating paths (the affected scan paths carry reduced row estimates),
+ * so the plan-time row counts already reflect the expected elimination.
*/
static void
-try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan)
+try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan,
+ List *realized_filters)
{
- List *hashkeys = hj->hashkeys;
- List *hashops = hj->hashoperators;
- List *hashcolls = hj->hashcollations;
- ListCell *lc;
- Index target_relid = 0;
+ ExpectedFilter *f;
+ Index owner_relid;
Plan *recipient;
BloomFilter *bf;
- /* bail out if feature disabled. */
- if (!enable_hashjoin_bloom)
- return;
-
- /* XXX shouldn't really happen, I think */
- if (hashkeys == NIL)
+ /* Nothing to do unless the planner chose to realize a filter here. */
+ if (realized_filters == NIL)
return;
- Assert(list_length(hashkeys) == list_length(hashops));
- Assert(list_length(hashkeys) == list_length(hashcolls));
-
/*
- * Pushdown is unsafe for join types that emit unmatched outer tuples
- * (LEFT/ANTI/FULL): we'd risk dropping outer tuples the join would
- * otherwise have emitted (possibly NULL-extended).
+ * The feature must have been enabled when paths were built; otherwise no
+ * filter would have been realized.
*/
- switch (hj->join.jointype)
- {
- case JOIN_INNER:
- case JOIN_RIGHT:
- case JOIN_SEMI:
- case JOIN_RIGHT_SEMI:
- case JOIN_RIGHT_ANTI:
- /* these join types are OK */
- break;
- default:
- return;
- }
+ Assert(enable_hashjoin_bloom);
+ Assert(hj->hashkeys != NIL);
+ Assert(list_length(hj->hashkeys) == list_length(hj->hashoperators));
+ Assert(list_length(hj->hashkeys) == list_length(hj->hashcollations));
/*
- * All hashkeys must be bare Vars referencing the same base RTE.
- *
- * XXX We could be a bit less strict, and check that at least some of the
- * hashkeys are bare Vars, not all of them. So with joins on multiple
- * expressions we'd have better chance to push a filter down. Doesn't
- * seem worth it, at least for now.
+ * All realized filters share the same owner relation (every hash clause's
+ * outer side is a bare Var of that relation).
*/
- foreach(lc, hashkeys)
- {
- Node *k = (Node *) lfirst(lc);
- Var *var;
-
- /* not a plain Var */
- if (!IsA(k, Var))
- return;
+ f = (ExpectedFilter *) linitial(realized_filters);
+ owner_relid = f->owner_relid;
- var = (Var *) k;
+#ifdef USE_ASSERT_CHECKING
+ {
+ ListCell *lc;
- /*
- * Reject outer references, whole-row or system columns, and
- * special varnos (not sure we can get them here, though).
- */
- if ((var->varlevelsup != 0) ||
- (var->varattno <= 0) ||
- IS_SPECIAL_VARNO(var->varno))
- return;
-
- /* make sure all the vars are for the same relid */
- if (target_relid == 0)
- target_relid = var->varno;
- else if (var->varno != target_relid)
- return;
+ foreach(lc, realized_filters)
+ Assert(((ExpectedFilter *) lfirst(lc))->owner_relid == owner_relid);
}
-
- /* should have found at least one var */
- Assert(target_relid != 0);
+#endif
/*
- * See if we can find the scan node for target_relid. It certainly is
- * in the plan somewhere, but it may not be able to pushdown the filter
- * to it (because of a join or so).
+ * Locate the scan node for the owner relation in the outer subtree. The
+ * path machinery guaranteed such a recipient exists (the filter could not
+ * have been realized otherwise), but stay defensive.
*/
- recipient = find_bloom_filter_recipient(outer_plan, target_relid);
+ recipient = find_bloom_filter_recipient(outer_plan, owner_relid);
if (recipient == NULL)
return;
/*
- * If we found a recipient, assign the filter an ID. We'll use it to
- * register the filter in ExecRegisterBloomFilterProducer, and then
- * for lookups in LookupBloomFilterProducer during execution.
+ * Assign the filter an ID. We'll use it to register the filter in
+ * ExecRegisterBloomFilterProducer, and then for lookups in
+ * LookupBloomFilterProducer during execution.
*
* XXX We can't use plan_node_id, as it's not assigned yet, that only
- * happens in set_plan_refs. Also, if we ever allow multiple filters
- * per hashtable (e.g. for different subsets of keys), it's not work.
+ * happens in set_plan_refs.
*/
hj->bloom_filter_id = ++root->glob->lastBloomFilterId;
bf = makeNode(BloomFilter);
- bf->filter_exprs = (List *) copyObject(hashkeys);
- bf->hashops = list_copy(hashops);
- bf->hashcollations = list_copy(hashcolls);
+ bf->filter_exprs = (List *) copyObject(hj->hashkeys);
+ bf->hashops = list_copy(hj->hashoperators);
+ bf->hashcollations = list_copy(hj->hashcollations);
bf->producer_id = hj->bloom_filter_id;
recipient->bloom_filters = lappend(recipient->bloom_filters, bf);
- /*
- * XXX We've manged to push the filter to the scan node, but maybe
- * we should wait with updating bloom_consumer_count when it actually
- * initializes the filters in ExecInit()?
- */
hj->bloom_consumer_count++;
}
@@ -5152,11 +5084,14 @@ create_hashjoin_plan(PlannerInfo *root,
copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
/*
- * Try to push the bloom filter for the hashtable down to nodes in the outer
- * subtree. If a suitable scan node exists, add the filter to bloom_filters,
- * and bump our bloom_consumer_count.
+ * Propagate the bloom filter pushdown decision made while creating paths:
+ * if this hash join was found to realize one or more bloom filters, push
+ * the corresponding filter down to the recipient scan in the outer
+ * subtree, and bump our bloom_consumer_count. No filter that was not
+ * selected as interesting during path creation is pushed here.
*/
- try_push_bloom_filter(root, join_plan, outer_plan);
+ try_push_bloom_filter(root, join_plan, outer_plan,
+ best_path->realized_filters);
return join_plan;
}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 73518c8f870..9cd9188a1cf 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -286,6 +286,16 @@ set_cheapest(RelOptInfo *parent_rel)
Path *path = (Path *) lfirst(p);
int cmp;
+ /*
+ * Paths that expect a pushed-down Bloom filter are speculative: their
+ * rows/cost estimates assume a hash join above will build and push a
+ * filter to them. They must never be chosen as the cheapest startup,
+ * total, or parameterized path; they are only consumed explicitly by
+ * join path generation (see joinpath.c). Skip them here.
+ */
+ if (path->expected_filters != NIL)
+ continue;
+
if (path->param_info)
{
/* Parameterized path, so add it to parameterized_paths */
@@ -383,6 +393,129 @@ set_cheapest(RelOptInfo *parent_rel)
parent_rel->cheapest_parameterized_paths = parameterized_paths;
}
+/*
+ * expected_filters_equal
+ * Return true if the two lists of ExpectedFilter nodes denote the same
+ * set of expected Bloom filters (order-independent).
+ */
+bool
+expected_filters_equal(List *a, List *b)
+{
+ ListCell *lc;
+
+ if (a == NIL && b == NIL)
+ return true;
+ if (list_length(a) != list_length(b))
+ return false;
+
+ foreach(lc, a)
+ {
+ ExpectedFilter *fa = (ExpectedFilter *) lfirst(lc);
+ ListCell *lc2;
+ bool found = false;
+
+ foreach(lc2, b)
+ {
+ ExpectedFilter *fb = (ExpectedFilter *) lfirst(lc2);
+
+ if (fa->owner_relid == fb->owner_relid &&
+ bms_equal(fa->build_relids, fb->build_relids) &&
+ equal(fa->clauses, fb->clauses))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * expected_filters_selectivity
+ * Combined surviving fraction of a set of expected filters, assuming
+ * independence. Returns a value in (0, 1].
+ */
+double
+expected_filters_selectivity(List *filters)
+{
+ double sel = 1.0;
+ ListCell *lc;
+
+ foreach(lc, filters)
+ {
+ ExpectedFilter *f = (ExpectedFilter *) lfirst(lc);
+
+ sel *= f->selectivity;
+ }
+
+ /* clamp to a sane range */
+ if (sel < 0.0)
+ sel = 0.0;
+ if (sel > 1.0)
+ sel = 1.0;
+
+ return sel;
+}
+
+/*
+ * create_filtered_scan_path
+ * Build a copy of a base-relation scan path that additionally expects the
+ * given set of pushed-down Bloom filters.
+ *
+ * The clone shares all substructure with the original path (parent,
+ * pathtarget, clauses, etc.); only the rows estimate is reduced to reflect
+ * the filters' combined selectivity, and expected_filters is set. This is
+ * safe because create_plan() treats the clone identically to the original
+ * (it ignores expected_filters), and add_path() may freely pfree the clone.
+ *
+ * Only the plain scan path node types that can receive a pushed-down filter
+ * are supported (matching find_bloom_filter_recipient in createplan.c).
+ * Returns NULL for unsupported path types.
+ *
+ * XXX This should probably adjust the CPU cost in some way. It assumes the
+ * filter checks are free, which does not seem right.
+ */
+Path *
+create_filtered_scan_path(PlannerInfo *root, Path *subpath, List *filters)
+{
+ Path *newpath;
+ size_t sz;
+
+ switch (nodeTag(subpath))
+ {
+ case T_Path:
+ /* plain seqscan/samplescan etc. */
+ sz = sizeof(Path);
+ break;
+ case T_IndexPath:
+ sz = sizeof(IndexPath);
+ break;
+ case T_BitmapHeapPath:
+ sz = sizeof(BitmapHeapPath);
+ break;
+ case T_TidPath:
+ sz = sizeof(TidPath);
+ break;
+ case T_TidRangePath:
+ sz = sizeof(TidRangePath);
+ break;
+ default:
+ /* unsupported scan path type */
+ return NULL;
+ }
+
+ newpath = (Path *) palloc(sz);
+ memcpy(newpath, subpath, sz);
+
+ newpath->expected_filters = filters;
+ newpath->rows = clamp_row_est(subpath->rows *
+ expected_filters_selectivity(filters));
+
+ return newpath;
+}
+
/*
* add_path
* Consider a potential implementation path for the specified parent rel,
@@ -485,6 +618,17 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
PathKeysComparison keyscmp;
BMS_Comparison outercmp;
+ /*
+ * Paths carrying different sets of expected Bloom filters serve
+ * different purposes (each may be consumed by a different parent join,
+ * or none at all), and their cost/row estimates aren't directly
+ * comparable. So if the two paths don't expect the same filters, keep
+ * both and don't let either dominate the other.
+ */
+ if (!expected_filters_equal(new_path->expected_filters,
+ old_path->expected_filters))
+ continue;
+
/*
* Do a fuzzy cost comparison with standard fuzziness limit.
*/
@@ -702,6 +846,20 @@ add_path_precheck(RelOptInfo *parent_rel, int disabled_nodes,
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
+ /*
+ * Paths carrying expected Bloom filters serve a different purpose and
+ * are not directly cost-comparable with ordinary paths, exactly as in
+ * add_path (which keeps both when the expected filter sets differ).
+ * The candidates submitted to this precheck never carry expected
+ * filters of their own, so any filter-bearing old path is a
+ * non-comparable speculative path and must not be allowed to dominate
+ * (and thereby suppress) the new path. Skipping them here also
+ * guarantees that a join relation always retains at least one ordinary,
+ * filter-free path to serve as cheapest_total_path.
+ */
+ if (old_path->expected_filters != NIL)
+ continue;
+
/*
* Since the pathlist is sorted by disabled_nodes and then by
* total_cost, we can stop looking once we reach a path with more
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index c9dcb294d4d..9ea40bcb798 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -380,6 +380,26 @@
max => 'BLCKSZ',
},
+{ name => 'bloom_filter_pushdown_max', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER',
+ short_desc => 'Maximum number of pushed-down hash join bloom filters considered per scan.',
+ long_desc => 'Bounds how many interesting bloom filters the planner enumerates subsets of when building filter-aware scan paths.',
+ flags => 'GUC_EXPLAIN',
+ variable => 'bloom_filter_pushdown_max',
+ boot_val => '3',
+ min => '0',
+ max => '10',
+},
+
+{ name => 'bloom_filter_pushdown_threshold', type => 'real', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER',
+ short_desc => 'Minimum fraction of tuples a pushed-down hash join bloom filter must be expected to eliminate.',
+ long_desc => 'A bloom filter is only considered during planning if it is expected to discard at least this fraction of the scanned tuples.',
+ flags => 'GUC_EXPLAIN',
+ variable => 'bloom_filter_pushdown_threshold',
+ boot_val => '0.3',
+ min => '0.0',
+ max => '1.0',
+},
+
{ name => 'bonjour', type => 'bool', context => 'PGC_POSTMASTER', group => 'CONN_AUTH_SETTINGS',
short_desc => 'Enables advertising the server via Bonjour.',
variable => 'enable_bonjour',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 34f98b42ff6..faf01bf7e43 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -485,6 +485,8 @@
# - Other Planner Options -
+#bloom_filter_pushdown_max = 3 # range 0-10
+#bloom_filter_pushdown_threshold = 0.3 # range 0.0-1.0
#default_statistics_target = 100 # range 1-10000
#constraint_exclusion = partition # on, off, or partition
#cursor_tuple_fraction = 0.1 # range 0.0-1.0
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 69f9ad2d5e3..f2f21f4ade1 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1836,6 +1836,54 @@ typedef struct GroupByOrdering
List *clauses;
} GroupByOrdering;
+/*
+ * ExpectedFilter
+ *
+ * Represents the planner's assumption that a scan path will receive a Bloom
+ * filter pushed down at plan-creation time by some hash join above it (see
+ * the bloom filter pushdown logic in createplan.c and nodeHashjoin.c). When
+ * a scan participates in a hashjoinable equality join, the hash join may build
+ * a Bloom filter on the inner ("build") side keys and push it to the scan on
+ * the outer ("probe") side, eliminating outer tuples that cannot match.
+ *
+ * Because that pushdown happens after path selection, the row/cost estimates
+ * of the affected paths would normally ignore the filter's selectivity. To
+ * account for it during planning, we generate additional scan paths that carry
+ * one or more ExpectedFilter nodes and whose rows/cost reflect the expected
+ * elimination. These filters are propagated up the join tree much like
+ * pathkeys, until the hash join that is their "source" realizes them.
+ *
+ * 'owner_relid' is the base relation the filter would be pushed down to.
+ * 'build_relids' is the set of base relids on the other (build) side of the
+ * join clause(s); the filter is "sourced" by the join that first brings
+ * those relids together with the owner.
+ * 'clauses' is the list of hashjoinable equality RestrictInfos defining the
+ * filter keys (the owner side of each clause is a bare Var of owner_relid).
+ * 'selectivity' is the expected surviving fraction of owner rows (in (0,1]).
+ *
+ * XXX The "owner_relid" may be a bit misleading, particularly if we allow
+ * pushing a filter to multiple nodes (e.g. scans on a partition). In that case
+ * we'd have multiple "owners", but ownership suggests there's just one. And
+ * some places already use "consumer" when referencing to the scan nodes, so
+ * maybe we should just use that?
+ *
+ * XXX What if we allow pushdown to non-scan nodes, e.g. above a join when
+ * pushdown to a scan is not possible (e.g. because the join clause is complex
+ * and references multiple relations)? Or maybe we could push to ForeignScan,
+ * and I'm not sure if those have a valid relid in all cases.
+ */
+typedef struct ExpectedFilter
+{
+ pg_node_attr(no_read, no_query_jumble)
+
+ NodeTag type;
+
+ Index owner_relid;
+ Relids build_relids;
+ List *clauses pg_node_attr(copy_as_scalar, equal_as_scalar);
+ Selectivity selectivity;
+} ExpectedFilter;
+
/*
* VolatileFunctionStatus -- allows nodes to cache their
* contain_volatile_functions properties. VOLATILITY_UNKNOWN means not yet
@@ -2012,6 +2060,14 @@ typedef struct Path
/* sort ordering of path's output; a List of PathKey nodes; see above */
List *pathkeys;
+
+ /*
+ * Bloom filters this path expects to receive from some hash join above
+ * it (a List of ExpectedFilter nodes; see below). An empty list means
+ * the path makes no such assumption. The path's rows/cost estimates
+ * already reflect the expected selectivity of these filters.
+ */
+ List *expected_filters;
} Path;
/* Macro for extracting a path's parameterization relids; beware double eval */
@@ -2493,6 +2549,16 @@ typedef struct HashPath
List *path_hashclauses; /* join clauses used for hashing */
int num_batches; /* number of batches expected */
Cardinality inner_rows_total; /* total inner rows expected */
+
+ /*
+ * Bloom filters this hash join "realizes": expected filters (see
+ * ExpectedFilter) carried by an input path for which this hash join is the
+ * source, and which it can push down to the scan of the owner relation.
+ * The pushdown decision is made here, while building paths; create_plan()
+ * merely propagates it to the plan (see create_hashjoin_plan). NIL means
+ * this hash join pushes no filter.
+ */
+ List *realized_filters;
} HashPath;
/*
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 7339979c008..35ef6bddab2 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -71,6 +71,8 @@ extern PGDLLIMPORT bool enable_parallel_hash;
extern PGDLLIMPORT bool enable_partition_pruning;
extern PGDLLIMPORT bool enable_presorted_aggregate;
extern PGDLLIMPORT bool enable_async_append;
+extern PGDLLIMPORT double bloom_filter_pushdown_threshold;
+extern PGDLLIMPORT int bloom_filter_pushdown_max;
extern PGDLLIMPORT int constraint_exclusion;
extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index e8db321f92b..0dcae2ae3b3 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -64,6 +64,11 @@ extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
int disabled_nodes, Cost startup_cost,
Cost total_cost, List *pathkeys);
+extern bool expected_filters_equal(List *a, List *b);
+extern double expected_filters_selectivity(List *filters);
+extern Path *create_filtered_scan_path(PlannerInfo *root, Path *subpath,
+ List *filters);
+
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer, int parallel_workers);
extern Path *create_samplescan_path(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 17f2099ec3b..636761f2e94 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -104,6 +104,7 @@ extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, SpecialJoinInfo *sjinfo,
List *restrictlist);
+extern bool bloom_join_side_preserved(JoinType jointype, bool to_outer);
/*
* joinrels.c
@@ -198,6 +199,11 @@ extern List *generate_implied_equalities_for_column(PlannerInfo *root,
ec_matches_callback_type callback,
void *callback_arg,
Relids prohibited_rels);
+extern List *generate_implied_equalities_for_all_columns(PlannerInfo *root,
+ RelOptInfo *rel,
+ ec_matches_callback_type callback,
+ void *callback_arg,
+ Relids prohibited_rels);
extern bool have_relevant_eclass_joinclause(PlannerInfo *root,
RelOptInfo *rel1, RelOptInfo *rel2);
extern bool has_relevant_eclass_joinclause(PlannerInfo *root,
diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out
index bbdb9526471..90578629061 100644
--- a/src/test/regress/expected/eager_aggregate.out
+++ b/src/test/regress/expected/eager_aggregate.out
@@ -147,15 +147,15 @@ GROUP BY t1.a ORDER BY t1.a;
Group Key: t2.b
-> Hash Join
Output: t2.c, t2.b, t3.c
- Hash Cond: (t3.a = t2.a)
- -> Seq Scan on public.eager_agg_t3 t3
- Output: t3.a, t3.b, t3.c
- Bloom Filter 1: keys=(t3.a)
+ Hash Cond: (t2.a = t3.a)
+ -> Seq Scan on public.eager_agg_t2 t2
+ Output: t2.a, t2.b, t2.c
+ Bloom Filter 1: keys=(t2.a)
-> Hash
- Output: t2.c, t2.b, t2.a
+ Output: t3.c, t3.a
Bloom Filter 1
- -> Seq Scan on public.eager_agg_t2 t2
- Output: t2.c, t2.b, t2.a
+ -> Seq Scan on public.eager_agg_t3 t3
+ Output: t3.c, t3.a
(29 rows)
SELECT t1.a, avg(t2.c + t3.c)
@@ -209,15 +209,15 @@ GROUP BY t1.a ORDER BY t1.a;
Sort Key: t2.b
-> Hash Join
Output: t2.c, t2.b, t3.c
- Hash Cond: (t3.a = t2.a)
- -> Seq Scan on public.eager_agg_t3 t3
- Output: t3.a, t3.b, t3.c
- Bloom Filter 1: keys=(t3.a)
+ Hash Cond: (t2.a = t3.a)
+ -> Seq Scan on public.eager_agg_t2 t2
+ Output: t2.a, t2.b, t2.c
+ Bloom Filter 1: keys=(t2.a)
-> Hash
- Output: t2.c, t2.b, t2.a
+ Output: t3.c, t3.a
Bloom Filter 1
- -> Seq Scan on public.eager_agg_t2 t2
- Output: t2.c, t2.b, t2.a
+ -> Seq Scan on public.eager_agg_t3 t3
+ Output: t3.c, t3.a
(32 rows)
SELECT t1.a, avg(t2.c + t3.c)
@@ -261,16 +261,14 @@ GROUP BY t1.a ORDER BY t1.a;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
- Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL avg(t2.c))
- Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL avg(t2.c)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(20 rows)
+(18 rows)
SELECT t1.a, avg(t2.c)
FROM eager_agg_t1 t1
@@ -309,13 +307,11 @@ GROUP BY t2.b ORDER BY t2.b;
Hash Cond: (t2.b = t1.b)
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
- Bloom Filter 1: keys=(t2.b)
-> Hash
Output: t1.b
- Bloom Filter 1
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.b
-(17 rows)
+(15 rows)
SELECT t2.b, avg(t2.c)
FROM eager_agg_t1 t1
@@ -460,12 +456,12 @@ GROUP BY t1.a ORDER BY t1.a;
-> Sort
Sort Key: t1.a
-> Hash Join
- Hash Cond: (t2.b = t1.b)
- -> Seq Scan on eager_agg_t2 t2
+ Hash Cond: (t1.b = t2.b)
+ -> Seq Scan on eager_agg_t1 t1
Bloom Filter 1: keys=(b)
-> Hash
Bloom Filter 1
- -> Seq Scan on eager_agg_t1 t1
+ -> Seq Scan on eager_agg_t2 t2
(11 rows)
EXPLAIN (COSTS OFF)
@@ -480,12 +476,12 @@ GROUP BY t1.a ORDER BY t1.a;
-> Sort
Sort Key: t1.a
-> Hash Join
- Hash Cond: (t2.b = t1.b)
- -> Seq Scan on eager_agg_t2 t2
+ Hash Cond: (t1.b = t2.b)
+ -> Seq Scan on eager_agg_t1 t1
Bloom Filter 1: keys=(b)
-> Hash
Bloom Filter 1
- -> Seq Scan on eager_agg_t1 t1
+ -> Seq Scan on eager_agg_t2 t2
(11 rows)
-- Eager aggregation must not push a partial aggregate onto the inner side of a
@@ -552,14 +548,16 @@ GROUP BY t2.b ORDER BY t2.b;
Hash Cond: (t1.b = t2.b)
-> Seq Scan on public.eager_agg_t1 t1
Output: t1.a, t1.b, t1.c
+ Bloom Filter 1: keys=(t1.b)
-> Hash
Output: t2.b, (PARTIAL count(*))
+ Bloom Filter 1
-> Partial HashAggregate
Output: t2.b, PARTIAL count(*)
Group Key: t2.b
-> Seq Scan on public.eager_agg_t2 t2
Output: t2.a, t2.b, t2.c
-(18 rows)
+(20 rows)
SELECT t2.b, count(*)
FROM eager_agg_t2 t2
@@ -621,10 +619,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
- Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -638,10 +634,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
- Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -655,16 +649,14 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
- Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x, t1_2.y
-(55 rows)
+(49 rows)
SELECT t1.x, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -709,10 +701,8 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
- Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -726,10 +716,8 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
- Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -743,16 +731,14 @@ GROUP BY t2.y ORDER BY t2.y;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
- Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.y, t1_2.x
-(55 rows)
+(49 rows)
SELECT t2.y, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -799,10 +785,8 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.x, t2.y
- Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.x)), (PARTIAL count(*)), (PARTIAL avg(t1.x))
- Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.x), PARTIAL count(*), PARTIAL avg(t1.x)
Group Key: t1.x
@@ -813,10 +797,8 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.x, t2_1.y
- Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.x)), (PARTIAL count(*)), (PARTIAL avg(t1_1.x))
- Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.x), PARTIAL count(*), PARTIAL avg(t1_1.x)
Group Key: t1_1.x
@@ -827,16 +809,14 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.x, t2_2.y
- Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.x)), (PARTIAL count(*)), (PARTIAL avg(t1_2.x))
- Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.x), PARTIAL count(*), PARTIAL avg(t1_2.x)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x
-(50 rows)
+(44 rows)
SELECT t2.x, sum(t1.x), count(*)
FROM eager_agg_tab1 t1
@@ -878,10 +858,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab1_p1 t1
Output: t1.x
- Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y)))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y))
Group Key: t2.x
@@ -890,10 +868,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab1_p1 t2
Output: t2.y, t2.x
- Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
- Bloom Filter 1
-> Seq Scan on public.eager_agg_tab1_p1 t3
Output: t3.y, t3.x
-> Finalize HashAggregate
@@ -904,10 +880,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab1_p2 t1_1
Output: t1_1.x
- Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y)))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y))
Group Key: t2_1.x
@@ -916,10 +890,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab1_p2 t2_1
Output: t2_1.y, t2_1.x
- Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
- Bloom Filter 3
-> Seq Scan on public.eager_agg_tab1_p2 t3_1
Output: t3_1.y, t3_1.x
-> Finalize HashAggregate
@@ -930,10 +902,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x
- Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y)))
- Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y))
Group Key: t2_2.x
@@ -942,13 +912,11 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab1_p3 t2_2
Output: t2_2.y, t2_2.x
- Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
- Bloom Filter 5
-> Seq Scan on public.eager_agg_tab1_p3 t3_2
Output: t3_2.y, t3_2.x
-(82 rows)
+(70 rows)
SELECT t1.x, sum(t2.y + t3.y)
FROM eager_agg_tab1 t1
@@ -1118,10 +1086,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.y = t1.x)
-> Seq Scan on public.eager_agg_tab2_p1 t2
Output: t2.y
- Bloom Filter 1: keys=(t2.y)
-> Hash
Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*)
Group Key: t1.x
@@ -1135,10 +1101,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.y = t1_1.x)
-> Seq Scan on public.eager_agg_tab2_p2 t2_1
Output: t2_1.y
- Bloom Filter 2: keys=(t2_1.y)
-> Hash
Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*)
Group Key: t1_1.x
@@ -1152,16 +1116,14 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on public.eager_agg_tab2_p3 t2_2
Output: t2_2.y
- Bloom Filter 3: keys=(t2_2.y)
-> Hash
Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*)
Group Key: t1_2.x
-> Seq Scan on public.eager_agg_tab1_p3 t1_2
Output: t1_2.x, t1_2.y
-(55 rows)
+(49 rows)
SELECT t1.x, sum(t1.y), count(*)
FROM eager_agg_tab1 t1
@@ -1224,10 +1186,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
- Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1241,10 +1201,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
- Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1258,10 +1216,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
- Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1275,10 +1231,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
- Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1292,16 +1246,14 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
- Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
- Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(89 rows)
+(79 rows)
SELECT t1.x, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1362,10 +1314,8 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.y, t1.x
- Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1376,10 +1326,8 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.y, t1_1.x
- Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1390,10 +1338,8 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.y, t1_2.x
- Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1404,10 +1350,8 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.y, t1_3.x
- Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1418,16 +1362,14 @@ GROUP BY t1.y ORDER BY t1.y;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.y, t1_4.x
- Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
- Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(77 rows)
+(67 rows)
SELECT t1.y, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1489,10 +1431,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
- Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*)
Group Key: t2.x
@@ -1501,10 +1441,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t2
Output: t2.y, t2.x
- Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
- Bloom Filter 1
-> Seq Scan on public.eager_agg_tab_ml_p1 t3
Output: t3.y, t3.x
-> Finalize HashAggregate
@@ -1515,10 +1453,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
- Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*)
Group Key: t2_1.x
@@ -1527,10 +1463,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1
Output: t2_1.y, t2_1.x
- Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
- Bloom Filter 3
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1
Output: t3_1.y, t3_1.x
-> Finalize HashAggregate
@@ -1541,10 +1475,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
- Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*))
- Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*)
Group Key: t2_2.x
@@ -1553,10 +1485,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2
Output: t2_2.y, t2_2.x
- Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
- Bloom Filter 5
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2
Output: t3_2.y, t3_2.x
-> Finalize HashAggregate
@@ -1567,10 +1497,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
- Bloom Filter 8: keys=(t1_3.x)
-> Hash
Output: t2_3.x, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*))
- Bloom Filter 8
-> Partial HashAggregate
Output: t2_3.x, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*)
Group Key: t2_3.x
@@ -1579,10 +1507,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_3.x = t3_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3
Output: t2_3.y, t2_3.x
- Bloom Filter 7: keys=(t2_3.x)
-> Hash
Output: t3_3.y, t3_3.x
- Bloom Filter 7
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3
Output: t3_3.y, t3_3.x
-> Finalize HashAggregate
@@ -1593,10 +1519,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
- Bloom Filter 10: keys=(t1_4.x)
-> Hash
Output: t2_4.x, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*))
- Bloom Filter 10
-> Partial HashAggregate
Output: t2_4.x, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*)
Group Key: t2_4.x
@@ -1605,13 +1529,11 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t2_4.x = t3_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
- Bloom Filter 9: keys=(t2_4.x)
-> Hash
Output: t3_4.y, t3_4.x
- Bloom Filter 9
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4
Output: t3_4.y, t3_4.x
-(134 rows)
+(114 rows)
SELECT t1.x, sum(t2.y + t3.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1673,10 +1595,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
- Bloom Filter 2: keys=(t1.x)
-> Hash
Output: t2.x, t3.y, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2.x, t3.y, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*)
Group Key: t2.x, t3.y, t3.x
@@ -1685,10 +1605,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2.x = t3.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t2
Output: t2.y, t2.x
- Bloom Filter 1: keys=(t2.x)
-> Hash
Output: t3.y, t3.x
- Bloom Filter 1
-> Seq Scan on public.eager_agg_tab_ml_p1 t3
Output: t3.y, t3.x
-> Hash Join
@@ -1696,10 +1614,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
- Bloom Filter 4: keys=(t1_1.x)
-> Hash
Output: t2_1.x, t3_1.y, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_1.x, t3_1.y, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*)
Group Key: t2_1.x, t3_1.y, t3_1.x
@@ -1708,10 +1624,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_1.x = t3_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1
Output: t2_1.y, t2_1.x
- Bloom Filter 3: keys=(t2_1.x)
-> Hash
Output: t3_1.y, t3_1.x
- Bloom Filter 3
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1
Output: t3_1.y, t3_1.x
-> Hash Join
@@ -1719,10 +1633,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
- Bloom Filter 6: keys=(t1_2.x)
-> Hash
Output: t2_2.x, t3_2.y, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*))
- Bloom Filter 6
-> Partial HashAggregate
Output: t2_2.x, t3_2.y, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*)
Group Key: t2_2.x, t3_2.y, t3_2.x
@@ -1731,10 +1643,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_2.x = t3_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2
Output: t2_2.y, t2_2.x
- Bloom Filter 5: keys=(t2_2.x)
-> Hash
Output: t3_2.y, t3_2.x
- Bloom Filter 5
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2
Output: t3_2.y, t3_2.x
-> Hash Join
@@ -1742,10 +1652,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
- Bloom Filter 8: keys=(t1_3.x)
-> Hash
Output: t2_3.x, t3_3.y, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*))
- Bloom Filter 8
-> Partial HashAggregate
Output: t2_3.x, t3_3.y, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*)
Group Key: t2_3.x, t3_3.y, t3_3.x
@@ -1754,10 +1662,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_3.x = t3_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3
Output: t2_3.y, t2_3.x
- Bloom Filter 7: keys=(t2_3.x)
-> Hash
Output: t3_3.y, t3_3.x
- Bloom Filter 7
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3
Output: t3_3.y, t3_3.x
-> Hash Join
@@ -1765,10 +1671,8 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
- Bloom Filter 10: keys=(t1_4.x)
-> Hash
Output: t2_4.x, t3_4.y, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*))
- Bloom Filter 10
-> Partial HashAggregate
Output: t2_4.x, t3_4.y, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*)
Group Key: t2_4.x, t3_4.y, t3_4.x
@@ -1777,13 +1681,11 @@ GROUP BY t3.y ORDER BY t3.y;
Hash Cond: (t2_4.x = t3_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
- Bloom Filter 9: keys=(t2_4.x)
-> Hash
Output: t3_4.y, t3_4.x
- Bloom Filter 9
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4
Output: t3_4.y, t3_4.x
-(122 rows)
+(102 rows)
SELECT t3.y, sum(t2.y + t3.y), count(*)
FROM eager_agg_tab_ml t1
@@ -1846,10 +1748,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1.x = t2.x)
-> Seq Scan on public.eager_agg_tab_ml_p1 t1
Output: t1.x
- Bloom Filter 1: keys=(t1.x)
-> Hash
Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*))
- Bloom Filter 1
-> Partial HashAggregate
Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*)
Group Key: t2.x
@@ -1863,10 +1763,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_1.x = t2_1.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1
Output: t1_1.x
- Bloom Filter 2: keys=(t1_1.x)
-> Hash
Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*))
- Bloom Filter 2
-> Partial HashAggregate
Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*)
Group Key: t2_1.x
@@ -1880,10 +1778,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_2.x = t2_2.x)
-> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2
Output: t1_2.x
- Bloom Filter 3: keys=(t1_2.x)
-> Hash
Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*))
- Bloom Filter 3
-> Partial HashAggregate
Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*)
Group Key: t2_2.x
@@ -1897,10 +1793,8 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_3.x = t2_3.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3
Output: t1_3.x
- Bloom Filter 4: keys=(t1_3.x)
-> Hash
Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*))
- Bloom Filter 4
-> Partial HashAggregate
Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*)
Group Key: t2_3.x
@@ -1914,16 +1808,14 @@ GROUP BY t1.x ORDER BY t1.x;
Hash Cond: (t1_4.x = t2_4.x)
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4
Output: t1_4.x
- Bloom Filter 5: keys=(t1_4.x)
-> Hash
Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*))
- Bloom Filter 5
-> Partial HashAggregate
Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*)
Group Key: t2_4.x
-> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4
Output: t2_4.y, t2_4.x
-(89 rows)
+(79 rows)
SELECT t1.x, sum(t2.y), count(*)
FROM eager_agg_tab_ml t1
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 70d986e8ab0..14fbdcc645a 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -250,8 +250,8 @@ SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'U
SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g;
a | customer_name | cid | order_id
---+---------------+-----+----------
- 1 | customer1 | 1 | 1
2 | customer1 | 1 | 1
+ 1 | customer1 | 1 | 1
(2 rows)
-- lateral reference with multi-label pattern, which is rewritten as UNION of
@@ -407,8 +407,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname, a.vprop1)
SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, b.ename AS conn, c.vname AS dest, c.lprop1, c.vprop2, c.vprop1));
src | conn | dest | lprop1 | vprop2 | vprop1
-----+------+------+----------+--------+--------
- v12 | e122 | v21 | vl2_prop | 1100 | 1010
v11 | e121 | v22 | vl2_prop | 1200 | 1020
+ v12 | e122 | v21 | vl2_prop | 1100 | 1010
v11 | e131 | v33 | vl3_prop | | 2030
v11 | e132 | v31 | vl3_prop | | 2010
(4 rows)
@@ -417,16 +417,16 @@ SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS
SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-[conn]-(v2) COLUMNS (v1.vname AS v1name, conn.ename AS cname, v2.vname AS v2name));
v1name | cname | v2name
--------+-------+--------
- v21 | e122 | v12
v22 | e121 | v11
+ v21 | e122 | v12
v22 | e231 | v32
(3 rows)
SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-(v2) COLUMNS (v1.vname AS v1name, v2.vname AS v2name));
v1name | v2name
--------+--------
- v21 | v12
v22 | v11
+ v21 | v12
v22 | v32
(3 rows)
@@ -487,8 +487,8 @@ LINE 1: SELECT * FROM GRAPH_TABLE (g1 MATCH (WHERE b.eprop1 = 10001)...
SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname, src.vprop1 AS svp1, src.vprop2 AS svp2, src.lprop1 AS slp1, dest.vprop1 AS dvp1, dest.vprop2 AS dvp2, dest.lprop1 AS dlp1, conn.eprop1 AS cep1, conn.lprop2 AS clp2));
svname | cename | dvname | svp1 | svp2 | slp1 | dvp1 | dvp2 | dlp1 | cep1 | clp2
--------+--------+--------+------+------+----------+------+------+----------+-------+--------
- v12 | e122 | v21 | 20 | | | 1010 | 1100 | vl2_prop | 10002 |
v11 | e121 | v22 | 10 | | | 1020 | 1200 | vl2_prop | 10001 |
+ v12 | e122 | v21 | 20 | | | 1010 | 1100 | vl2_prop | 10002 |
v11 | e131 | v33 | 10 | | | 2030 | | vl3_prop | 10003 |
v11 | e132 | v31 | 10 | | | 2010 | | vl3_prop | 10004 |
v22 | e231 | v32 | 1020 | 1200 | vl2_prop | 2020 | | vl3_prop | | 100050
@@ -498,8 +498,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS s
SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 | vl3)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname));
svname | cename | dvname
--------+--------+--------
- v12 | e122 | v21
v11 | e121 | v22
+ v12 | e122 | v21
v11 | e131 | v33
v11 | e132 | v31
v22 | e231 | v32
@@ -519,8 +519,8 @@ SELECT vn FROM all_vertices EXCEPT (SELECT svn FROM all_connected_vertices UNION
SELECT sn, cn, dn FROM GRAPH_TABLE (g1 MATCH (src IS l1)-[conn IS l1]->(dest IS l1) COLUMNS (src.elname AS sn, conn.elname AS cn, dest.elname AS dn));
sn | cn | dn
-----+------+-----
- v12 | e122 | v21
v11 | e121 | v22
+ v12 | e122 | v21
v11 | e131 | v33
v11 | e132 | v31
v22 | e231 | v32
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 4fccc7c4057..a04e99f1ed4 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -218,13 +218,13 @@ SELECT t1.a, t2.e
WHERE t1.a = t2.d;
a | e
---+----
- 0 |
1 | -1
2 | 2
- 2 | 4
3 | -3
+ 2 | 4
5 | -5
5 | -5
+ 0 |
(7 rows)
--
@@ -1573,13 +1573,13 @@ SELECT *
FROM J1_TBL INNER JOIN J2_TBL USING (i);
i | j | t | k
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
-- Same as above, slightly different syntax
@@ -1587,13 +1587,13 @@ SELECT *
FROM J1_TBL JOIN J2_TBL USING (i);
i | j | t | k
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
SELECT *
@@ -1681,35 +1681,35 @@ SELECT *
FROM J1_TBL NATURAL JOIN J2_TBL;
i | j | t | k
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
SELECT *
FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d);
a | b | c | d
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
SELECT *
FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
a | b | c | d
---+---+------+---
- 0 | | zero |
2 | 3 | two | 2
4 | 1 | four | 2
+ 0 | | zero |
(3 rows)
-- mismatch number of columns
@@ -1718,13 +1718,13 @@ SELECT *
FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a);
a | b | t | k
---+---+-------+----
- 0 | | zero |
1 | 4 | one | -1
2 | 3 | two | 2
- 2 | 3 | two | 4
3 | 2 | three | -3
+ 2 | 3 | two | 4
5 | 0 | five | -5
5 | 0 | five | -5
+ 0 | | zero |
(7 rows)
--
@@ -1734,22 +1734,22 @@ SELECT *
FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i);
i | j | t | i | k
---+---+-------+---+----
- 0 | | zero | 0 |
1 | 4 | one | 1 | -1
2 | 3 | two | 2 | 2
- 2 | 3 | two | 2 | 4
3 | 2 | three | 3 | -3
+ 2 | 3 | two | 2 | 4
5 | 0 | five | 5 | -5
5 | 0 | five | 5 | -5
+ 0 | | zero | 0 |
(7 rows)
SELECT *
FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k);
i | j | t | i | k
---+---+------+---+---
- 0 | | zero | | 0
2 | 3 | two | 2 | 2
4 | 1 | four | 2 | 4
+ 0 | | zero | | 0
(3 rows)
--
@@ -1909,8 +1909,8 @@ select * from tenk1 a, tenk1 b
where exists(select * from tenk1 c
where b.twothousand = c.twothousand and b.fivethous <> c.fivethous)
and a.tenthous = b.tenthous and a.tenthous < 5000;
- QUERY PLAN
---------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------
Hash Semi Join
Hash Cond: (b.twothousand = c.twothousand)
Join Filter: (b.fivethous <> c.fivethous)
@@ -1918,15 +1918,13 @@ where exists(select * from tenk1 c
Hash Cond: (b.tenthous = a.tenthous)
-> Seq Scan on tenk1 b
Bloom Filter 1: keys=(tenthous)
- Bloom Filter 2: keys=(twothousand)
-> Hash
Bloom Filter 1
-> Seq Scan on tenk1 a
Filter: (tenthous < 5000)
-> Hash
- Bloom Filter 2
-> Seq Scan on tenk1 c
-(15 rows)
+(13 rows)
--
-- More complicated constructs
@@ -2604,14 +2602,12 @@ select * from int4_tbl t1
Join Filter: (t2.f1 > 0)
Filter: (t3.f1 IS NULL)
-> Seq Scan on int4_tbl t2
- Bloom Filter 1: keys=(f1)
-> Materialize
-> Seq Scan on int4_tbl t3
-> Seq Scan on tenk1 t4
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl t1
-(15 rows)
+(13 rows)
explain (costs off)
select * from int4_tbl t1
@@ -2630,15 +2626,13 @@ select * from int4_tbl t1
Join Filter: (t2.f1 > 0)
Filter: (t2.f1 <> COALESCE(t3.f1, '-1'::integer))
-> Seq Scan on int4_tbl t2
- Bloom Filter 1: keys=(f1)
-> Materialize
-> Seq Scan on int4_tbl t3
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl t1
-> Materialize
-> Seq Scan on tenk1 t4
-(16 rows)
+(14 rows)
explain (costs off)
select * from onek t1
@@ -3126,17 +3120,17 @@ set enable_memoize to off;
explain (costs off)
select count(*) from tenk1 a, tenk1 b
where a.hundred = b.thousand and (b.fivethous % 10) < 10;
- QUERY PLAN
-------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------
Aggregate
-> Hash Join
- Hash Cond: (a.hundred = b.thousand)
- -> Index Only Scan using tenk1_hundred on tenk1 a
- Bloom Filter 1: keys=(hundred)
+ Hash Cond: (b.thousand = a.hundred)
+ -> Seq Scan on tenk1 b
+ Filter: ((fivethous % 10) < 10)
+ Bloom Filter 1: keys=(thousand)
-> Hash
Bloom Filter 1
- -> Seq Scan on tenk1 b
- Filter: ((fivethous % 10) < 10)
+ -> Index Only Scan using tenk1_hundred on tenk1 a
(9 rows)
select count(*) from tenk1 a, tenk1 b
@@ -3180,13 +3174,11 @@ ORDER BY 1;
Hash Cond: (b.f1 = c.f1)
Filter: (COALESCE(c.f1, 0) = 0)
-> Seq Scan on tt3 b
- Bloom Filter 1: keys=(f1)
-> Hash
-> Seq Scan on tt3 c
-> Hash
- Bloom Filter 1
-> Seq Scan on tt4 a
-(15 rows)
+(13 rows)
SELECT a.f1
FROM tt4 a
@@ -3224,12 +3216,10 @@ where t1.filt = 5;
Hash Join
Hash Cond: (t2.val = t1.val)
-> Seq Scan on skewedtable t2
- Bloom Filter 1: keys=(val)
-> Hash
- Bloom Filter 1
-> Seq Scan on skewedtable t1
Filter: (filt = 5)
-(8 rows)
+(6 rows)
drop table skewedtable;
--
@@ -3243,11 +3233,9 @@ where unique1 in (select unique2 from tenk1 b);
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
- Bloom Filter 1: keys=(unique1)
-> Hash
- Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(7 rows)
+(5 rows)
-- sadly, this is not an antijoin
explain (costs off)
@@ -3269,11 +3257,9 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2);
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
- Bloom Filter 1: keys=(unique1)
-> Hash
- Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(7 rows)
+(5 rows)
explain (costs off)
select a.* from tenk1 a
@@ -3306,17 +3292,15 @@ select 1 from tenk1
where (hundred, thousand) in (select twothousand, twothousand from onek);
QUERY PLAN
-------------------------------------------------
- Hash Join
- Hash Cond: (tenk1.hundred = onek.twothousand)
- -> Seq Scan on tenk1
- Filter: (hundred = thousand)
- Bloom Filter 1: keys=(hundred)
+ Hash Right Semi Join
+ Hash Cond: (onek.twothousand = tenk1.hundred)
+ -> Seq Scan on onek
+ Bloom Filter 1: keys=(twothousand)
-> Hash
Bloom Filter 1
- -> HashAggregate
- Group Key: onek.twothousand
- -> Seq Scan on onek
-(10 rows)
+ -> Seq Scan on tenk1
+ Filter: (hundred = thousand)
+(8 rows)
reset enable_memoize;
--
@@ -3333,19 +3317,17 @@ where t2.a is null;
Hash Right Anti Join
Hash Cond: (t2.b = t1.unique1)
-> Seq Scan on tbl_anti t2
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on tenk1 t1
-(7 rows)
+(5 rows)
-- this is an antijoin, as t2.a is non-null for any matching row
explain (costs off)
select * from tenk1 t1 left join
(tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b
where t2.a is null;
- QUERY PLAN
-----------------------------------------------
+ QUERY PLAN
+-------------------------------------------
Hash Right Anti Join
Hash Cond: (t2.b = t1.unique1)
-> Merge Left Join
@@ -3353,22 +3335,20 @@ where t2.a is null;
-> Sort
Sort Key: t2.c
-> Seq Scan on tbl_anti t2
- Bloom Filter 1: keys=(b)
-> Sort
Sort Key: t3.c
-> Seq Scan on tbl_anti t3
-> Hash
- Bloom Filter 1
-> Seq Scan on tenk1 t1
-(14 rows)
+(12 rows)
-- this is not an antijoin, as t3.a can be nulled by t2/t3 join
explain (costs off)
select * from tenk1 t1 left join
(tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b
where t3.a is null;
- QUERY PLAN
-----------------------------------------------
+ QUERY PLAN
+-------------------------------------------
Hash Right Join
Hash Cond: (t2.b = t1.unique1)
Filter: (t3.a IS NULL)
@@ -3377,14 +3357,12 @@ where t3.a is null;
-> Sort
Sort Key: t2.c
-> Seq Scan on tbl_anti t2
- Bloom Filter 1: keys=(b)
-> Sort
Sort Key: t3.c
-> Seq Scan on tbl_anti t3
-> Hash
- Bloom Filter 1
-> Seq Scan on tenk1 t1
-(15 rows)
+(13 rows)
rollback;
--
@@ -3398,11 +3376,9 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2 group by b.uniqu
Hash Semi Join
Hash Cond: (a.unique1 = b.unique2)
-> Seq Scan on tenk1 a
- Bloom Filter 1: keys=(unique1)
-> Hash
- Bloom Filter 1
-> Index Only Scan using tenk1_unique2 on tenk1 b
-(7 rows)
+(5 rows)
--
-- regression test for proper handling of outer joins within antijoins
@@ -3572,14 +3548,16 @@ create temp table tidv (idv mycomptype);
create index on tidv (idv);
explain (costs off)
select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv;
- QUERY PLAN
-----------------------------------------------------------
- Merge Join
- Merge Cond: (a.idv = b.idv)
- -> Index Only Scan using tidv_idv_idx on tidv a
- -> Materialize
- -> Index Only Scan using tidv_idv_idx on tidv b
-(5 rows)
+ QUERY PLAN
+------------------------------------
+ Hash Join
+ Hash Cond: (a.idv = b.idv)
+ -> Seq Scan on tidv a
+ Bloom Filter 1: keys=(idv)
+ -> Hash
+ Bloom Filter 1
+ -> Seq Scan on tidv b
+(7 rows)
set enable_mergejoin = 0;
set enable_hashjoin = 0;
@@ -4019,13 +3997,11 @@ where q1 = thousand or q2 = thousand;
-> Seq Scan on q2
-> Bitmap Heap Scan on tenk1
Recheck Cond: ((q1.q1 = thousand) OR (q2.q2 = thousand))
- Bloom Filter 1: keys=(twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = ANY (ARRAY[q1.q1, q2.q2]))
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl
-(14 rows)
+(12 rows)
explain (costs off)
select * from
@@ -4042,13 +4018,11 @@ where thousand = (q1 + q2);
-> Seq Scan on q2
-> Bitmap Heap Scan on tenk1
Recheck Cond: (thousand = (q1.q1 + q2.q2))
- Bloom Filter 1: keys=(twothousand)
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = (q1.q1 + q2.q2))
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl
-(14 rows)
+(12 rows)
--
-- test ability to generate a suitable plan for a star-schema query
@@ -4154,10 +4128,8 @@ where t1.unique1 < i4.f1;
Hash Cond: (t2.ten = t1.tenthous)
-> Seq Scan on public.tenk1 t2
Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4
- Bloom Filter 1: keys=(t2.ten)
-> Hash
Output: t1.tenthous, t1.unique1
- Bloom Filter 1
-> Nested Loop
Output: t1.tenthous, t1.unique1
-> Subquery Scan on ss0
@@ -4173,7 +4145,7 @@ where t1.unique1 < i4.f1;
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
Filter: (i8.q1 = ((64)::information_schema.cardinal_number)::integer)
-(35 rows)
+(33 rows)
select ss1.d1 from
tenk1 as t1
@@ -5270,7 +5242,6 @@ order by i0.f1, x;
Output: i1.f1, i2.q1, i2.q2, '123'::bigint
-> Seq Scan on public.int4_tbl i1
Output: i1.f1
- Bloom Filter 1: keys=(i1.f1)
-> Materialize
Output: i2.q1, i2.q2
-> Seq Scan on public.int8_tbl i2
@@ -5278,10 +5249,9 @@ order by i0.f1, x;
Filter: (123 = i2.q2)
-> Hash
Output: i0.f1
- Bloom Filter 1
-> Seq Scan on public.int4_tbl i0
Output: i0.f1
-(21 rows)
+(19 rows)
select * from
int4_tbl i0 left join
@@ -5335,10 +5305,8 @@ select t1.* from
Hash Cond: (i8.q1 = i8b2.q1)
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
- Bloom Filter 1: keys=(i8.q1)
-> Hash
Output: i8b2.q1, (NULL::integer)
- Bloom Filter 1
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, NULL::integer
-> Hash
@@ -5349,7 +5317,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(32 rows)
+(30 rows)
select t1.* from
text_tbl t1
@@ -5400,12 +5368,10 @@ select t1.* from
Output: i8b2.q1, NULL::integer
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, i8b2.q2
- Bloom Filter 1: keys=(i8b2.q1)
-> Materialize
-> Seq Scan on public.int4_tbl i4b2
-> Hash
Output: i8.q1, i8.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Hash
@@ -5416,7 +5382,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(36 rows)
+(34 rows)
select t1.* from
text_tbl t1
@@ -5469,16 +5435,12 @@ select t1.* from
Hash Cond: (i8b2.q1 = i4b2.f1)
-> Seq Scan on public.int8_tbl i8b2
Output: i8b2.q1, i8b2.q2
- Bloom Filter 1: keys=(i8b2.q1)
- Bloom Filter 2: keys=(i8b2.q1)
-> Hash
Output: i4b2.f1
- Bloom Filter 1
-> Seq Scan on public.int4_tbl i4b2
Output: i4b2.f1
-> Hash
Output: i8.q1, i8.q2
- Bloom Filter 2
-> Seq Scan on public.int8_tbl i8
Output: i8.q1, i8.q2
-> Hash
@@ -5489,7 +5451,7 @@ select t1.* from
Output: i4.f1
-> Seq Scan on public.int4_tbl i4
Output: i4.f1
-(41 rows)
+(37 rows)
select t1.* from
text_tbl t1
@@ -5840,17 +5802,15 @@ where ss1.c2 = 0;
Filter: (i43.f1 = 0)
-> Seq Scan on public.int4_tbl i41
Output: i41.f1
- Bloom Filter 1: keys=(i41.f1)
-> Hash
Output: i42.f1
- Bloom Filter 1
-> Seq Scan on public.int4_tbl i42
Output: i42.f1
-> Limit
Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42))
-> Seq Scan on public.text_tbl
Output: i41.f1, i8.q1, i8.q2, i42.f1, i43.f1, (42)
-(27 rows)
+(25 rows)
select ss2.* from
int4_tbl i41
@@ -5976,19 +5936,19 @@ explain (costs off)
select a.unique1, b.unique2
from onek a left join onek b on a.unique1 = b.unique2
where (b.unique2, random() > 0) = any (select q1, random() > 0 from int8_tbl c where c.q1 < b.unique1);
- QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------------------------
Hash Join
- Hash Cond: (b.unique2 = a.unique1)
- -> Seq Scan on onek b
- Filter: (ANY ((unique2 = (SubPlan any_1).col1) AND ((random() > '0'::double precision) = (SubPlan any_1).col2)))
- Bloom Filter 1: keys=(unique2)
- SubPlan any_1
- -> Seq Scan on int8_tbl c
- Filter: (q1 < b.unique1)
+ Hash Cond: (a.unique1 = b.unique2)
+ -> Index Only Scan using onek_unique1 on onek a
+ Bloom Filter 1: keys=(unique1)
-> Hash
Bloom Filter 1
- -> Index Only Scan using onek_unique1 on onek a
+ -> Seq Scan on onek b
+ Filter: (ANY ((unique2 = (SubPlan any_1).col1) AND ((random() > '0'::double precision) = (SubPlan any_1).col2)))
+ SubPlan any_1
+ -> Seq Scan on int8_tbl c
+ Filter: (q1 < b.unique1)
(11 rows)
select a.unique1, b.unique2
@@ -6142,16 +6102,14 @@ explain (costs off)
select id from a where id in (
select b.id from b left join c on b.id = c.id
);
- QUERY PLAN
------------------------------------
+ QUERY PLAN
+----------------------------
Hash Join
Hash Cond: (a.id = b.id)
-> Seq Scan on a
- Bloom Filter 1: keys=(id)
-> Hash
- Bloom Filter 1
-> Seq Scan on b
-(7 rows)
+(5 rows)
-- check optimization with oddly-nested outer joins
explain (costs off)
@@ -6574,18 +6532,16 @@ explain (costs off)
select c.id, ss.a from c
left join (select d.a from onerow, d left join b on d.a = b.id) ss
on c.id = ss.a;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+--------------------------------
Hash Right Join
Hash Cond: (d.a = c.id)
-> Nested Loop
-> Seq Scan on onerow
-> Seq Scan on d
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on c
-(9 rows)
+(7 rows)
-- check the case when the placeholder relates to an outer join and its
-- inner in the press field but actually uses only the outer side of the join
@@ -8254,33 +8210,32 @@ JOIN (
)
) _t2t3t4
ON sj_t1.id = _t2t3t4.id;
- QUERY PLAN
--------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
Nested Loop
- Join Filter: (sj_t3.id = sj_t1.id)
+ Join Filter: (sj_t1.id = sj_t3.id)
-> Nested Loop
- Join Filter: (sj_t2.id = sj_t3.id)
- -> Nested Loop Semi Join
+ Join Filter: (sj_t3.id = sj_t2_1.id)
+ -> Nested Loop
+ Join Filter: (sj_t2.id = sj_t3.id)
-> Nested Loop
- -> HashAggregate
- Group Key: sj_t3.id
+ -> Unique
+ -> Nested Loop
+ -> Index Only Scan using sj_t3_a_id_idx on sj_t3 sj_t3_1
+ Index Cond: (a = 1)
+ -> Seq Scan on sj_t4 sj_t4_1
+ -> Index Only Scan using sj_t2_id_idx on sj_t2
+ Index Cond: (id = sj_t3_1.id)
+ -> Materialize
+ -> Unique
-> Nested Loop
+ -> Index Only Scan using sj_t3_a_id_idx on sj_t3
+ Index Cond: (a = 1)
-> Seq Scan on sj_t4
- -> Materialize
- -> Bitmap Heap Scan on sj_t3
- Recheck Cond: (a = 1)
- -> Bitmap Index Scan on sj_t3_a_id_idx
- Index Cond: (a = 1)
- -> Index Only Scan using sj_t2_id_idx on sj_t2 sj_t2_1
- Index Cond: (id = sj_t3.id)
- -> Nested Loop
- -> Index Only Scan using sj_t3_a_id_idx on sj_t3 sj_t3_1
- Index Cond: ((a = 1) AND (id = sj_t3.id))
- -> Seq Scan on sj_t4 sj_t4_1
- -> Index Only Scan using sj_t2_id_idx on sj_t2
- Index Cond: (id = sj_t2_1.id)
+ -> Index Only Scan using sj_t2_id_idx on sj_t2 sj_t2_1
+ Index Cond: (id = sj_t2.id)
-> Seq Scan on sj_t1
-(24 rows)
+(23 rows)
--
-- Test RowMarks-related code
@@ -9143,15 +9098,13 @@ select * from
Output: b.q1, COALESCE(b.q2, '42'::bigint)
-> Seq Scan on public.int8_tbl d
Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)
- Bloom Filter 1: keys=(d.q1)
-> Hash
Output: c.q1, c.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Result
Output: (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))
-(26 rows)
+(24 rows)
-- another case requiring nested PlaceHolderVars
explain (verbose, costs off)
@@ -9210,29 +9163,25 @@ select c.*,a.*,ss1.q1,ss2.q1,ss3.* from
Join Filter: (b.q1 < b2.f1)
-> Seq Scan on public.int8_tbl b
Output: b.q1, b.q2
- Bloom Filter 1: keys=(b.q1)
-> Materialize
Output: b2.f1
-> Seq Scan on public.int4_tbl b2
Output: b2.f1
-> Hash
Output: a.q1, a.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl a
Output: a.q1, a.q2
-> Seq Scan on public.int8_tbl d
Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)
- Bloom Filter 2: keys=(d.q1)
-> Hash
Output: c.q1, c.q2
- Bloom Filter 2
-> Seq Scan on public.int8_tbl c
Output: c.q1, c.q2
-> Materialize
Output: i.f1
-> Seq Scan on public.int4_tbl i
Output: i.f1
-(38 rows)
+(34 rows)
-- check processing of postponed quals (bug #9041)
explain (verbose, costs off)
@@ -9539,10 +9488,8 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral
Hash Cond: (t3.b = t2.a)
-> Seq Scan on public.join_ut1 t3
Output: t3.a, t3.b, t3.c
- Bloom Filter 1: keys=(t3.b)
-> Hash
Output: t2.a
- Bloom Filter 1
-> Append
-> Seq Scan on public.join_pt1p1p1 t2_1
Output: t2_1.a
@@ -9550,7 +9497,7 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral
-> Seq Scan on public.join_pt1p2 t2_2
Output: t2_2.a
Filter: (t1.a = t2_2.a)
-(23 rows)
+(21 rows)
select t1.b, ss.phv from join_ut1 t1 left join lateral
(select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv
@@ -9600,22 +9547,21 @@ select * from fkest f1
join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100)
join fkest f3 on f1.x = f3.x
where f1.x100 = 2;
- QUERY PLAN
------------------------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------
Hash Join
Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10))
- -> Hash Join
- Hash Cond: (f3.x = f2.x)
- -> Seq Scan on fkest f3
- Bloom Filter 1: keys=(x)
- -> Hash
- Bloom Filter 1
- -> Seq Scan on fkest f2
- Filter: (x100 = 2)
+ -> Nested Loop
+ -> Seq Scan on fkest f2
+ Filter: (x100 = 2)
+ Bloom Filter 1: keys=(x, x10b)
+ -> Index Scan using fkest_x_x10_x100_idx on fkest f3
+ Index Cond: (x = f2.x)
-> Hash
+ Bloom Filter 1
-> Seq Scan on fkest f1
Filter: (x100 = 2)
-(13 rows)
+(12 rows)
rollback;
--
@@ -9668,21 +9614,19 @@ analyze j3;
-- ensure join is properly marked as unique
explain (verbose, costs off)
select * from j1 inner join j2 on j1.id = j2.id;
- QUERY PLAN
---------------------------------------
+ QUERY PLAN
+-----------------------------------
Hash Join
Output: j1.id, j2.id
Inner Unique: true
Hash Cond: (j1.id = j2.id)
-> Seq Scan on public.j1
Output: j1.id
- Bloom Filter 1: keys=(j1.id)
-> Hash
Output: j2.id
- Bloom Filter 1
-> Seq Scan on public.j2
Output: j2.id
-(12 rows)
+(10 rows)
-- ensure join is not unique when not an equi-join
explain (verbose, costs off)
@@ -9707,17 +9651,16 @@ select * from j1 inner join j3 on j1.id = j3.id;
--------------------------------------
Hash Join
Output: j1.id, j3.id
- Inner Unique: true
- Hash Cond: (j3.id = j1.id)
- -> Seq Scan on public.j3
- Output: j3.id
- Bloom Filter 1: keys=(j3.id)
- -> Hash
+ Hash Cond: (j1.id = j3.id)
+ -> Seq Scan on public.j1
Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
+ -> Hash
+ Output: j3.id
Bloom Filter 1
- -> Seq Scan on public.j1
- Output: j1.id
-(12 rows)
+ -> Seq Scan on public.j3
+ Output: j3.id
+(11 rows)
-- ensure left join is marked as unique
explain (verbose, costs off)
@@ -9788,64 +9731,70 @@ select * from j1 cross join j2;
-- ensure a natural join is marked as unique
explain (verbose, costs off)
select * from j1 natural join j2;
- QUERY PLAN
---------------------------------------
+ QUERY PLAN
+-----------------------------------
Hash Join
Output: j1.id
Inner Unique: true
Hash Cond: (j1.id = j2.id)
-> Seq Scan on public.j1
Output: j1.id
- Bloom Filter 1: keys=(j1.id)
-> Hash
Output: j2.id
- Bloom Filter 1
-> Seq Scan on public.j2
Output: j2.id
-(12 rows)
+(10 rows)
-- ensure a distinct clause allows the inner to become unique
explain (verbose, costs off)
select * from j1
inner join (select distinct id from j3) j3 on j1.id = j3.id;
- QUERY PLAN
------------------------------------------
- Nested Loop
+ QUERY PLAN
+-----------------------------------------------
+ Hash Join
Output: j1.id, j3.id
Inner Unique: true
- Join Filter: (j1.id = j3.id)
- -> Unique
+ Hash Cond: (j1.id = j3.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
+ -> Hash
Output: j3.id
- -> Sort
+ Bloom Filter 1
+ -> Unique
Output: j3.id
- Sort Key: j3.id
- -> Seq Scan on public.j3
+ -> Sort
Output: j3.id
- -> Seq Scan on public.j1
- Output: j1.id
-(13 rows)
+ Sort Key: j3.id
+ -> Seq Scan on public.j3
+ Output: j3.id
+(17 rows)
-- ensure group by clause allows the inner to become unique
explain (verbose, costs off)
select * from j1
inner join (select id from j3 group by id) j3 on j1.id = j3.id;
- QUERY PLAN
------------------------------------------
- Nested Loop
+ QUERY PLAN
+-----------------------------------------------
+ Hash Join
Output: j1.id, j3.id
Inner Unique: true
- Join Filter: (j1.id = j3.id)
- -> Group
+ Hash Cond: (j1.id = j3.id)
+ -> Seq Scan on public.j1
+ Output: j1.id
+ Bloom Filter 1: keys=(j1.id)
+ -> Hash
Output: j3.id
- Group Key: j3.id
- -> Sort
+ Bloom Filter 1
+ -> Group
Output: j3.id
- Sort Key: j3.id
- -> Seq Scan on public.j3
+ Group Key: j3.id
+ -> Sort
Output: j3.id
- -> Seq Scan on public.j1
- Output: j1.id
-(14 rows)
+ Sort Key: j3.id
+ -> Seq Scan on public.j3
+ Output: j3.id
+(18 rows)
drop table j1;
drop table j2;
@@ -9959,14 +9908,16 @@ create index j2_id1_idx on j2 (id1) where id1 % 1000 = 1;
explain (costs off) select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
- QUERY PLAN
------------------------------------------
- Merge Join
- Merge Cond: (j1.id1 = j2.id1)
- Join Filter: (j2.id2 = j1.id2)
- -> Index Scan using j1_id1_idx on j1
- -> Index Scan using j2_id1_idx on j2
-(5 rows)
+ QUERY PLAN
+----------------------------------------------------------
+ Nested Loop
+ Disabled: true
+ Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2))
+ -> Seq Scan on j1
+ Filter: ((id1 % 1000) = 1)
+ -> Seq Scan on j2
+ Filter: ((id1 % 1000) = 1)
+(7 rows)
select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
@@ -9981,15 +9932,16 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
explain (costs off) select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
- QUERY PLAN
-----------------------------------------------------
- Merge Join
- Merge Cond: (j1.id1 = j2.id1)
- Join Filter: (j2.id2 = j1.id2)
- -> Index Scan using j1_id1_idx on j1
- -> Index Scan using j2_id1_idx on j2
- Index Cond: (id1 = ANY ('{1}'::integer[]))
-(6 rows)
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Nested Loop
+ Disabled: true
+ Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2))
+ -> Seq Scan on j1
+ Filter: ((id1 % 1000) = 1)
+ -> Seq Scan on j2
+ Filter: ((id1 = ANY ('{1}'::integer[])) AND ((id1 % 1000) = 1))
+(7 rows)
select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
@@ -10004,15 +9956,16 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
explain (costs off) select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
- QUERY PLAN
--------------------------------------------------------
- Merge Join
- Merge Cond: (j1.id1 = j2.id1)
- Join Filter: (j2.id2 = j1.id2)
- -> Index Scan using j1_id1_idx on j1
- -> Index Scan using j2_id1_idx on j2
- Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
-(6 rows)
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Nested Loop
+ Disabled: true
+ Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2))
+ -> Seq Scan on j1
+ Filter: ((id1 % 1000) = 1)
+ -> Seq Scan on j2
+ Filter: ((id1 >= ANY ('{1,5}'::integer[])) AND ((id1 % 1000) = 1))
+(7 rows)
select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
diff --git a/src/test/regress/expected/join_hash.out b/src/test/regress/expected/join_hash.out
index 0a8ade8b961..21900564149 100644
--- a/src/test/regress/expected/join_hash.out
+++ b/src/test/regress/expected/join_hash.out
@@ -90,17 +90,15 @@ set local work_mem = '4MB';
set local hash_mem_multiplier = 1.0;
explain (costs off)
select count(*) from simple r join simple s using (id);
- QUERY PLAN
------------------------------------------
+ QUERY PLAN
+----------------------------------------
Aggregate
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
- Bloom Filter 1: keys=(id)
-> Hash
- Bloom Filter 1
-> Seq Scan on simple s
-(8 rows)
+(6 rows)
select count(*) from simple r join simple s using (id);
count
@@ -205,17 +203,15 @@ set local work_mem = '128kB';
set local hash_mem_multiplier = 1.0;
explain (costs off)
select count(*) from simple r join simple s using (id);
- QUERY PLAN
------------------------------------------
+ QUERY PLAN
+----------------------------------------
Aggregate
-> Hash Join
Hash Cond: (r.id = s.id)
-> Seq Scan on simple r
- Bloom Filter 1: keys=(id)
-> Hash
- Bloom Filter 1
-> Seq Scan on simple s
-(8 rows)
+(6 rows)
select count(*) from simple r join simple s using (id);
count
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index c5aa11cd249..40461acf17c 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -39,18 +39,17 @@ USING source AS s
ON t.tid = s.sid
WHEN MATCHED THEN
DELETE;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+------------------------------------------
Merge on target t
- -> Merge Join
- Merge Cond: (t.tid = s.sid)
- -> Sort
- Sort Key: t.tid
- -> Seq Scan on target t
- -> Sort
- Sort Key: s.sid
+ -> Hash Join
+ Hash Cond: (t.tid = s.sid)
+ -> Seq Scan on target t
+ Bloom Filter 1: keys=(tid)
+ -> Hash
+ Bloom Filter 1
-> Seq Scan on source s
-(9 rows)
+(8 rows)
--
-- Errors
@@ -1640,42 +1639,38 @@ SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
WHEN MATCHED THEN
UPDATE SET b = t.b + 1');
- explain_merge
--------------------------------------------------------------------------
+ explain_merge
+------------------------------------------------------------------------------------------
Merge on ex_mtarget t (actual rows=0.00 loops=1)
Tuples: updated=50
- -> Merge Join (actual rows=50.00 loops=1)
- Merge Cond: (t.a = s.a)
- -> Sort (actual rows=50.00 loops=1)
- Sort Key: t.a
- Sort Method: quicksort Memory: xxx
- -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
- -> Sort (actual rows=100.00 loops=1)
- Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ -> Hash Join (actual rows=50.00 loops=1)
+ Hash Cond: (t.a = s.a)
+ -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
+ Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%)
+ -> Hash (actual rows=100.00 loops=1)
+ Buckets: xxx Batches: xxx Memory Usage: xxx
+ Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0
-> Seq Scan on ex_msource s (actual rows=100.00 loops=1)
-(12 rows)
+(10 rows)
-- only updates to selected tuples
SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a
WHEN MATCHED AND t.a < 10 THEN
UPDATE SET b = t.b + 1');
- explain_merge
--------------------------------------------------------------------------
+ explain_merge
+------------------------------------------------------------------------------------------
Merge on ex_mtarget t (actual rows=0.00 loops=1)
Tuples: updated=5 skipped=45
- -> Merge Join (actual rows=50.00 loops=1)
- Merge Cond: (t.a = s.a)
- -> Sort (actual rows=50.00 loops=1)
- Sort Key: t.a
- Sort Method: quicksort Memory: xxx
- -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
- -> Sort (actual rows=100.00 loops=1)
- Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ -> Hash Join (actual rows=50.00 loops=1)
+ Hash Cond: (t.a = s.a)
+ -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
+ Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%)
+ -> Hash (actual rows=100.00 loops=1)
+ Buckets: xxx Batches: xxx Memory Usage: xxx
+ Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0
-> Seq Scan on ex_msource s (actual rows=100.00 loops=1)
-(12 rows)
+(10 rows)
-- updates + deletes
SELECT explain_merge('
@@ -1684,21 +1679,19 @@ WHEN MATCHED AND t.a < 10 THEN
UPDATE SET b = t.b + 1
WHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN
DELETE');
- explain_merge
--------------------------------------------------------------------------
+ explain_merge
+------------------------------------------------------------------------------------------
Merge on ex_mtarget t (actual rows=0.00 loops=1)
Tuples: updated=5 deleted=5 skipped=40
- -> Merge Join (actual rows=50.00 loops=1)
- Merge Cond: (t.a = s.a)
- -> Sort (actual rows=50.00 loops=1)
- Sort Key: t.a
- Sort Method: quicksort Memory: xxx
- -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
- -> Sort (actual rows=100.00 loops=1)
- Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ -> Hash Join (actual rows=50.00 loops=1)
+ Hash Cond: (t.a = s.a)
+ -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1)
+ Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%)
+ -> Hash (actual rows=100.00 loops=1)
+ Buckets: xxx Batches: xxx Memory Usage: xxx
+ Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0
-> Seq Scan on ex_msource s (actual rows=100.00 loops=1)
-(12 rows)
+(10 rows)
-- only inserts
SELECT explain_merge('
@@ -1795,21 +1788,20 @@ SELECT explain_merge('
MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000
WHEN MATCHED AND t.a < 10 THEN
DO NOTHING');
- explain_merge
------------------------------------------------------------------------
+ explain_merge
+-----------------------------------------------------------------------------------------
Merge on ex_mtarget t (actual rows=0.00 loops=1)
- -> Merge Join (actual rows=0.00 loops=1)
- Merge Cond: (t.a = s.a)
- -> Sort (actual rows=0.00 loops=1)
- Sort Key: t.a
- Sort Method: quicksort Memory: xxx
+ -> Hash Join (actual rows=0.00 loops=1)
+ Hash Cond: (s.a = t.a)
+ -> Seq Scan on ex_msource s (actual rows=1.00 loops=1)
+ Bloom Filter 1: keys=(a) checked=0 rejected=0 (0.0%)
+ -> Hash (actual rows=0.00 loops=1)
+ Buckets: xxx Batches: xxx Memory Usage: xxx
+ Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=0 rejected=0
-> Seq Scan on ex_mtarget t (actual rows=0.00 loops=1)
Filter: (a < '-1000'::integer)
Rows Removed by Filter: 54
- -> Sort (never executed)
- Sort Key: s.a
- -> Seq Scan on ex_msource s (never executed)
-(12 rows)
+(11 rows)
DROP TABLE ex_msource, ex_mtarget;
DROP FUNCTION explain_merge(text);
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b52528870ef..7fff6a720aa 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -614,14 +614,16 @@ CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer
SUPPORT test_support_func;
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+----------------------------------------------
Hash Join
- Hash Cond: (g.g = a.unique1)
- -> Function Scan on my_gen_series g
+ Hash Cond: (a.unique1 = g.g)
+ -> Seq Scan on tenk1 a
+ Bloom Filter 1: keys=(unique1)
-> Hash
- -> Seq Scan on tenk1 a
-(5 rows)
+ Bloom Filter 1
+ -> Function Scan on my_gen_series g
+(7 rows)
EXPLAIN (COSTS OFF)
SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g;
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index be56036461b..c30304b99c7 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -460,29 +460,23 @@ SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
- Bloom Filter 1: keys=(x)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> HashAggregate
Group Key: t1_1.x
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
- Bloom Filter 2: keys=(x)
-> Hash
- Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> HashAggregate
Group Key: t1_2.x
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
- Bloom Filter 3: keys=(y)
-> Hash
- Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(30 rows)
+(24 rows)
SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3;
x | sum | count
@@ -539,29 +533,23 @@ SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
- Bloom Filter 1: keys=(x)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> HashAggregate
Group Key: t2_1.y
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
- Bloom Filter 2: keys=(x)
-> Hash
- Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> HashAggregate
Group Key: t2_2.y
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
- Bloom Filter 3: keys=(y)
-> Hash
- Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(30 rows)
+(24 rows)
-- When GROUP BY clause does not match; partial aggregation is performed for each partition.
-- Also test GroupAggregate paths by disabling hash aggregates.
@@ -584,9 +572,7 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1.x = t2.y)
-> Seq Scan on pagg_tab1_p1 t1
- Bloom Filter 1: keys=(x)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 t2
-> Partial GroupAggregate
Group Key: t1_1.y
@@ -595,9 +581,7 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t1_1.x = t2_1.y)
-> Seq Scan on pagg_tab1_p2 t1_1
- Bloom Filter 2: keys=(x)
-> Hash
- Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 t2_1
-> Partial GroupAggregate
Group Key: t1_2.y
@@ -606,11 +590,9 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2
-> Hash Join
Hash Cond: (t2_2.y = t1_2.x)
-> Seq Scan on pagg_tab2_p3 t2_2
- Bloom Filter 3: keys=(y)
-> Hash
- Bloom Filter 3
-> Seq Scan on pagg_tab1_p3 t1_2
-(40 rows)
+(34 rows)
SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3;
y | sum | count
@@ -656,11 +638,9 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP B
-> Hash Right Join
Hash Cond: (b_2.y = a_2.x)
-> Seq Scan on pagg_tab2_p3 b_2
- Bloom Filter 1: keys=(y)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab1_p3 a_2
-(28 rows)
+(26 rows)
SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
y | sum
@@ -687,18 +667,14 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
-> Hash Right Join
Hash Cond: (a.x = b.y)
-> Seq Scan on pagg_tab1_p1 a
- Bloom Filter 1: keys=(x)
-> Hash
- Bloom Filter 1
-> Seq Scan on pagg_tab2_p1 b
-> HashAggregate
Group Key: b_1.y
-> Hash Right Join
Hash Cond: (a_1.x = b_1.y)
-> Seq Scan on pagg_tab1_p2 a_1
- Bloom Filter 2: keys=(x)
-> Hash
- Bloom Filter 2
-> Seq Scan on pagg_tab2_p2 b_1
-> HashAggregate
Group Key: b_2.y
@@ -707,7 +683,7 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP
-> Seq Scan on pagg_tab2_p3 b_2
-> Hash
-> Seq Scan on pagg_tab1_p3 a_2
-(28 rows)
+(24 rows)
SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST;
y | sum
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 1906b3641a3..38643d41fd7 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -36,28 +36,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -83,28 +77,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a =
-> Hash Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = b)
-> Hash Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = b)
-> Hash Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_p3 t2_3
Filter: (a = b)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a = t2.b ORDER BY t1.a, t2.b;
a | c | b | c
@@ -214,17 +202,13 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE
-> Hash Right Join
Hash Cond: (t1_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = 0)
-> Hash Right Join
Hash Cond: (t1_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Left Join
@@ -232,7 +216,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE
Filter: (a = 0)
-> Index Scan using iprt1_p3_a on prt1_p3 t1_3
Index Cond: (a = t2_3.b)
-(24 rows)
+(20 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -299,12 +283,10 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a <
Hash Cond: (t2.b = t1.a)
-> Seq Scan on prt2_p2 t2
Filter: (b > 250)
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p2 t1
Filter: ((a < 450) AND (b = 0))
-(11 rows)
+(9 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -400,18 +382,14 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
Hash Cond: (t1_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
Filter: (a = 0)
-> Hash Semi Join
Hash Cond: (t1_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Semi Join
@@ -421,7 +399,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
-> Materialize
-> Seq Scan on prt2_p3 t2_3
Filter: (a = 0)
-(28 rows)
+(24 rows)
SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -537,25 +515,19 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
-> Hash Join
Hash Cond: (t2_1.a = t3_1.b)
-> Seq Scan on prt1_p1 t2_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t3_1
-> Hash Join
Hash Cond: (t2_2.a = t3_2.b)
-> Seq Scan on prt1_p2 t2_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t3_2
-> Hash Join
Hash Cond: (t2_3.a = t3_3.b)
-> Seq Scan on prt1_p3 t2_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_p3 t3_3
-(32 rows)
+(26 rows)
SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
@@ -756,41 +728,29 @@ SELECT * FROM prt1 t1 JOIN prt1 t2 ON t1.a = t2.a WHERE t1.a IN (SELECT a FROM p
-> Hash Join
Hash Cond: (t1_1.a = t2_1.a)
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t2_1
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p1 t3_1
-> Hash Semi Join
Hash Cond: (t1_2.a = t3_2.a)
-> Hash Join
Hash Cond: (t1_2.a = t2_2.a)
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 3: keys=(a)
- Bloom Filter 4: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p2 t2_2
-> Hash
- Bloom Filter 4
-> Seq Scan on prt1_p2 t3_2
-> Hash Semi Join
Hash Cond: (t1_3.a = t3_3.a)
-> Hash Join
Hash Cond: (t1_3.a = t2_3.a)
-> Seq Scan on prt1_p3 t1_3
- Bloom Filter 5: keys=(a)
- Bloom Filter 6: keys=(a)
-> Hash
- Bloom Filter 5
-> Seq Scan on prt1_p3 t2_3
-> Hash
- Bloom Filter 6
-> Seq Scan on prt1_p3 t3_3
-(40 rows)
+(28 rows)
--
-- partitioned by expression
@@ -861,9 +821,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3_1
@@ -873,9 +831,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2
@@ -885,14 +841,12 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3
Index Cond: (((a + b) / 2) = t2_3.b)
-(39 rows)
+(33 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c | ?column? | c
@@ -917,9 +871,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
@@ -929,9 +881,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
@@ -941,12 +891,10 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(39 rows)
+(33 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
a | c | b | c | ?column? | c
@@ -976,9 +924,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_1.a = ((t3_1.a + t3_1.b) / 2))
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_e_p1 t3_1
Filter: (c = 0)
-> Index Scan using iprt2_p1_b on prt2_p1 t2_1
@@ -987,9 +933,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_2.a = ((t3_2.a + t3_2.b) / 2))
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_e_p2 t3_2
Filter: (c = 0)
-> Index Scan using iprt2_p2_b on prt2_p2 t2_2
@@ -998,14 +942,12 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2
-> Hash Right Join
Hash Cond: (t1_3.a = ((t3_3.a + t3_3.b) / 2))
-> Seq Scan on prt1_p3 t1_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_e_p3 t3_3
Filter: (c = 0)
-> Index Scan using iprt2_p3_b on prt2_p3 t2_3
Index Cond: (b = t1_3.a)
-(36 rows)
+(30 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
a | c | b | c | ?column? | c
@@ -1263,9 +1205,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_6.b = ((t1_9.a + t1_9.b) / 2))
-> Seq Scan on prt2_p1 t1_6
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_e_p1 t1_9
Filter: (c = 0)
-> Index Scan using iprt1_p1_a on prt1_p1 t1_3
@@ -1278,9 +1218,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_7.b = ((t1_10.a + t1_10.b) / 2))
-> Seq Scan on prt2_p2 t1_7
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_e_p2 t1_10
Filter: (c = 0)
-> Index Scan using iprt1_p2_a on prt1_p2 t1_4
@@ -1293,15 +1231,13 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
-> Hash Semi Join
Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2))
-> Seq Scan on prt2_p3 t1_8
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_e_p3 t1_11
Filter: (c = 0)
-> Index Scan using iprt1_p3_a on prt1_p3 t1_5
Index Cond: (a = t1_8.b)
Filter: (b = 0)
-(47 rows)
+(41 rows)
SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -1631,41 +1567,29 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, pl
-> Hash Join
Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on plt1_p1 t1_1
- Bloom Filter 1: keys=(b, c)
- Bloom Filter 2: keys=(c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt2_p1 t2_1
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_e_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on plt1_p2 t1_2
- Bloom Filter 3: keys=(b, c)
- Bloom Filter 4: keys=(c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt2_p2 t2_2
-> Hash
- Bloom Filter 4
-> Seq Scan on plt1_e_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
-> Seq Scan on plt1_p3 t1_3
- Bloom Filter 5: keys=(b, c)
- Bloom Filter 6: keys=(c)
-> Hash
- Bloom Filter 5
-> Seq Scan on plt2_p3 t2_3
-> Hash
- Bloom Filter 6
-> Seq Scan on plt1_e_p3 t3_3
-(44 rows)
+(32 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
avg | avg | avg | c | c | c
@@ -1713,29 +1637,23 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1
-> Hash Join
Hash Cond: (t3_1.a = t2_1.b)
-> Seq Scan on prt1_p1 t3_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t2_1
-> Hash Join
Hash Cond: (t3_2.a = t2_2.b)
-> Seq Scan on prt1_p2 t3_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t2_2
-> Hash Join
Hash Cond: (t3_3.a = t2_3.b)
-> Seq Scan on prt1_p3 t3_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_p3 t2_3
-> Hash
-> Result
Replaces: Scan on prt1
One-Time Filter: false
-(28 rows)
+(22 rows)
EXPLAIN (COSTS OFF)
SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b;
@@ -1797,41 +1715,29 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, ph
-> Hash Join
Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on pht1_p1 t1_1
- Bloom Filter 1: keys=(b, c)
- Bloom Filter 2: keys=(c)
-> Hash
- Bloom Filter 1
-> Seq Scan on pht2_p1 t2_1
-> Hash
- Bloom Filter 2
-> Seq Scan on pht1_e_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on pht1_p2 t1_2
- Bloom Filter 3: keys=(b, c)
- Bloom Filter 4: keys=(c)
-> Hash
- Bloom Filter 3
-> Seq Scan on pht2_p2 t2_2
-> Hash
- Bloom Filter 4
-> Seq Scan on pht1_e_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text))
-> Hash Join
Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
-> Seq Scan on pht1_p3 t1_3
- Bloom Filter 5: keys=(b, c)
- Bloom Filter 6: keys=(c)
-> Hash
- Bloom Filter 5
-> Seq Scan on pht2_p3 t2_3
-> Hash
- Bloom Filter 6
-> Seq Scan on pht1_e_p3 t3_3
-(44 rows)
+(32 rows)
SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c;
avg | avg | avg | c | c | c
@@ -1861,28 +1767,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
-- test default partition behavior for list
ALTER TABLE plt1 DETACH PARTITION plt1_p3;
@@ -1903,28 +1803,22 @@ SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c
-> Hash Join
Hash Cond: (t2_1.c = t1_1.c)
-> Seq Scan on plt2_p1 t2_1
- Bloom Filter 1: keys=(c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_p1 t1_1
Filter: ((a % 25) = 0)
-> Hash Join
Hash Cond: (t2_2.c = t1_2.c)
-> Seq Scan on plt2_p2 t2_2
- Bloom Filter 2: keys=(c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_p2 t1_2
Filter: ((a % 25) = 0)
-> Hash Join
Hash Cond: (t2_3.c = t1_3.c)
-> Seq Scan on plt2_p3 t2_3
- Bloom Filter 3: keys=(c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_p3 t1_3
Filter: ((a % 25) = 0)
-(29 rows)
+(23 rows)
--
-- multiple levels of partitioning
@@ -1960,9 +1854,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_l_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_l_p1 t1_1
Filter: (b = 0)
-> Hash Join
@@ -1984,7 +1876,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
-> Hash
-> Seq Scan on prt1_l_p3_p1 t1_5
Filter: (b = 0)
-(30 rows)
+(28 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2463,25 +2355,19 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a
-> Hash Join
Hash Cond: (t1_1.a = t3_1.b)
-> Seq Scan on prt1_p1 t1_1
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_p1 t3_1
-> Hash Join
Hash Cond: (t1_2.a = t3_2.b)
-> Seq Scan on prt1_p2 t1_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt2_p2 t3_2
-> Hash Join
Hash Cond: (t1_3.a = t3_3.b)
-> Seq Scan on prt1_p3 t1_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_p3 t3_3
-(29 rows)
+(23 rows)
-- partitionwise join can not be applied if there are no equi-join conditions
-- between partition keys
@@ -2753,28 +2639,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2800,28 +2680,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2847,28 +2721,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -2898,28 +2766,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -2986,28 +2848,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3033,28 +2889,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Semi Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -3080,28 +2930,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3157,28 +3001,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: (b = 0)
-> Hash Right Anti Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_adv_p3 t2_3
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p3 t1_3
Filter: (b = 0)
-(27 rows)
+(21 rows)
SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
a | b | c
@@ -3264,32 +3102,24 @@ SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2
-> Hash Right Join
Hash Cond: (t2_2.a = t1_2.b)
-> Seq Scan on prt1_adv_p2 t2_2
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Hash Join
Hash Cond: (t3_2.a = t1_2.b)
-> Seq Scan on prt1_adv_p2 t3_2
- Bloom Filter 1: keys=(a)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt2_adv_p2 t1_2
Filter: (a = 0)
-> Hash Right Join
Hash Cond: (t2_3.a = t1_3.b)
-> Seq Scan on prt1_adv_p3 t2_3
- Bloom Filter 4: keys=(a)
-> Hash
- Bloom Filter 4
-> Hash Join
Hash Cond: (t3_3.a = t1_3.b)
-> Seq Scan on prt1_adv_p3 t3_3
- Bloom Filter 3: keys=(a)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt2_adv_p3 t1_3
Filter: (a = 0)
-(39 rows)
+(31 rows)
SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a;
b | c | a | c | a | c
@@ -3459,20 +3289,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_2.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_2
Filter: (b = 0)
-> Hash Join
Hash Cond: (t2_2.b = t1_1.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_1
Filter: (b = 0)
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3564,32 +3390,24 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2
-> Hash Right Join
Hash Cond: (t3_1.a = t1_1.a)
-> Seq Scan on prt3_adv_p1 t3_1
- Bloom Filter 2: keys=(a)
-> Hash
- Bloom Filter 2
-> Hash Right Join
Hash Cond: (t2_2.b = t1_1.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p2 t1_1
Filter: (b = 0)
-> Hash Right Join
Hash Cond: (t3_2.a = t1_2.a)
-> Seq Scan on prt3_adv_p2 t3_2
- Bloom Filter 4: keys=(a)
-> Hash
- Bloom Filter 4
-> Hash Right Join
Hash Cond: (t2_1.b = t1_2.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 3: keys=(b)
-> Hash
- Bloom Filter 3
-> Seq Scan on prt1_adv_p1 t1_2
Filter: (b = 0)
-(31 rows)
+(23 rows)
SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a;
a | c | b | c | a | c
@@ -3631,20 +3449,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: ((a < 300) AND (b = 0))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: ((a < 300) AND (b = 0))
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3674,20 +3488,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_adv_p1 t2_1
- Bloom Filter 1: keys=(b)
-> Hash
- Bloom Filter 1
-> Seq Scan on prt1_adv_p1 t1_1
Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_adv_p2 t2_2
- Bloom Filter 2: keys=(b)
-> Hash
- Bloom Filter 2
-> Seq Scan on prt1_adv_p2 t1_2
Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
a | c | b | c
@@ -3728,28 +3538,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3771,28 +3575,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3814,28 +3612,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3859,28 +3651,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -3945,28 +3731,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -3988,28 +3768,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4031,28 +3805,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4102,28 +3870,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4336,28 +4098,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4379,28 +4135,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a
-> Hash Right Semi Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Semi Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4422,28 +4172,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4468,28 +4212,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t
-> Hash Right Anti Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1_null t1_1
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Anti Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3_null t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a;
a | b | c
@@ -4565,28 +4303,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4662,28 +4394,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-(27 rows)
+(21 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4705,25 +4431,19 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-> Nested Loop Left Join
@@ -4731,7 +4451,7 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Seq Scan on plt1_adv_extra t1_4
Filter: (b < 10)
-> Seq Scan on plt2_adv_extra t2_4
-(32 rows)
+(26 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4805,43 +4525,31 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
-> Hash Right Join
Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
-> Seq Scan on plt1_adv_p1 t3_1
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
-> Seq Scan on plt1_adv_p2 t3_2
- Bloom Filter 4: keys=(a, c)
-> Hash
- Bloom Filter 4
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p2 t1_2
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c))
-> Seq Scan on plt1_adv_p3 t3_3
- Bloom Filter 6: keys=(a, c)
-> Hash
- Bloom Filter 6
-> Hash Right Join
Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c))
-> Seq Scan on plt2_adv_p3 t2_3
- Bloom Filter 5: keys=(a, c)
-> Hash
- Bloom Filter 5
-> Seq Scan on plt1_adv_p3 t1_3
Filter: (b < 10)
-> Nested Loop Left Join
@@ -4852,7 +4560,7 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
Filter: (b < 10)
-> Seq Scan on plt2_adv_extra t2_4
-> Seq Scan on plt1_adv_extra t3_4
-(53 rows)
+(41 rows)
SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c | a | c
@@ -4888,20 +4596,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -4983,32 +4687,24 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2
-> Hash Right Join
Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c))
-> Seq Scan on plt3_adv_p1 t3_1
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Hash Right Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-> Hash Right Join
Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c))
-> Seq Scan on plt3_adv_p2 t3_2
- Bloom Filter 4: keys=(a, c)
-> Hash
- Bloom Filter 4
-> Hash Right Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1 t2_1
- Bloom Filter 3: keys=(a, c)
-> Hash
- Bloom Filter 3
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-(31 rows)
+(23 rows)
SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c | a | c
@@ -5037,20 +4733,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c))
-> Seq Scan on plt2_adv_p1_null t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p1 t1_2
Filter: (b < 10)
-> Hash Join
Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c))
-> Seq Scan on plt2_adv_p2 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p2 t1_1
Filter: (b < 10)
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5075,12 +4767,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p2 t2
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p2 t1
Filter: (b < 10)
-(10 rows)
+(8 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5119,20 +4809,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p3 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p3 t1_1
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p4 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p4 t1_2
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5151,12 +4837,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p4 t2
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p4 t1
Filter: ((c IS NULL) AND (b < 10))
-(10 rows)
+(8 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5178,20 +4862,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
-> Hash Join
Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
-> Seq Scan on plt2_adv_p3 t2_1
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p3 t1_1
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
-> Seq Scan on plt2_adv_p4 t2_2
- Bloom Filter 2: keys=(a, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on plt1_adv_p4 t1_2
Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(19 rows)
+(15 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5210,12 +4890,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a =
-> Hash Right Join
Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c))
-> Seq Scan on plt2_adv_p4 t2
- Bloom Filter 1: keys=(a, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on plt1_adv_p4 t1
Filter: ((c IS NULL) AND (b < 10))
-(10 rows)
+(8 rows)
SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a;
a | c | a | c
@@ -5337,16 +5015,12 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b))
-> Seq Scan on alpha_neg_p1 t1_1
Filter: ((b >= 125) AND (b < 225))
- Bloom Filter 1: keys=(a, b)
-> Hash
- Bloom Filter 1
-> Seq Scan on beta_neg_p1 t2_1
-> Hash Join
Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-> Seq Scan on beta_neg_p2 t2_2
- Bloom Filter 2: keys=(a, b)
-> Hash
- Bloom Filter 2
-> Seq Scan on alpha_neg_p2 t1_2
Filter: ((b >= 125) AND (b < 225))
-> Hash Join
@@ -5363,7 +5037,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Filter: ((b >= 125) AND (b < 225))
-> Seq Scan on alpha_pos_p3 t1_6
Filter: ((b >= 125) AND (b < 225))
-(33 rows)
+(29 rows)
SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
a | b | c | a | b | c
@@ -5476,18 +5150,14 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-> Seq Scan on alpha_neg_p1 t1_1
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
- Bloom Filter 1: keys=(a, b, c)
-> Hash
- Bloom Filter 1
-> Seq Scan on beta_neg_p1 t2_1
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-> Hash Join
Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
-> Seq Scan on alpha_neg_p2 t1_2
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
- Bloom Filter 2: keys=(a, b, c)
-> Hash
- Bloom Filter 2
-> Seq Scan on beta_neg_p2 t2_2
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-> Nested Loop
@@ -5502,7 +5172,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
-> Seq Scan on beta_pos_p3 t2_4
Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-(33 rows)
+(29 rows)
SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b;
a | b | c | a | b | c
@@ -5646,25 +5316,19 @@ EXPLAIN (COSTS OFF) SELECT * FROM pht1 p1 JOIN pht1 p2 USING (c) LIMIT 1000;
-> Hash Join
Hash Cond: (p1_1.c = p2_1.c)
-> Seq Scan on pht1_p1 p1_1
- Bloom Filter 1: keys=(c)
-> Hash
- Bloom Filter 1
-> Seq Scan on pht1_p1 p2_1
-> Hash Join
Hash Cond: (p1_2.c = p2_2.c)
-> Seq Scan on pht1_p2 p1_2
- Bloom Filter 2: keys=(c)
-> Hash
- Bloom Filter 2
-> Seq Scan on pht1_p2 p2_2
-> Hash Join
Hash Cond: (p1_3.c = p2_3.c)
-> Seq Scan on pht1_p3 p1_3
- Bloom Filter 3: keys=(c)
-> Hash
- Bloom Filter 3
-> Seq Scan on pht1_p3 p2_3
-(23 rows)
+(17 rows)
RESET enable_mergejoin;
SET max_parallel_workers_per_gather = 1;
diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out
index 079f6422fdc..feae77cb840 100644
--- a/src/test/regress/expected/predicate.out
+++ b/src/test/regress/expected/predicate.out
@@ -748,16 +748,14 @@ SELECT id FROM dist_tab WHERE row_nn IS DISTINCT FROM ROW(1, 5)::dist_row_t;
SET enable_nestloop TO off;
EXPLAIN (COSTS OFF)
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
- QUERY PLAN
----------------------------------------
+ QUERY PLAN
+--------------------------------------
Hash Join
Hash Cond: (t1.val_nn = t2.val_nn)
-> Seq Scan on dist_tab t1
- Bloom Filter 1: keys=(val_nn)
-> Hash
- Bloom Filter 1
-> Seq Scan on dist_tab t2
-(7 rows)
+(5 rows)
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
id | val_nn | val_null | row_nn | id | val_nn | val_null | row_nn
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index dc44871b682..50cd3be8030 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -713,33 +713,31 @@ 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
+ Bloom Filter 2: keys=(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
+ Bloom Filter 2
-> 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)
+ 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)
-> Seq Scan on pg_temp.joinme
Output: joinme.ctid, joinme.other, joinme.f2j
- Bloom Filter 2: keys=(joinme.f2j)
+ Bloom Filter 1: keys=(joinme.f2j)
-> Hash
- Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
- 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)
-(31 rows)
+ Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+ Bloom Filter 1
+ -> Nested Loop
+ Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
+ Join Filter: (foo_1.f2 = foo_2.f2)
+ -> 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
+(29 rows)
UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 11cf56fdba4..07854247020 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -3610,17 +3610,16 @@ ANALYZE sb_1, sb_2;
-- bucket size is quite big because there are possibly many correlations.
EXPLAIN (COSTS OFF) -- Choose merge join
SELECT * FROM sb_1 a, sb_2 b WHERE a.x = b.x AND a.y = b.y AND a.z = b.z;
- QUERY PLAN
--------------------------------------------------------------
- Merge Join
- Merge Cond: ((a.z = b.z) AND (a.x = b.x) AND (a.y = b.y))
- -> Sort
- Sort Key: a.z, a.x, a.y
+ QUERY PLAN
+------------------------------------------------------------
+ Hash Join
+ Hash Cond: ((b.x = a.x) AND (b.y = a.y) AND (b.z = a.z))
+ -> Seq Scan on sb_2 b
+ Bloom Filter 1: keys=(x, y, z)
+ -> Hash
+ Bloom Filter 1
-> Seq Scan on sb_1 a
- -> Sort
- Sort Key: b.z, b.x, b.y
- -> Seq Scan on sb_2 b
-(8 rows)
+(7 rows)
-- The ndistinct extended statistics on (x, y, z) provides more reliable value
-- of bucket size.
@@ -3633,11 +3632,9 @@ SELECT * FROM sb_1 a, sb_2 b WHERE a.x = b.x AND a.y = b.y AND a.z = b.z;
Hash Join
Hash Cond: ((a.x = b.x) AND (a.y = b.y) AND (a.z = b.z))
-> Seq Scan on sb_1 a
- Bloom Filter 1: keys=(x, y, z)
-> Hash
- Bloom Filter 1
-> Seq Scan on sb_2 b
-(7 rows)
+(5 rows)
-- Check that the Hash Join bucket size estimator detects equal clauses correctly.
SET enable_nestloop = 'off';
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index 5c7d49050db..3519942030b 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -406,11 +406,9 @@ select * from int4_tbl o where exists
Hash Semi Join
Hash Cond: (o.f1 = i.f1)
-> Seq Scan on int4_tbl o
- Bloom Filter 1: keys=(f1)
-> Hash
- Bloom Filter 1
-> Seq Scan on int4_tbl i
-(7 rows)
+(5 rows)
explain (costs off)
select * from int4_tbl o where not exists
@@ -1673,7 +1671,7 @@ select * from int4_tbl where
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop Semi Join
Output: int4_tbl.f1
- Join Filter: (CASE WHEN (ANY (int4_tbl.f1 = (hashed SubPlan any_1).col1)) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten)
+ Join Filter: (b.ten = CASE WHEN (ANY (int4_tbl.f1 = (hashed SubPlan any_1).col1)) THEN int4_tbl.f1 ELSE NULL::integer END)
-> Seq Scan on public.int4_tbl
Output: int4_tbl.f1
-> Seq Scan on public.tenk1 b
@@ -2180,13 +2178,11 @@ order by t1.ten;
Hash Cond: (t2.fivethous = t1.unique1)
-> Seq Scan on public.tenk1 t2
Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4
- Bloom Filter 1: keys=(t2.fivethous)
-> Hash
Output: t1.ten, t1.unique1
- Bloom Filter 1
-> Seq Scan on public.tenk1 t1
Output: t1.ten, t1.unique1
-(17 rows)
+(15 rows)
select t1.ten, sum(x) from
tenk1 t1 left join lateral (
@@ -2281,19 +2277,15 @@ order by 1, 2;
Hash Cond: (t2.q2 = t3.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q2)
- Bloom Filter 2: keys=(t2.q2)
-> Hash
Output: t3.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t3
Output: t3.q2
-> Hash
Output: t1.q1, t1.q2
- Bloom Filter 2
-> Seq Scan on public.int8_tbl t1
Output: t1.q1, t1.q2
-(23 rows)
+(19 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2336,16 +2328,14 @@ order by 1, 2;
Output: t2.q2, ((t2.q1 + 1))
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q2)
-> Seq Scan on public.int8_tbl t3
Output: t3.q2, (t2.q1 + 1)
Filter: (t2.q2 = t3.q2)
-> Hash
Output: t1.q1, t1.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q1, t1.q2
-(19 rows)
+(17 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2390,19 +2380,15 @@ order by 1, 2;
Hash Cond: (t2.q2 = t3.q1)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q2)
- Bloom Filter 2: keys=(t2.q1)
-> Hash
Output: t3.q1
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t3
Output: t3.q1
-> Hash
Output: t1.q1
- Bloom Filter 2
-> Seq Scan on public.int8_tbl t1
Output: t1.q1
-(23 rows)
+(19 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2455,16 +2441,14 @@ order by 1, 2;
Output: t2.q1, (t2.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q1)
-> Seq Scan on public.int8_tbl t3
Output: t3.q1, t2.q2
Filter: (t2.q2 = t3.q1)
-> Hash
Output: t1.q1
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q1
-(19 rows)
+(17 rows)
select t1.q1, x from
int8_tbl t1 left join
@@ -2528,21 +2512,19 @@ order by 1, 2, 3;
Hash Cond: (t2.q1 = t3.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Bloom Filter 1: keys=(t2.q2)
-> Hash
Output: t3.q2, (COALESCE(t3.q1, t3.q1))
-> Seq Scan on public.int8_tbl t3
Output: t3.q2, COALESCE(t3.q1, t3.q1)
-> Hash
Output: t4.q1, t4.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t4
Output: t4.q1, t4.q2
-> Hash
Output: t1.q2
-> Seq Scan on public.int8_tbl t1
Output: t1.q2
-(28 rows)
+(26 rows)
select ss2.* from
int8_tbl t1 left join
@@ -2610,13 +2592,11 @@ order by 1, 2, 3;
Output: t3.q2, COALESCE(t3.q1, t3.q1)
-> Seq Scan on public.int8_tbl t4
Output: t4.q1, t4.q2, (COALESCE(t3.q1, t3.q1))
- Bloom Filter 1: keys=(t4.q1)
-> Hash
Output: t1.q2
- Bloom Filter 1
-> Seq Scan on public.int8_tbl t1
Output: t1.q2
-(26 rows)
+(24 rows)
select ss2.* from
int8_tbl t1 left join
@@ -2917,13 +2897,11 @@ select * from tenk1 A where hundred in (select hundred from tenk2 B where B.odd
Hash Join
Hash Cond: ((a.odd = b.odd) AND (a.hundred = b.hundred))
-> Seq Scan on tenk1 a
- Bloom Filter 1: keys=(odd, hundred)
-> Hash
- Bloom Filter 1
-> HashAggregate
Group Key: b.odd, b.hundred
-> Seq Scan on tenk2 b
-(9 rows)
+(7 rows)
explain (costs off)
select * from tenk1 A where exists
@@ -2988,13 +2966,11 @@ ON B.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = b.odd);
-> Hash Join
Hash Cond: ((b.odd = c.odd) AND (b.hundred = c.hundred))
-> Seq Scan on tenk2 b
- Bloom Filter 1: keys=(odd, hundred)
-> Hash
- Bloom Filter 1
-> HashAggregate
Group Key: c.odd, c.hundred
-> Seq Scan on tenk2 c
-(12 rows)
+(10 rows)
-- we can pull up the sublink into the inner JoinExpr.
explain (costs off)
@@ -3009,15 +2985,13 @@ WHERE a.thousand < 750;
Hash Cond: (a.hundred = c.hundred)
-> Seq Scan on tenk1 a
Filter: (thousand < 750)
- Bloom Filter 1: keys=(hundred)
-> Hash
- Bloom Filter 1
-> HashAggregate
Group Key: c.odd, c.hundred
-> Seq Scan on tenk2 c
-> Hash
-> Seq Scan on tenk2 b
-(14 rows)
+(12 rows)
-- we can pull up the aggregate sublink into RHS of a left join.
explain (costs off)
@@ -3154,11 +3128,9 @@ WHERE a.ten IN (VALUES (1), (2));
Hash Cond: (a.ten = c.ten)
-> Seq Scan on onek a
Filter: (ten = ANY ('{1,2}'::integer[]))
- Bloom Filter 1: keys=(ten)
-> Hash
- Bloom Filter 1
-> Seq Scan on tenk1 c
-(8 rows)
+(6 rows)
EXPLAIN (COSTS OFF)
SELECT c.unique1,c.ten FROM tenk1 c JOIN onek a USING (ten)
@@ -3169,11 +3141,9 @@ WHERE c.ten IN (VALUES (1), (2));
Hash Cond: (c.ten = a.ten)
-> Seq Scan on tenk1 c
Filter: (ten = ANY ('{1,2}'::integer[]))
- Bloom Filter 1: keys=(ten)
-> Hash
- Bloom Filter 1
-> Seq Scan on onek a
-(8 rows)
+(6 rows)
-- Constant expressions are simplified
EXPLAIN (COSTS OFF)
@@ -3494,15 +3464,14 @@ WHERE id NOT IN (
Hash Cond: (not_null_tab.id = t2.id)
-> Seq Scan on not_null_tab
-> Hash
- -> Merge Join
- Merge Cond: (t1.id = t2.id)
- -> Sort
- Sort Key: t1.id
- -> Seq Scan on not_null_tab t1
- -> Sort
- Sort Key: t2.id
+ -> Hash Join
+ Hash Cond: (t1.id = t2.id)
+ -> Seq Scan on not_null_tab t1
+ Bloom Filter 1: keys=(id)
+ -> Hash
+ Bloom Filter 1
-> Seq Scan on not_null_tab t2
-(12 rows)
+(11 rows)
-- ANTI JOIN: outer side is defined NOT NULL, inner side is forced nonnullable
-- by qual clause
@@ -3543,11 +3512,8 @@ WHERE id NOT IN (
);
QUERY PLAN
-------------------------------------------------
- Merge Anti Join
- Merge Cond: (not_null_tab.id = t1.id)
- -> Sort
- Sort Key: not_null_tab.id
- -> Seq Scan on not_null_tab
+ Merge Right Anti Join
+ Merge Cond: (t1.id = not_null_tab.id)
-> Nested Loop Left Join
-> Merge Join
Merge Cond: (t1.id = t2.id)
@@ -3559,6 +3525,9 @@ WHERE id NOT IN (
-> Seq Scan on null_tab t2
-> Materialize
-> Seq Scan on null_tab t3
+ -> Sort
+ Sort Key: not_null_tab.id
+ -> Seq Scan on not_null_tab
(16 rows)
-- ANTI JOIN: outer side is defined NOT NULL and is not nulled by outer join,
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 7d4af80faf6..13025cf93c5 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -623,13 +623,11 @@ MERGE INTO rw_view1 t
Hash Cond: (base_tbl.a = generate_series.generate_series)
-> Bitmap Heap Scan on base_tbl
Recheck Cond: (a > 0)
- Bloom Filter 1: keys=(a)
-> Bitmap Index Scan on base_tbl_pkey
Index Cond: (a > 0)
-> Hash
- Bloom Filter 1
-> Function Scan on generate_series
-(11 rows)
+(9 rows)
-- it's still updatable if we add a DO ALSO rule
CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
@@ -3528,18 +3526,17 @@ EXPLAIN (COSTS OFF) UPDATE v2 SET a = 1;
Update on t1
InitPlan exists_1
-> Result
- -> Merge Join
- Merge Cond: (t1.a = v1.a)
- -> Sort
- Sort Key: t1.a
- -> Seq Scan on t1
- -> Sort
- Sort Key: v1.a
+ -> Hash Join
+ Hash Cond: (t1.a = v1.a)
+ -> Seq Scan on t1
+ Bloom Filter 1: keys=(a)
+ -> Hash
+ Bloom Filter 1
-> Subquery Scan on v1
-> Result
One-Time Filter: (InitPlan exists_1).col1
-> Seq Scan on t1 t1_1
-(14 rows)
+(13 rows)
DROP VIEW v2;
DROP VIEW v1;
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 5c86619f023..dfda848b13c 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4324,15 +4324,14 @@ WHERE s.c = 1;
Run Condition: (ntile(e2.salary) OVER w1 <= 1)
-> Sort
Sort Key: e1.depname
- -> Merge Join
- Merge Cond: (e1.empno = e2.empno)
- -> Sort
- Sort Key: e1.empno
- -> Seq Scan on empsalary e1
- -> Sort
- Sort Key: e2.empno
+ -> Hash Join
+ Hash Cond: (e1.empno = e2.empno)
+ -> Seq Scan on empsalary e1
+ Bloom Filter 1: keys=(empno)
+ -> Hash
+ Bloom Filter 1
-> Seq Scan on empsalary e2
-(15 rows)
+(14 rows)
-- Ensure the run condition optimization is used in cases where the WindowFunc
-- has a Var from another query level
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index db8c77721e7..25262b08839 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -686,11 +686,9 @@ select count(*) from tenk1 a
-> Hash Semi Join
Hash Cond: (a.unique1 = x.unique1)
-> Index Only Scan using tenk1_unique1 on tenk1 a
- Bloom Filter 1: keys=(unique1)
-> Hash
- Bloom Filter 1
-> CTE Scan on x
-(10 rows)
+(8 rows)
explain (costs off)
with x as materialized (insert into tenk1 default values returning unique1)
@@ -753,22 +751,20 @@ select * from search_graph order by seq;
-> Recursive Union
-> Seq Scan on pg_temp.graph0 g
Output: g.f, g.t, g.label, ARRAY[ROW(g.f, g.t)]
- -> Merge Join
+ -> Hash Join
Output: g_1.f, g_1.t, g_1.label, array_cat(sg.seq, ARRAY[ROW(g_1.f, g_1.t)])
- Merge Cond: (g_1.f = sg.t)
- -> Sort
+ Hash Cond: (g_1.f = sg.t)
+ -> Seq Scan on pg_temp.graph0 g_1
Output: g_1.f, g_1.t, g_1.label
- Sort Key: g_1.f
- -> Seq Scan on pg_temp.graph0 g_1
- Output: g_1.f, g_1.t, g_1.label
- -> Sort
+ Bloom Filter 1: keys=(g_1.f)
+ -> Hash
Output: sg.seq, sg.t
- Sort Key: sg.t
+ Bloom Filter 1
-> WorkTable Scan on search_graph sg
Output: sg.seq, sg.t
-> CTE Scan on search_graph
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
-(22 rows)
+(20 rows)
with recursive search_graph(f, t, label) as (
select * from graph0 g
@@ -826,22 +822,20 @@ select * from search_graph order by seq;
-> Recursive Union
-> Seq Scan on pg_temp.graph0 g
Output: g.f, g.t, g.label, ROW('0'::bigint, g.f, g.t)
- -> Merge Join
+ -> Hash Join
Output: g_1.f, g_1.t, g_1.label, ROW(int8inc((sg.seq)."*DEPTH*"), g_1.f, g_1.t)
- Merge Cond: (g_1.f = sg.t)
- -> Sort
+ Hash Cond: (g_1.f = sg.t)
+ -> Seq Scan on pg_temp.graph0 g_1
Output: g_1.f, g_1.t, g_1.label
- Sort Key: g_1.f
- -> Seq Scan on pg_temp.graph0 g_1
- Output: g_1.f, g_1.t, g_1.label
- -> Sort
+ Bloom Filter 1: keys=(g_1.f)
+ -> Hash
Output: sg.seq, sg.t
- Sort Key: sg.t
+ Bloom Filter 1
-> WorkTable Scan on search_graph sg
Output: sg.seq, sg.t
-> CTE Scan on search_graph
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
-(22 rows)
+(20 rows)
with recursive search_graph(f, t, label) as (
select * from graph0 g
@@ -1097,20 +1091,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1135,20 +1129,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1210,21 +1204,19 @@ select * from search_graph;
-> Recursive Union
-> Seq Scan on pg_temp.graph g
Output: g.f, g.t, g.label, false, ARRAY[ROW(g.f, g.t)]
- -> Merge Join
+ -> Hash Join
Output: g_1.f, g_1.t, g_1.label, CASE WHEN (ROW(g_1.f, g_1.t) = ANY (sg.path)) THEN true ELSE false END, array_cat(sg.path, ARRAY[ROW(g_1.f, g_1.t)])
- Merge Cond: (g_1.f = sg.t)
- -> Sort
+ Hash Cond: (g_1.f = sg.t)
+ -> Seq Scan on pg_temp.graph g_1
Output: g_1.f, g_1.t, g_1.label
- Sort Key: g_1.f
- -> Seq Scan on pg_temp.graph g_1
- Output: g_1.f, g_1.t, g_1.label
- -> Sort
+ Bloom Filter 1: keys=(g_1.f)
+ -> Hash
Output: sg.path, sg.t
- Sort Key: sg.t
+ Bloom Filter 1
-> WorkTable Scan on search_graph sg
Output: sg.path, sg.t
Filter: (NOT sg.is_cycle)
-(20 rows)
+(18 rows)
with recursive search_graph(f, t, label) as (
select * from graph g
@@ -1244,20 +1236,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1281,20 +1273,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | N | {"(5,1)"}
1 | 2 | arc 1 -> 2 | N | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | N | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | N | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | N | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | N | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | N | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | N | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | N | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | N | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | N | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | N | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | N | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | Y | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | Y | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1446,20 +1438,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | {"(5,1)"} | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1484,20 +1476,20 @@ select * from search_graph;
5 | 1 | arc 5 -> 1 | (0,5,1) | f | {"(5,1)"}
1 | 2 | arc 1 -> 2 | (1,1,2) | f | {"(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | (1,1,3) | f | {"(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | (1,2,3) | f | {"(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | (1,4,5) | f | {"(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | (1,5,1) | f | {"(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | (2,1,2) | f | {"(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | (2,1,3) | f | {"(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | (2,2,3) | f | {"(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | (2,4,5) | f | {"(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | (2,5,1) | f | {"(1,4)","(4,5)","(5,1)"}
1 | 2 | arc 1 -> 2 | (3,1,2) | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
1 | 3 | arc 1 -> 3 | (3,1,3) | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
- 1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
2 | 3 | arc 2 -> 3 | (3,2,3) | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
+ 1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
4 | 5 | arc 4 -> 5 | (3,4,5) | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
5 | 1 | arc 5 -> 1 | (3,5,1) | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
2 | 3 | arc 2 -> 3 | (4,2,3) | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
@@ -1677,20 +1669,20 @@ select * from v_cycle1;
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
2 | 3 | arc 2 -> 3
@@ -1707,20 +1699,20 @@ select * from v_cycle2;
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
1 | 2 | arc 1 -> 2
1 | 3 | arc 1 -> 3
- 1 | 4 | arc 1 -> 4
2 | 3 | arc 2 -> 3
+ 1 | 4 | arc 1 -> 4
4 | 5 | arc 4 -> 5
5 | 1 | arc 5 -> 1
2 | 3 | arc 2 -> 3
@@ -3248,10 +3240,8 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
Hash Cond: (m.k = o.k)
-> Seq Scan on public.m
Output: m.ctid, m.k
- Bloom Filter 1: keys=(m.k)
-> Hash
Output: o.k, o.v, o.*
- Bloom Filter 1
-> Subquery Scan on o
Output: o.k, o.v, o.*
-> Result
@@ -3262,7 +3252,7 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
-> CTE Scan on cte_basic
Output: (cte_basic.b || ' merge update'::text)
Filter: (cte_basic.a = m.k)
-(23 rows)
+(21 rows)
-- InitPlan
WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b)
@@ -3299,15 +3289,13 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
Hash Cond: (m.k = o.k)
-> Seq Scan on public.m
Output: m.ctid, m.k
- Bloom Filter 1: keys=(m.k)
-> Hash
Output: o.k, o.v, o.*
- Bloom Filter 1
-> Subquery Scan on o
Output: o.k, o.v, o.*
-> Result
Output: 1, 'merge source InitPlan'::text
-(23 rows)
+(21 rows)
-- MERGE source comes from CTE:
WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b)
@@ -3345,13 +3333,11 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text
Hash Cond: (m.k = merge_source_cte.a)
-> Seq Scan on public.m
Output: m.ctid, m.k
- Bloom Filter 1: keys=(m.k)
-> Hash
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
- Bloom Filter 1
-> CTE Scan on merge_source_cte
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
-(22 rows)
+(20 rows)
DROP TABLE m;
-- check that run to completion happens in proper ordering
--
2.50.1 (Apple Git-155)
[text/plain] v4-0003-Add-tests-for-CustomScan-filter-pushdown.patch (23.1K, ../[email protected]/4-v4-0003-Add-tests-for-CustomScan-filter-pushdown.patch)
download | inline diff:
From 81b42bfbd75d2a466c6119a92a1bb206081ae165 Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <[email protected]>
Date: Thu, 2 Jul 2026 15:46:05 -0300
Subject: [PATCH v4 3/4] Add tests for CustomScan filter pushdown
---
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../modules/test_bloom_customscan/Makefile | 23 ++
.../expected/test_bloom_customscan.out | 113 ++++++
.../modules/test_bloom_customscan/meson.build | 33 ++
.../sql/test_bloom_customscan.sql | 66 ++++
.../test_bloom_customscan--1.0.sql | 20 +
.../test_bloom_customscan.c | 362 ++++++++++++++++++
.../test_bloom_customscan.control | 5 +
9 files changed, 624 insertions(+)
create mode 100644 src/test/modules/test_bloom_customscan/Makefile
create mode 100644 src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
create mode 100644 src/test/modules/test_bloom_customscan/meson.build
create mode 100644 src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql
create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql
create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan.c
create mode 100644 src/test/modules/test_bloom_customscan/test_bloom_customscan.control
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 0a74ab5c86f..3f83feeb074 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -20,6 +20,7 @@ SUBDIRS = \
test_binaryheap \
test_bitmapset \
test_bloomfilter \
+ test_bloom_customscan \
test_cloexec \
test_checksums \
test_copy_callbacks \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 4bca42bb370..e9efdb1e8a4 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -20,6 +20,7 @@ subdir('test_autovacuum')
subdir('test_binaryheap')
subdir('test_bitmapset')
subdir('test_bloomfilter')
+subdir('test_bloom_customscan')
subdir('test_cloexec')
subdir('test_checksums')
subdir('test_copy_callbacks')
diff --git a/src/test/modules/test_bloom_customscan/Makefile b/src/test/modules/test_bloom_customscan/Makefile
new file mode 100644
index 00000000000..5de5b5e6f4b
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_bloom_customscan/Makefile
+
+MODULE_big = test_bloom_customscan
+OBJS = \
+ $(WIN32RES) \
+ test_bloom_customscan.o
+PGFILEDESC = "test_bloom_customscan - CustomScan consuming a hashjoin bloom filter"
+
+EXTENSION = test_bloom_customscan
+DATA = test_bloom_customscan--1.0.sql
+
+REGRESS = test_bloom_customscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_bloom_customscan
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
new file mode 100644
index 00000000000..e1ce5903a02
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
@@ -0,0 +1,113 @@
+CREATE EXTENSION test_bloom_customscan;
+-- ensure the library (and its planner hook + GUC) is loaded
+LOAD 'test_bloom_customscan';
+-- Force a serial hash join, which is where the bloom-filter pushdown applies.
+SET enable_hashjoin_bloom = on;
+SET enable_mergejoin = off;
+SET enable_nestloop = off;
+SET max_parallel_workers_per_gather = 0;
+CREATE TABLE cs_fact (a int, b int, payload int);
+CREATE TABLE cs_dim (id int, id2 int, label text);
+-- Fact keys range over 0..999; the dimension only covers 1..50, so the join
+-- is highly selective and the pushed-down filter should reject most fact rows.
+INSERT INTO cs_fact
+ SELECT g % 1000, g % 1000, g FROM generate_series(1, 20000) g;
+INSERT INTO cs_dim
+ SELECT g, g, 'x' || g FROM generate_series(1, 50) g;
+ANALYZE cs_fact;
+ANALYZE cs_dim;
+-- Offer the bloom-capable custom scan and force it to be chosen (the CustomPath
+-- keeps disabled_nodes = 0, so disabling seqscan makes it win).
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+-- The fact relation is scanned by our Custom Scan and receives the filter.
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
+ QUERY PLAN
+------------------------------------------------------------------------
+ Aggregate
+ Output: count(*)
+ -> Hash Join
+ Hash Cond: (f.a = d.id)
+ -> Custom Scan (TestBloomCustomScan) on public.cs_fact f
+ Output: f.a
+ -> Hash
+ Output: d.id
+ -> Custom Scan (TestBloomCustomScan) on public.cs_dim d
+ Output: d.id
+(10 rows)
+
+-- Single-key join: the filter must actually reject fact rows.
+SELECT test_bloom_cs_reset();
+ test_bloom_cs_reset
+---------------------
+
+(1 row)
+
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
+ count
+-------
+ 1000
+(1 row)
+
+SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
+ filter_rejected_rows
+----------------------
+ f
+(1 row)
+
+-- Correctness: the result must be identical with and without the filter.
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+CREATE TEMP TABLE r_cs AS
+ SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a;
+SET test_bloom_customscan.enable = off;
+SET enable_seqscan = on;
+CREATE TEMP TABLE r_plain AS
+ SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a;
+SELECT count(*) AS cs_minus_plain
+ FROM (SELECT * FROM r_cs EXCEPT SELECT * FROM r_plain) x;
+ cs_minus_plain
+----------------
+ 0
+(1 row)
+
+SELECT count(*) AS plain_minus_cs
+ FROM (SELECT * FROM r_plain EXCEPT SELECT * FROM r_cs) x;
+ plain_minus_cs
+----------------
+ 0
+(1 row)
+
+-- Two-key join: the opted-in recipient also gets per-key filters built.
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+SELECT test_bloom_cs_reset();
+ test_bloom_cs_reset
+---------------------
+
+(1 row)
+
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id AND f.b = d.id2;
+ count
+-------
+ 1000
+(1 row)
+
+SELECT test_bloom_cs_perkey_built() AS perkey_filters_built;
+ perkey_filters_built
+----------------------
+ f
+(1 row)
+
+SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
+ filter_rejected_rows
+----------------------
+ f
+(1 row)
+
+-- cleanup
+SET test_bloom_customscan.enable = off;
+SET enable_seqscan = on;
+DROP TABLE cs_fact, cs_dim;
+DROP EXTENSION test_bloom_customscan;
diff --git a/src/test/modules/test_bloom_customscan/meson.build b/src/test/modules/test_bloom_customscan/meson.build
new file mode 100644
index 00000000000..e12dc80942c
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2026, PostgreSQL Global Development Group
+
+test_bloom_customscan_sources = files(
+ 'test_bloom_customscan.c',
+)
+
+if host_system == 'windows'
+ test_bloom_customscan_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_bloom_customscan',
+ '--FILEDESC', 'test_bloom_customscan - CustomScan consuming a hashjoin bloom filter',])
+endif
+
+test_bloom_customscan = shared_module('test_bloom_customscan',
+ test_bloom_customscan_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_bloom_customscan
+
+test_install_data += files(
+ 'test_bloom_customscan.control',
+ 'test_bloom_customscan--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_bloom_customscan',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_bloom_customscan',
+ ],
+ },
+}
diff --git a/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql b/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql
new file mode 100644
index 00000000000..daae84a531a
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/sql/test_bloom_customscan.sql
@@ -0,0 +1,66 @@
+CREATE EXTENSION test_bloom_customscan;
+-- ensure the library (and its planner hook + GUC) is loaded
+LOAD 'test_bloom_customscan';
+
+-- Force a serial hash join, which is where the bloom-filter pushdown applies.
+SET enable_hashjoin_bloom = on;
+SET enable_mergejoin = off;
+SET enable_nestloop = off;
+SET max_parallel_workers_per_gather = 0;
+
+CREATE TABLE cs_fact (a int, b int, payload int);
+CREATE TABLE cs_dim (id int, id2 int, label text);
+
+-- Fact keys range over 0..999; the dimension only covers 1..50, so the join
+-- is highly selective and the pushed-down filter should reject most fact rows.
+INSERT INTO cs_fact
+ SELECT g % 1000, g % 1000, g FROM generate_series(1, 20000) g;
+INSERT INTO cs_dim
+ SELECT g, g, 'x' || g FROM generate_series(1, 50) g;
+
+ANALYZE cs_fact;
+ANALYZE cs_dim;
+
+-- Offer the bloom-capable custom scan and force it to be chosen (the CustomPath
+-- keeps disabled_nodes = 0, so disabling seqscan makes it win).
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+
+-- The fact relation is scanned by our Custom Scan and receives the filter.
+EXPLAIN (COSTS OFF, VERBOSE)
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
+
+-- Single-key join: the filter must actually reject fact rows.
+SELECT test_bloom_cs_reset();
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
+SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
+
+-- Correctness: the result must be identical with and without the filter.
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+CREATE TEMP TABLE r_cs AS
+ SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a;
+
+SET test_bloom_customscan.enable = off;
+SET enable_seqscan = on;
+CREATE TEMP TABLE r_plain AS
+ SELECT f.a, count(*) AS n FROM cs_fact f JOIN cs_dim d ON f.a = d.id GROUP BY f.a;
+
+SELECT count(*) AS cs_minus_plain
+ FROM (SELECT * FROM r_cs EXCEPT SELECT * FROM r_plain) x;
+SELECT count(*) AS plain_minus_cs
+ FROM (SELECT * FROM r_plain EXCEPT SELECT * FROM r_cs) x;
+
+-- Two-key join: the opted-in recipient also gets per-key filters built.
+SET test_bloom_customscan.enable = on;
+SET enable_seqscan = off;
+SELECT test_bloom_cs_reset();
+SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id AND f.b = d.id2;
+SELECT test_bloom_cs_perkey_built() AS perkey_filters_built;
+SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
+
+-- cleanup
+SET test_bloom_customscan.enable = off;
+SET enable_seqscan = on;
+DROP TABLE cs_fact, cs_dim;
+DROP EXTENSION test_bloom_customscan;
diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql b/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql
new file mode 100644
index 00000000000..329d795d6f0
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql
@@ -0,0 +1,20 @@
+/* src/test/modules/test_bloom_customscan/test_bloom_customscan--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_bloom_customscan" to load this file. \quit
+
+CREATE FUNCTION test_bloom_cs_reset()
+ RETURNS void
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bloom_cs_scanned_rows()
+ RETURNS bigint
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bloom_cs_rejected_rows()
+ RETURNS bigint
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION test_bloom_cs_perkey_built()
+ RETURNS boolean
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan.c b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
new file mode 100644
index 00000000000..124cbef9ded
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
@@ -0,0 +1,362 @@
+/*-------------------------------------------------------------------------
+ *
+ * test_bloom_customscan.c
+ * Minimal CustomScan provider that consumes a hash-join Bloom filter.
+ *
+ * This is a test-only stand-in for a storage-level scan provider. When enabled, it
+ * installs a set_rel_pathlist_hook that offers a CustomPath for plain heap
+ * base relations, advertising CUSTOMPATH_SUPPORT_BLOOM_FILTERS. That opt-in
+ * is what lets the planner (a) generate filter-bearing paths for the scan and
+ * (b) push a hash-join Bloom filter down to it.
+ *
+ * The scan itself is an ordinary heap sequential scan; the only interesting
+ * part is that its per-tuple loop probes the pushed-down combined filter with
+ * ExecBloomFilters() -- exactly what a stock scan does inside ExecScanExtended,
+ * but here done by the provider itself -- and, for a multi-key join, checks
+ * that the per-key filters were populated on the producer side.
+ *
+ * A few SQL-callable functions expose counters so the regression test can
+ * assert that the filter was actually probed and rejected tuples, and that
+ * per-key filters were built, without depending on timing or EXPLAIN output.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/modules/test_bloom_customscan/test_bloom_customscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/relscan.h"
+#include "access/tableam.h"
+#include "executor/executor.h"
+#include "fmgr.h"
+#include "nodes/execnodes.h"
+#include "nodes/extensible.h"
+#include "nodes/pathnodes.h"
+#include "nodes/plannodes.h"
+#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
+#include "optimizer/restrictinfo.h"
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+/* GUC: when off, the hook does nothing and plans look exactly like upstream. */
+static bool test_bloom_cs_enabled = false;
+
+/* Observation counters, reset/read from SQL. */
+static uint64 test_bloom_cs_scanned = 0;
+static uint64 test_bloom_cs_rejected = 0;
+static bool test_bloom_cs_perkey_seen = false;
+
+static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook = NULL;
+
+/* Execution state, embedding CustomScanState as its first field. */
+typedef struct BloomCSScanState
+{
+ CustomScanState css;
+ TableScanDesc scandesc;
+ bool inspected; /* have we checked per-key filters yet? */
+} BloomCSScanState;
+
+/* forward declarations */
+static Plan *bloom_cs_plan_custom_path(PlannerInfo *root, RelOptInfo *rel,
+ CustomPath *best_path, List *tlist,
+ List *clauses, List *custom_plans);
+static Node *bloom_cs_create_scan_state(CustomScan *cscan);
+static void bloom_cs_begin_scan(CustomScanState *node, EState *estate,
+ int eflags);
+static TupleTableSlot *bloom_cs_exec_scan(CustomScanState *node);
+static void bloom_cs_end_scan(CustomScanState *node);
+static void bloom_cs_rescan(CustomScanState *node);
+
+static const CustomPathMethods bloom_cs_path_methods = {
+ .CustomName = "TestBloomCustomScan",
+ .PlanCustomPath = bloom_cs_plan_custom_path,
+};
+
+static const CustomScanMethods bloom_cs_scan_methods = {
+ .CustomName = "TestBloomCustomScan",
+ .CreateCustomScanState = bloom_cs_create_scan_state,
+};
+
+static const CustomExecMethods bloom_cs_exec_methods = {
+ .CustomName = "TestBloomCustomScan",
+ .BeginCustomScan = bloom_cs_begin_scan,
+ .ExecCustomScan = bloom_cs_exec_scan,
+ .EndCustomScan = bloom_cs_end_scan,
+ .ReScanCustomScan = bloom_cs_rescan,
+};
+
+/*
+ * set_rel_pathlist_hook: offer a bloom-filter-capable CustomPath for plain
+ * heap base relations. We copy the cost of the existing sequential scan path
+ * so join costing stays sane; the test forces the custom scan to be chosen
+ * with "SET enable_seqscan = off" (the CustomPath keeps disabled_nodes = 0).
+ */
+static void
+bloom_cs_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti,
+ RangeTblEntry *rte)
+{
+ CustomPath *cpath;
+ Cost startup_cost = 0;
+ Cost total_cost = 0;
+ ListCell *lc;
+
+ if (prev_set_rel_pathlist_hook)
+ prev_set_rel_pathlist_hook(root, rel, rti, rte);
+
+ if (!test_bloom_cs_enabled)
+ return;
+
+ /* Only plain, ordinary base tables. */
+ if (rel->reloptkind != RELOPT_BASEREL)
+ return;
+ if (rte->rtekind != RTE_RELATION || rte->relkind != RELKIND_RELATION)
+ return;
+
+ /* Borrow the sequential scan's cost estimate. */
+ foreach(lc, rel->pathlist)
+ {
+ Path *path = (Path *) lfirst(lc);
+
+ if (path->pathtype == T_SeqScan && path->param_info == NULL)
+ {
+ startup_cost = path->startup_cost;
+ total_cost = path->total_cost;
+ break;
+ }
+ }
+
+ cpath = makeNode(CustomPath);
+ cpath->path.pathtype = T_CustomScan;
+ cpath->path.parent = rel;
+ cpath->path.pathtarget = rel->reltarget;
+ cpath->path.param_info = NULL;
+ cpath->path.parallel_aware = false;
+ cpath->path.parallel_safe = false;
+ cpath->path.parallel_workers = 0;
+ cpath->path.rows = rel->rows;
+ cpath->path.startup_cost = startup_cost;
+ cpath->path.total_cost = total_cost;
+ cpath->path.pathkeys = NIL;
+
+ cpath->custom_paths = NIL;
+ cpath->custom_private = NIL;
+ cpath->methods = &bloom_cs_path_methods;
+
+ add_path(rel, (Path *) cpath);
+}
+
+static Plan *
+bloom_cs_plan_custom_path(PlannerInfo *root, RelOptInfo *rel,
+ CustomPath *best_path, List *tlist,
+ List *clauses, List *custom_plans)
+{
+ CustomScan *cscan = makeNode(CustomScan);
+
+ cscan->flags = best_path->flags;
+ cscan->methods = &bloom_cs_scan_methods;
+ cscan->custom_plans = custom_plans;
+
+ /* A base-relation scan: leave custom_scan_tlist NIL, use rel's rowtype. */
+ cscan->scan.scanrelid = rel->relid;
+ cscan->scan.plan.targetlist = tlist;
+ cscan->scan.plan.qual = extract_actual_clauses(clauses, false);
+
+ return &cscan->scan.plan;
+}
+
+static Node *
+bloom_cs_create_scan_state(CustomScan *cscan)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) newNode(sizeof(BloomCSScanState),
+ T_CustomScanState);
+
+ bcss->css.methods = &bloom_cs_exec_methods;
+ bcss->scandesc = NULL;
+ bcss->inspected = false;
+
+ /*
+ * We scan a heap table with table_scan_getnextslot(), which stores
+ * on-disk heap tuples, so the scan tuple slot must be a buffer-heap slot
+ * rather than the default virtual slot. ExecInitCustomScan() reads
+ * css.slotOps when building ss_ScanTupleSlot (before BeginCustomScan
+ * runs), so it has to be set here.
+ */
+ bcss->css.slotOps = &TTSOpsBufferHeapTuple;
+
+ return (Node *) bcss;
+}
+
+static void
+bloom_cs_begin_scan(CustomScanState *node, EState *estate, int eflags)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) node;
+
+ /*
+ * ExecInitCustomScan already opened the scan relation (scanrelid > 0) and
+ * called ExecInitBloomFilters(), so node->ss.ps.bloom_filters is set up.
+ * We just need a table scan descriptor.
+ */
+ if (!(eflags & (EXEC_FLAG_EXPLAIN_ONLY | EXEC_FLAG_EXPLAIN_GENERIC)))
+ bcss->scandesc = table_beginscan(node->ss.ss_currentRelation,
+ estate->es_snapshot, 0, NULL, 0);
+}
+
+/*
+ * On the first tuple (by which point an eager producer has built its hash
+ * table and filters), record whether the per-key filters were populated.
+ */
+static void
+bloom_cs_maybe_inspect_perkey(BloomCSScanState * bcss)
+{
+ ListCell *lc;
+
+ if (bcss->inspected)
+ return;
+
+ foreach(lc, bcss->css.ss.ps.bloom_filters)
+ {
+ BloomFilterState *bfs = (BloomFilterState *) lfirst(lc);
+ HashJoinState *producer = bfs->producer;
+ HashState *hashNode;
+
+ if (producer == NULL)
+ continue;
+ hashNode = castNode(HashState, innerPlanState(&producer->js.ps));
+
+ /* Hash table (and filters) not built yet; try again next tuple. */
+ if (hashNode->bloom_filter == NULL)
+ return;
+
+ }
+
+ bcss->inspected = true;
+}
+
+static TupleTableSlot *
+bloom_cs_exec_scan(CustomScanState *node)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) node;
+ ExprContext *econtext = node->ss.ps.ps_ExprContext;
+ TupleTableSlot *scanslot = node->ss.ss_ScanTupleSlot;
+ ExprState *qual = node->ss.ps.qual;
+ ProjectionInfo *proj = node->ss.ps.ps_ProjInfo;
+
+ bloom_cs_maybe_inspect_perkey(bcss);
+
+ for (;;)
+ {
+ ResetExprContext(econtext);
+
+ if (!table_scan_getnextslot(bcss->scandesc, ForwardScanDirection,
+ scanslot))
+ {
+ if (proj)
+ return ExecClearTuple(proj->pi_state.resultslot);
+ return ExecClearTuple(scanslot);
+ }
+
+ test_bloom_cs_scanned++;
+ econtext->ecxt_scantuple = scanslot;
+
+ /*
+ * Probe the pushed-down combined Bloom filter, just as a stock scan
+ * would inside ExecScanExtended. A conclusive miss lets us drop the
+ * tuple without evaluating the qual or projection.
+ */
+ if (!ExecBloomFilters(node->ss.ps.bloom_filters, econtext))
+ {
+ test_bloom_cs_rejected++;
+ continue;
+ }
+
+ if (qual != NULL && !ExecQual(qual, econtext))
+ continue;
+
+ if (proj != NULL)
+ return ExecProject(proj);
+
+ return scanslot;
+ }
+}
+
+static void
+bloom_cs_end_scan(CustomScanState *node)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) node;
+
+ if (bcss->scandesc != NULL)
+ table_endscan(bcss->scandesc);
+}
+
+static void
+bloom_cs_rescan(CustomScanState *node)
+{
+ BloomCSScanState *bcss = (BloomCSScanState *) node;
+
+ bcss->inspected = false;
+ if (bcss->scandesc != NULL)
+ table_rescan(bcss->scandesc, NULL);
+}
+
+/* ------------------------------------------------------------------------
+ * SQL-callable observation helpers
+ * ------------------------------------------------------------------------
+ */
+PG_FUNCTION_INFO_V1(test_bloom_cs_reset);
+PG_FUNCTION_INFO_V1(test_bloom_cs_scanned_rows);
+PG_FUNCTION_INFO_V1(test_bloom_cs_rejected_rows);
+PG_FUNCTION_INFO_V1(test_bloom_cs_perkey_built);
+
+Datum
+test_bloom_cs_reset(PG_FUNCTION_ARGS)
+{
+ test_bloom_cs_scanned = 0;
+ test_bloom_cs_rejected = 0;
+ test_bloom_cs_perkey_seen = false;
+ PG_RETURN_VOID();
+}
+
+Datum
+test_bloom_cs_scanned_rows(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64((int64) test_bloom_cs_scanned);
+}
+
+Datum
+test_bloom_cs_rejected_rows(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_INT64((int64) test_bloom_cs_rejected);
+}
+
+Datum
+test_bloom_cs_perkey_built(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(test_bloom_cs_perkey_seen);
+}
+
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("test_bloom_customscan.enable",
+ "Offer a bloom-filter-capable CustomScan for heap tables.",
+ NULL,
+ &test_bloom_cs_enabled,
+ false,
+ PGC_USERSET,
+ 0,
+ NULL, NULL, NULL);
+
+ MarkGUCPrefixReserved("test_bloom_customscan");
+
+ RegisterCustomScanMethods(&bloom_cs_scan_methods);
+
+ prev_set_rel_pathlist_hook = set_rel_pathlist_hook;
+ set_rel_pathlist_hook = bloom_cs_set_rel_pathlist;
+}
diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan.control b/src/test/modules/test_bloom_customscan/test_bloom_customscan.control
new file mode 100644
index 00000000000..10f90510302
--- /dev/null
+++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan.control
@@ -0,0 +1,5 @@
+# test_bloom_customscan extension
+comment = 'Test CustomScan provider that consumes a hashjoin bloom filter'
+default_version = '1.0'
+module_pathname = '$libdir/test_bloom_customscan'
+relocatable = true
--
2.50.1 (Apple Git-155)
[text/plain] v4-0004-Allow-a-CustomScan-to-consume-a-pushed-down-hashj.patch (21.5K, ../[email protected]/5-v4-0004-Allow-a-CustomScan-to-consume-a-pushed-down-hashj.patch)
download | inline diff:
From 903311f0c1a494f530dfa7dba92edad518eb9329 Mon Sep 17 00:00:00 2001
From: Matheus Alcantara <[email protected]>
Date: Thu, 2 Jul 2026 16:29:26 -0300
Subject: [PATCH v4 4/4] Allow a CustomScan to consume a pushed-down hashjoin
Bloom filter
Extend the hashjoin Bloom-filter pushdown so that a base-relation
CustomScan can build filter-aware paths, be chosen as a filter
recipient, and consume the filter inside its own scan loop The whole
mechanism is gated on a new opt-in path flag,
CUSTOMPATH_SUPPORT_BLOOM_FILTERS; providers that do not set it, and
heap, are unaffected.
The core of this change is teaching path generation about the custom
scan:
* generate_expected_filter_paths()/create_filtered_scan_path() now clone
a base-relation CustomPath into filter-bearing variants when the path
advertised CUSTOMPATH_SUPPORT_BLOOM_FILTERS, exactly as they already do
for the stock scan path types.
* find_bloom_filter_recipient() treats a base-rel CustomScan
(scanrelid > 0) that carries the flag the same as a SeqScan. The probe
is not wired into ExecScanExtended() -- a CustomScan dispatches to the
provider's ExecCustomScan -- so the provider calls ExecBloomFilters()
itself at whatever granularity it supports. ExecInitCustomScan()
compiles the probe state up front via ExecInitBloomFilters(), and
set_customscan_references() fixes the pushed key expressions for a
base-relation scan just like the scan qual.
In addition, an opted-in recipient gets two things a stock scan does
not:
* Per-key filters. Alongside the combined-hash filter, the hash join
optionally builds one Bloom filter per join key.
The combined filter, keyed on the hash of all keys together, stays the
default and remains the more selective one for a per-row probe: per-key
filters only test whether each column's value appears somewhere in the
build side, so on a multi-column join they are strictly weaker. What
they enable is testing a single key column on its own (a column store
can check one column against its per-column dictionary or zone map and
skip whole row groups before decompression, which the combined filter
cannot support). The build reuses per-key inner hash ExprStates; the
extra CPU and memory are paid only by a consumer that opted in, and a
recipient correlates HashState.perkey_filters[i] with
BloomFilter.filter_exprs[i] by position. Heap and single-key joins are
unaffected.
* Eager filter build. When the outer relation's startup cost is below
the hash-table build cost, ExecHashJoinImpl fetches the first outer
tuple before building the hash table to take the empty-outer shortcut.
For a CustomScan that applies the filter in its own scan loop that is
too late -- its first tuple request may decompress a whole row group
before the filter exists. A HashJoin.bloom_eager flag, set when the
filter is pushed to such a recipient, tells the executor to skip the
prefetch and build the hash table (and filter) first. Only such a
recipient pays the cost of a possibly-needless hash build on an empty
outer.
Based on previous patches from Andrew Dunstan, adapted to the path-based
pushdown design.
---
src/backend/executor/nodeCustom.c | 11 +++++
src/backend/executor/nodeHash.c | 35 +++++++++++++++
src/backend/executor/nodeHashjoin.c | 36 +++++++++++++++
src/backend/optimizer/path/allpaths.c | 14 ++++++
src/backend/optimizer/plan/createplan.c | 45 +++++++++++++++++++
src/backend/optimizer/plan/setrefs.c | 10 +++++
src/backend/optimizer/util/pathnode.c | 23 +++++++---
src/include/nodes/execnodes.h | 14 ++++++
src/include/nodes/extensible.h | 2 +
src/include/nodes/plannodes.h | 21 +++++++++
.../expected/test_bloom_customscan.out | 9 ++--
.../test_bloom_customscan.c | 5 +++
12 files changed, 215 insertions(+), 10 deletions(-)
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index b7cc890cd20..c18dcb1035d 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -101,6 +101,17 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
css->ss.ps.qual =
ExecInitQual(cscan->scan.plan.qual, (PlanState *) css);
+ /*
+ * Set up any bloom filters a hash join pushed down to this scan (see
+ * nodeHashjoin.c). This compiles the probe expressions against the scan
+ * tuple slot; the provider is responsible for actually probing them with
+ * ExecBloomFilters() from its ExecCustomScan callback, at whatever
+ * granularity it supports. A no-op unless the provider advertised
+ * CUSTOMPATH_SUPPORT_BLOOM_FILTERS and the planner found a filter to
+ * push.
+ */
+ ExecInitBloomFilters((PlanState *) css, css->ss.ss_ScanTupleSlot);
+
/*
* The callback of custom-scan provider applies the final initialization
* of the custom-scan-state node according to its logic.
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 37224324bce..2b045eae186 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -197,6 +197,25 @@ MultiExecPrivateHash(HashState *node)
(unsigned char *) &hashvalue,
sizeof(hashvalue));
+ /*
+ * Likewise for the optional per-key filters, using the per-key
+ * (single-key) hash ExprStates. Same econtext as the combined
+ * hash above (ecxt_outertuple is the just-fetched inner tuple).
+ */
+ for (int k = 0; k < node->perkey_nfilters; k++)
+ {
+ bool keyisnull;
+ uint32 keyhash;
+
+ keyhash = DatumGetUInt32(ExecEvalExprSwitchContext(node->perkey_hash[k],
+ econtext,
+ &keyisnull));
+ if (!keyisnull)
+ bloom_add_element(node->perkey_filters[k],
+ (unsigned char *) &keyhash,
+ sizeof(keyhash));
+ }
+
bucketNumber = ExecHashGetSkewBucket(hashtable, hashvalue);
if (bucketNumber != INVALID_SKEW_BUCKET_NO)
{
@@ -722,6 +741,22 @@ ExecHashTableCreate(HashState *state)
oldctx = MemoryContextSwitchTo(hashtable->hashCxt);
state->bloom_filter = bloom_create((int64) Max(rows, 1.0),
bloom_work_mem, 0);
+
+ /*
+ * If a recipient opted in, also build one filter per join key (in
+ * addition to the combined one above). These let a recipient test an
+ * individual key column on its own; they are less selective than the
+ * combined filter, so they are built only on demand.
+ */
+ if (state->want_perkey_bloom)
+ {
+ state->perkey_filters = palloc_array(struct bloom_filter *,
+ state->perkey_nfilters);
+ for (int i = 0; i < state->perkey_nfilters; i++)
+ state->perkey_filters[i] = bloom_create((int64) Max(rows, 1.0),
+ bloom_work_mem, 0);
+ }
+
MemoryContextSwitchTo(oldctx);
}
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index e3467f14739..c501b98067d 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -317,6 +317,17 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel)
*/
node->hj_FirstOuterTupleSlot = NULL;
}
+ else if (((HashJoin *) node->js.ps.plan)->bloom_eager)
+ {
+ /*
+ * We pushed a bloom filter to a CustomScan on the outer
+ * side that wants it at scan start (e.g. to skip row groups
+ * before decompression). Skip the empty-outer prefetch and
+ * build the hash table -- and the filter -- first, so it is
+ * ready before the outer scan produces its first tuple.
+ */
+ node->hj_FirstOuterTupleSlot = NULL;
+ }
else if (HJ_FILL_OUTER(node) ||
(outerNode->plan->startup_cost < hashNode->ps.plan->total_cost &&
!node->hj_OuterNotEmpty))
@@ -908,6 +919,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
hashState = castNode(HashState, innerPlanState(hjstate));
hashState->want_bloom_filter = (node->bloom_consumer_count > 0);
hashState->bloom_filter_id = node->bloom_filter_id;
+ hashState->want_perkey_bloom = node->bloom_perkey;
/*
* Initialize result slot, type and projection.
@@ -1031,6 +1043,28 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
&hashstate->ps,
0);
+ /*
+ * If a recipient opted in to per-key bloom filters, build one inner
+ * (single-key) hash ExprState per join key, used by the Hash node to
+ * populate the per-key filters. The combined hash above cannot be
+ * decomposed, so this is the extra cost a per-key consumer pays.
+ */
+ if (hashstate->want_perkey_bloom)
+ {
+ hashstate->perkey_nfilters = nkeys;
+ hashstate->perkey_hash = palloc_array(ExprState *, nkeys);
+ for (int i = 0; i < nkeys; i++)
+ hashstate->perkey_hash[i] =
+ ExecBuildHash32Expr(hashstate->ps.ps_ResultTupleDesc,
+ hashstate->ps.resultops,
+ &inner_hashfuncid[i],
+ list_make1_oid(list_nth_oid(node->hashcollations, i)),
+ list_make1(list_nth(hash->hashkeys, i)),
+ &hash_strict[i],
+ &hashstate->ps,
+ 0);
+ }
+
/* Remember whether we need to save tuples with null join keys */
hjstate->hj_KeepNullTuples = HJ_FILL_OUTER(hjstate);
hashstate->keep_null_tuples = HJ_FILL_INNER(hjstate);
@@ -1118,6 +1152,7 @@ ExecEndHashJoin(HashJoinState *node)
ExecHashTableDestroy(node->hj_HashTable);
node->hj_HashTable = NULL;
hashNode->bloom_filter = NULL;
+ hashNode->perkey_filters = NULL;
}
/*
@@ -1775,6 +1810,7 @@ ExecReScanHashJoin(HashJoinState *node)
* freed by the ExecHashTableDestroy call.
*/
hashNode->bloom_filter = NULL;
+ hashNode->perkey_filters = NULL;
/*
* if chgParam of subnode is not null then plan will be re-scanned
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8829c7c3108..72c51422394 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -25,6 +25,7 @@
#include "catalog/pg_proc.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
+#include "nodes/extensible.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
@@ -1294,6 +1295,19 @@ generate_expected_filter_paths(PlannerInfo *root, RelOptInfo *rel)
case T_TidRangePath:
basepaths = lappend(basepaths, path);
break;
+ case T_CustomPath:
+
+ /*
+ * A base-relation CustomScan can receive a pushed-down filter,
+ * but only if the provider advertised that it knows how to
+ * consume one (it probes the filter itself inside its scan
+ * loop; see find_bloom_filter_recipient in createplan.c and
+ * ExecInitCustomScan). Providers that don't opt in, like heap,
+ * are unaffected.
+ */
+ if (((CustomPath *) path)->flags & CUSTOMPATH_SUPPORT_BLOOM_FILTERS)
+ basepaths = lappend(basepaths, path);
+ break;
default:
break;
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 51990b98419..b9e481ba298 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -4781,6 +4781,25 @@ find_bloom_filter_recipient(Plan *plan, Index target_relid)
return plan;
return NULL;
}
+ case T_CustomScan:
+ {
+ /*
+ * A CustomScan on a base relation can act as a recipient, but
+ * only if the provider advertised that it knows how to consume
+ * a pushed-down bloom filter. Unlike the stock scans, the
+ * probe is not performed by ExecScanExtended() (a CustomScan
+ * dispatches to the provider's own ExecCustomScan); the
+ * provider is responsible for calling ExecBloomFilters() at
+ * whatever granularity it likes. Non-leaf custom nodes have
+ * scanrelid == 0 and so are rejected by the relid test.
+ */
+ CustomScan *cscan = (CustomScan *) plan;
+
+ if ((cscan->flags & CUSTOMPATH_SUPPORT_BLOOM_FILTERS) &&
+ cscan->scan.scanrelid == target_relid)
+ return plan;
+ return NULL;
+ }
case T_Sort:
case T_IncrementalSort:
case T_Material:
@@ -4910,6 +4929,32 @@ try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan,
recipient->bloom_filters = lappend(recipient->bloom_filters, bf);
+ /*
+ * A CustomScan recipient that opted in consumes the filter in its own scan
+ * loop, possibly at the storage level, so it wants two things a stock scan
+ * does not.
+ */
+ if (IsA(recipient, CustomScan) &&
+ (((CustomScan *) recipient)->flags & CUSTOMPATH_SUPPORT_BLOOM_FILTERS))
+ {
+ /*
+ * Build the hash table (and filter) before the outer scan starts, so
+ * the filter is available on the first tuple request rather than after
+ * a batch has already been scanned unfiltered.
+ */
+ hj->bloom_eager = true;
+
+ /*
+ * Also build a separate filter per join key, so the recipient can test
+ * a single column on its own (e.g. against a per-column dictionary or
+ * zone map). The combined filter is always built and is the more
+ * selective one for a per-row probe; there is nothing to gain for a
+ * single-key join, where the two coincide.
+ */
+ if (list_length(hj->hashkeys) > 1)
+ hj->bloom_perkey = true;
+ }
+
hj->bloom_consumer_count++;
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 0059acfccbe..74c7a5bf3a5 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1826,6 +1826,16 @@ set_customscan_references(PlannerInfo *root,
cscan->custom_exprs =
fix_scan_list(root, cscan->custom_exprs,
rtoffset, NUM_EXEC_QUAL((Plan *) cscan));
+
+ /*
+ * Bloom filters pushed down to a base-relation CustomScan: the key
+ * expressions are plain Vars of the scanned relation, so they are
+ * fixed up the same way as the scan qual. (A CustomScan emitting a
+ * custom_scan_tlist takes the branch above and would instead need
+ * fix_upper_expr against the tlist index, like IndexOnlyScan; no
+ * in-tree provider needs that yet.)
+ */
+ fix_scan_bloom_filters(root, (Plan *) cscan, rtoffset);
}
/* Adjust child plan-nodes recursively, if needed */
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 9cd9188a1cf..65690553cf5 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -501,6 +501,17 @@ create_filtered_scan_path(PlannerInfo *root, Path *subpath, List *filters)
case T_TidRangePath:
sz = sizeof(TidRangePath);
break;
+ case T_CustomPath:
+
+ /*
+ * A base-relation CustomScan provider that advertised
+ * CUSTOMPATH_SUPPORT_BLOOM_FILTERS can receive a pushed-down
+ * filter and apply it in its own scan loop
+ * .generate_expected_filter_paths() only offers such paths here,
+ * so we need not re-check the flag.
+ */
+ sz = sizeof(CustomPath);
+ break;
default:
/* unsupported scan path type */
return NULL;
@@ -620,10 +631,10 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
/*
* Paths carrying different sets of expected Bloom filters serve
- * different purposes (each may be consumed by a different parent join,
- * or none at all), and their cost/row estimates aren't directly
- * comparable. So if the two paths don't expect the same filters, keep
- * both and don't let either dominate the other.
+ * different purposes (each may be consumed by a different parent
+ * join, or none at all), and their cost/row estimates aren't directly
+ * comparable. So if the two paths don't expect the same filters,
+ * keep both and don't let either dominate the other.
*/
if (!expected_filters_equal(new_path->expected_filters,
old_path->expected_filters))
@@ -854,8 +865,8 @@ add_path_precheck(RelOptInfo *parent_rel, int disabled_nodes,
* filters of their own, so any filter-bearing old path is a
* non-comparable speculative path and must not be allowed to dominate
* (and thereby suppress) the new path. Skipping them here also
- * guarantees that a join relation always retains at least one ordinary,
- * filter-free path to serve as cheapest_total_path.
+ * guarantees that a join relation always retains at least one
+ * ordinary, filter-free path to serve as cheapest_total_path.
*/
if (old_path->expected_filters != NIL)
continue;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index aad721f3421..873c02fb080 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2786,6 +2786,20 @@ typedef struct HashState
*/
struct bloom_filter *bloom_filter;
+ /*
+ * Optional per-key bloom filters, built in addition to the combined
+ * bloom_filter above when a recipient opted in (HashJoin.bloom_perkey).
+ * perkey_filters has perkey_nfilters entries, one per join key, in hashkey
+ * order; a recipient correlates them with BloomFilter.filter_exprs by
+ * position. perkey_hash holds the matching per-key (single-key) hash
+ * ExprStates used to populate them during the build. All live in hashCxt
+ * and follow the same lifecycle as bloom_filter.
+ */
+ bool want_perkey_bloom;
+ int perkey_nfilters;
+ struct bloom_filter **perkey_filters;
+ ExprState **perkey_hash;
+
/*
* Counters with total per-filter instrumentation. Separate from the
* per-recipient counters in BloomFilterState. Redundant, but will be
diff --git a/src/include/nodes/extensible.h b/src/include/nodes/extensible.h
index 517db95c4a3..ea2cef4fe3b 100644
--- a/src/include/nodes/extensible.h
+++ b/src/include/nodes/extensible.h
@@ -84,6 +84,8 @@ extern const ExtensibleNodeMethods *GetExtensibleNodeMethods(const char *extnode
#define CUSTOMPATH_SUPPORT_BACKWARD_SCAN 0x0001
#define CUSTOMPATH_SUPPORT_MARK_RESTORE 0x0002
#define CUSTOMPATH_SUPPORT_PROJECTION 0x0004
+/* provider can accept a hashjoin bloom filter pushed down to its scan */
+#define CUSTOMPATH_SUPPORT_BLOOM_FILTERS 0x0008
/*
* Custom path methods. Mostly, we just need to know how to convert a
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 4e35d77cc49..0e011f3d4e2 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -1124,6 +1124,27 @@ typedef struct HashJoin
* Zero when this HashJoin has no consumers.
*/
int bloom_filter_id;
+
+ /*
+ * Whether to also build one bloom filter per join key (in addition to the
+ * combined-hash filter), so that a recipient can test an individual key
+ * column on its own -- e.g. a column store probing a per-column dictionary
+ * or zone map. Set at plan time only when the recipient is a CustomScan
+ * that advertised CUSTOMPATH_SUPPORT_BLOOM_FILTERS. The combined filter is
+ * always built and remains the more selective one; per-key filters are an
+ * opt-in extra that nobody else pays for.
+ */
+ bool bloom_perkey;
+
+ /*
+ * Whether to build the hash table (and bloom filter) before fetching the
+ * first outer tuple, skipping the empty-outer prefetch optimization. Set
+ * at plan time when the filter is pushed to a CustomScan recipient, which
+ * may want to apply the filter the moment its scan starts (e.g. a column
+ * store skipping row groups before decompression) rather than after having
+ * already produced a batch unfiltered. See ExecHashJoinImpl.
+ */
+ bool bloom_eager;
} HashJoin;
/* ----------------
diff --git a/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
index e1ce5903a02..8e784209901 100644
--- a/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
+++ b/src/test/modules/test_bloom_customscan/expected/test_bloom_customscan.out
@@ -33,9 +33,10 @@ SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
Output: f.a
-> Hash
Output: d.id
+ Bloom Filter 1
-> Custom Scan (TestBloomCustomScan) on public.cs_dim d
Output: d.id
-(10 rows)
+(11 rows)
-- Single-key join: the filter must actually reject fact rows.
SELECT test_bloom_cs_reset();
@@ -53,7 +54,7 @@ SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id;
SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
filter_rejected_rows
----------------------
- f
+ t
(1 row)
-- Correctness: the result must be identical with and without the filter.
@@ -97,13 +98,13 @@ SELECT count(*) FROM cs_fact f JOIN cs_dim d ON f.a = d.id AND f.b = d.id2;
SELECT test_bloom_cs_perkey_built() AS perkey_filters_built;
perkey_filters_built
----------------------
- f
+ t
(1 row)
SELECT test_bloom_cs_rejected_rows() > 0 AS filter_rejected_rows;
filter_rejected_rows
----------------------
- f
+ t
(1 row)
-- cleanup
diff --git a/src/test/modules/test_bloom_customscan/test_bloom_customscan.c b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
index 124cbef9ded..99338c7d361 100644
--- a/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
+++ b/src/test/modules/test_bloom_customscan/test_bloom_customscan.c
@@ -145,6 +145,7 @@ bloom_cs_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti,
cpath->path.total_cost = total_cost;
cpath->path.pathkeys = NIL;
+ cpath->flags = CUSTOMPATH_SUPPORT_BLOOM_FILTERS;
cpath->custom_paths = NIL;
cpath->custom_private = NIL;
cpath->methods = &bloom_cs_path_methods;
@@ -234,6 +235,10 @@ bloom_cs_maybe_inspect_perkey(BloomCSScanState * bcss)
if (hashNode->bloom_filter == NULL)
return;
+ if (hashNode->want_perkey_bloom &&
+ hashNode->perkey_nfilters > 0 &&
+ hashNode->perkey_filters != NULL)
+ test_bloom_cs_perkey_seen = true;
}
bcss->inspected = true;
--
2.50.1 (Apple Git-155)
view thread (23+ 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]
Subject: Re: hashjoins vs. Bloom filters (yet again)
In-Reply-To: <[email protected]>
* 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