From b4c61a32fac544177239113ff99a93f03b70529e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 20 Jun 2026 20:20:08 +0000
Subject: [PATCH v3 2/2] 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.54.0

