From 1f6e74d2518cd2f184309c7a9117c73d9dde4658 Mon Sep 17 00:00:00 2001 From: Rui Zhao Date: Sat, 5 Sep 2026 22:57:11 +0800 Subject: [PATCH] Keep build-side alternatives realizable under exact filter matching A filter is realized only by a hash join whose build side equals the filter's build_relids exactly (compute_join_expected_filters). Two pieces of the candidate machinery still assume the older subset matching, and together they can cost a scan out of its filter entirely: - find_interesting_bloom_filters() prunes subset-related candidates against each other on selectivity, keeping the smaller build side unless the larger one discards strictly more. That was sound under subset matching, where the smaller build side was realizable at every join the larger one was, so the more selective of the two dominated. Under exact matching the two are realized at different joins, and which of them ever appears as an inner depends on the join order chosen later; a less selective candidate can be the only one a given order can realize, and a weaker filter still beats none. Selectivity alone therefore cannot establish dominance between them. In a snowflake the pruning leaves the fact with only {d1} while the planner pre-joins the dimension subtree and the only realizable inner is {d1,d11,d12}. Drop only exact duplicates; keep every candidate. - find_bloom_filter_combinations() emits a single combination, so even with the candidates kept, only one of them gets a path. Emit one alternative combination per filter skipped on a build-side conflict, built by the same greedy pass seeded with that filter, dropping duplicates. Filters dropped by the bloom_filter_pushdown_max cap alone get no alternative: they conflict with nothing, so the primary combination realizes fine without them. Each combination becomes its own path; a path expecting a filter some join order cannot realize is rejected at that join, so the join search keeps exactly the realizable alternative: a pre-joined dimension subtree realizes {d1,d11,d12}, a left-deep order realizes {d1}, and an order that joins two disjoint dimensions one at a time now applies their two singleton filters together where before only the cross-product build side {d1,d2} had a combination. This restores all six filter instances the snowflake suite lost when realization switched from subset to exact matching; the star and simple suites are unchanged. Two more scans take a filter from a pre-joined build side (t6 from {t4,t5} in join.out, joinme_1 from {foo_1,joinme,foo_2} in returning.out), two MERGE result listings without ORDER BY change row order as the MERGE's own join plan does, and the plan/stash advice expected outputs move to the plans the cost model picks among the wider set of alternatives. The price is planning time on a dimension that hangs many unrestricted sub-dimensions: every connected subset containing it is a tied candidate with its own path, and each such path is a join input at every level of the search. bloom_filter_pushdown_max_build_relids bounds the number of such candidates. --- .../pg_plan_advice/expected/join_order.out | 52 +++-- .../expected/pg_stash_advice.out | 98 +++++---- src/backend/optimizer/path/allpaths.c | 181 ++++++++++++---- .../expected/hashjoin_bloom_snowflake.out | 196 ++++++++++-------- src/test/regress/expected/join.out | 28 +-- src/test/regress/expected/merge.out | 4 +- src/test/regress/expected/returning.out | 30 ++- 7 files changed, 362 insertions(+), 227 deletions(-) diff --git a/contrib/pg_plan_advice/expected/join_order.out b/contrib/pg_plan_advice/expected/join_order.out index a274cfa4003..60a0c938e92 100644 --- a/contrib/pg_plan_advice/expected/join_order.out +++ b/contrib/pg_plan_advice/expected/join_order.out @@ -27,27 +27,29 @@ SELECT * FROM jo_fact f LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id WHERE val1 = 1 AND val2 = 1; - QUERY PLAN ------------------------------------------------------------- + QUERY PLAN +---------------------------------------------- Hash Join - Hash Cond: ((f.dim1_id = d1.id) AND (f.dim2_id = d2.id)) - -> Seq Scan on jo_fact f - Bloom Filter 1: keys=(dim1_id, dim2_id) - -> Hash - Bloom Filter 1 - -> Nested Loop + Hash Cond: (f.dim2_id = d2.id) + -> Hash Join + Hash Cond: (f.dim1_id = d1.id) + -> Seq Scan on jo_fact f + Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) + -> Hash + Bloom Filter 1 -> Seq Scan on jo_dim1 d1 Filter: (val1 = 1) - -> Materialize - -> Seq Scan on jo_dim2 d2 - Filter: (val2 = 1) + -> Hash + Bloom Filter 2 + -> Seq Scan on jo_dim2 d2 + Filter: (val2 = 1) Generated Plan Advice: - JOIN_ORDER(f (d1 d2)) - NESTED_LOOP_MATERIALIZE(d2) - HASH_JOIN((d1 d2)) + JOIN_ORDER(f d1 d2) + HASH_JOIN(d1 d2) SEQ_SCAN(f d1 d2) NO_GATHER(f d1 d2) -(18 rows) +(20 rows) -- Force a few different join orders. Some of these are very inefficient, -- but the planner considers them all viable. @@ -58,17 +60,21 @@ SELECT * FROM jo_fact f LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id WHERE val1 = 1 AND val2 = 1; - QUERY PLAN ------------------------------------------- + QUERY PLAN +---------------------------------------------- Hash Join Hash Cond: (f.dim2_id = d2.id) -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on jo_fact f + Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash + Bloom Filter 1 -> Seq Scan on jo_dim1 d1 Filter: (val1 = 1) -> Hash + Bloom Filter 2 -> Seq Scan on jo_dim2 d2 Filter: (val2 = 1) Supplied Plan Advice: @@ -78,7 +84,7 @@ SELECT * FROM jo_fact f HASH_JOIN(d1 d2) SEQ_SCAN(f d1 d2) NO_GATHER(f d1 d2) -(18 rows) +(22 rows) SET LOCAL pg_plan_advice.advice = 'join_order(f d2 d1)'; EXPLAIN (COSTS OFF, PLAN_ADVICE) @@ -86,17 +92,21 @@ SELECT * FROM jo_fact f LEFT JOIN jo_dim1 d1 ON f.dim1_id = d1.id LEFT JOIN jo_dim2 d2 ON f.dim2_id = d2.id WHERE val1 = 1 AND val2 = 1; - QUERY PLAN ------------------------------------------- + QUERY PLAN +---------------------------------------------- Hash Join Hash Cond: (f.dim1_id = d1.id) -> Hash Join Hash Cond: (f.dim2_id = d2.id) -> Seq Scan on jo_fact f + Bloom Filter 1: keys=(dim2_id) + Bloom Filter 2: keys=(dim1_id) -> Hash + Bloom Filter 1 -> Seq Scan on jo_dim2 d2 Filter: (val2 = 1) -> Hash + Bloom Filter 2 -> Seq Scan on jo_dim1 d1 Filter: (val1 = 1) Supplied Plan Advice: @@ -106,7 +116,7 @@ SELECT * FROM jo_fact f HASH_JOIN(d2 d1) SEQ_SCAN(f d2 d1) NO_GATHER(f d1 d2) -(18 rows) +(22 rows) SET LOCAL pg_plan_advice.advice = 'join_order(d1 f d2)'; EXPLAIN (COSTS OFF, PLAN_ADVICE) diff --git a/contrib/pg_stash_advice/expected/pg_stash_advice.out b/contrib/pg_stash_advice/expected/pg_stash_advice.out index f55f22349b0..6372bf36c2c 100644 --- a/contrib/pg_stash_advice/expected/pg_stash_advice.out +++ b/contrib/pg_stash_advice/expected/pg_stash_advice.out @@ -57,20 +57,24 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id WHERE val1 = 1 AND val2 = 1; - QUERY PLAN ------------------------------------------- + QUERY PLAN +---------------------------------------------- Hash Join - Hash Cond: (f.dim1_id = d1.id) + Hash Cond: (f.dim2_id = d2.id) -> Hash Join - Hash Cond: (f.dim2_id = d2.id) + Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f + Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash - -> Seq Scan on aa_dim2 d2 - Filter: (val2 = 1) + Bloom Filter 1 + -> Seq Scan on aa_dim1 d1 + Filter: (val1 = 1) -> Hash - -> Seq Scan on aa_dim1 d1 - Filter: (val1 = 1) -(11 rows) + Bloom Filter 2 + -> Seq Scan on aa_dim2 d2 + Filter: (val2 = 1) +(15 rows) -- Force an index scan on dim1 SELECT pg_set_stashed_advice('regress_stash', :'qid', @@ -84,22 +88,26 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id WHERE val1 = 1 AND val2 = 1; - QUERY PLAN ---------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------- Hash Join - Hash Cond: (f.dim1_id = d1.id) + Hash Cond: (f.dim2_id = d2.id) -> Hash Join - Hash Cond: (f.dim2_id = d2.id) + Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f + Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash - -> Seq Scan on aa_dim2 d2 - Filter: (val2 = 1) + Bloom Filter 1 + -> Index Scan using aa_dim1_pkey on aa_dim1 d1 + Filter: (val1 = 1) -> Hash - -> Index Scan using aa_dim1_pkey on aa_dim1 d1 - Filter: (val1 = 1) + Bloom Filter 2 + -> Seq Scan on aa_dim2 d2 + Filter: (val2 = 1) Supplied Plan Advice: INDEX_SCAN(d1 aa_dim1_pkey) /* matched */ -(13 rows) +(17 rows) -- Force an alternative join order SELECT pg_set_stashed_advice('regress_stash', :'qid', @@ -113,22 +121,26 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id WHERE val1 = 1 AND val2 = 1; - QUERY PLAN ------------------------------------------- + QUERY PLAN +---------------------------------------------- Hash Join Hash Cond: (f.dim2_id = d2.id) -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f + Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash + Bloom Filter 1 -> Seq Scan on aa_dim1 d1 Filter: (val1 = 1) -> Hash + Bloom Filter 2 -> Seq Scan on aa_dim2 d2 Filter: (val2 = 1) Supplied Plan Advice: JOIN_ORDER(f d1 d2) /* matched */ -(13 rows) +(17 rows) -- Force an alternative join strategy SELECT pg_set_stashed_advice('regress_stash', :'qid', @@ -202,20 +214,24 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id WHERE val1 = 1 AND val2 = 1; - QUERY PLAN ------------------------------------------- + QUERY PLAN +---------------------------------------------- Hash Join - Hash Cond: (f.dim1_id = d1.id) + Hash Cond: (f.dim2_id = d2.id) -> Hash Join - Hash Cond: (f.dim2_id = d2.id) + Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f + Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash - -> Seq Scan on aa_dim2 d2 - Filter: (val2 = 1) + Bloom Filter 1 + -> Seq Scan on aa_dim1 d1 + Filter: (val1 = 1) -> Hash - -> Seq Scan on aa_dim1 d1 - Filter: (val1 = 1) -(11 rows) + Bloom Filter 2 + -> Seq Scan on aa_dim2 d2 + Filter: (val2 = 1) +(15 rows) -- Test that we can list each stash individually and all of them together, -- but not a nonexistent stash. @@ -265,20 +281,24 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id LEFT JOIN aa_dim2 d2 ON f.dim2_id = d2.id WHERE val1 = 1 AND val2 = 1; - QUERY PLAN ------------------------------------------- + QUERY PLAN +---------------------------------------------- Hash Join - Hash Cond: (f.dim1_id = d1.id) + Hash Cond: (f.dim2_id = d2.id) -> Hash Join - Hash Cond: (f.dim2_id = d2.id) + Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f + Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash - -> Seq Scan on aa_dim2 d2 - Filter: (val2 = 1) + Bloom Filter 1 + -> Seq Scan on aa_dim1 d1 + Filter: (val1 = 1) -> Hash - -> Seq Scan on aa_dim1 d1 - Filter: (val1 = 1) -(11 rows) + Bloom Filter 2 + -> Seq Scan on aa_dim2 d2 + Filter: (val2 = 1) +(15 rows) SELECT * FROM pg_get_advice_stashes() ORDER BY stash_name; stash_name | num_entries diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 64e717d8ad4..798446c832f 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1375,23 +1375,23 @@ find_interesting_bloom_filters(PlannerInfo *root, RelOptInfo *rel) } /* - * De-duplicate against the filters produced so far. Beyond dropping - * an exact duplicate build side, we keep a larger filter only when it - * is strictly more selective (discards more tuples) than a smaller - * one it contains: a smaller build side is sourced no later and costs - * no more, so it dominates a larger build side that does not discard - * more. + * De-duplicate against the filters produced so far: drop only an exact + * duplicate build side. * - * - If an existing filter has the same build side, keep the existing - * filter and drop this candidate. - * - * - If an existing filter's build side is a subset of this one (the - * candidate is larger), keep the candidate only if it is strictly - * more selective than that existing filter. - * - * - If an existing filter's build side is a superset of this one (the - * candidate is smaller), drop that existing filter unless it is - * strictly more selective than the candidate. + * We deliberately do not prune between subset-related candidates on + * selectivity. Realization requires the build side to match a join's + * inner side exactly (see compute_join_expected_filters), so {d1} and + * {d1,d11,d12} are realized at different joins -- which of them ever + * shows up as an inner depends on the join order chosen later. A + * candidate that is less selective can still be the only one a given + * join order can realize, and a weaker filter beats none, so + * selectivity alone can never establish that one dominates the other. + * (Under the earlier subset matching the smaller build side was + * realizable wherever the larger one was, which is what made pruning by + * selectivity sound; exact matching removed that.) All candidates are + * kept; find_bloom_filter_combinations turns the overlapping ones into + * separate combinations (and so separate paths), and the join search + * keeps whichever it can realize. * * XXX Can we actually see the same set of build relids twice? I don't * think that should be possible, we only generate a single list, and @@ -1407,21 +1407,6 @@ find_interesting_bloom_filters(PlannerInfo *root, RelOptInfo *rel) add = false; break; } - else if (bms_is_subset(f->build_relids, brel->relids)) - { - /* existing (smaller) dominates unless candidate is better */ - if (f->selectivity <= sel) - { - add = false; - break; - } - } - else if (bms_is_subset(brel->relids, f->build_relids)) - { - /* candidate (smaller) dominates the existing larger filter */ - if (sel <= f->selectivity) - result = foreach_delete_current(result, lce); - } } if (add) @@ -1589,6 +1574,66 @@ expected_filter_selectivity_cmp(const ListCell *a, const ListCell *b) return 0; } +/* + * Greedy pass over the sorted candidates: force "seed" in first (when given), + * then add each filter that does not overlap the build sides already chosen, + * up to bloom_filter_pushdown_max members. + */ +static List * +build_bloom_filter_combination(List *sorted, ExpectedFilter *seed) +{ + List *combo = NIL; + Relids used = NULL; + ListCell *lc; + + if (seed != NULL) + { + combo = lappend(combo, seed); + used = bms_add_members(used, seed->build_relids); + } + + foreach(lc, sorted) + { + ExpectedFilter *f = (ExpectedFilter *) lfirst(lc); + + if (list_length(combo) >= bloom_filter_pushdown_max) + break; + + if (f == seed) + continue; + + /* skip a filter whose build side overlaps one already chosen */ + if (bms_overlap(used, f->build_relids)) + continue; + + combo = lappend(combo, f); + used = bms_add_members(used, f->build_relids); + } + + bms_free(used); + + /* keep the most selective first; the seed may belong further down */ + list_sort(combo, expected_filter_selectivity_cmp); + + return combo; +} + +/* Do two combinations apply the same set of filters? */ +static bool +bloom_filter_combinations_equal(List *a, List *b) +{ + ListCell *lc; + + if (list_length(a) != list_length(b)) + return false; + foreach(lc, a) + { + if (!list_member_ptr(b, lfirst(lc))) + return false; + } + return true; +} + /* * find_bloom_filter_combinations * Find the interesting Bloom filters rel could receive from a hash join @@ -1609,8 +1654,24 @@ expected_filter_selectivity_cmp(const ListCell *a, const ListCell *b) * first. bloom_filter_pushdown_max bounds how many filters are applied at * once, which bounds the per-tuple probe cost. * - * The result is a list of combinations (each a list of ExpectedFilter); this - * heuristic produces at most one combination. + * A filter skipped on a conflict is not necessarily inferior, though: a + * filter is realized only by a hash join whose build side matches its + * build_relids exactly, so of two overlapping candidates -- say {d1} and + * {d1,d11,d12}, equally selective -- the one that can be realized depends on + * the join order the planner ends up choosing. A path expecting the wrong + * one is rejected at that join (see compute_join_expected_filters), and the + * scan would lose the filter altogether. So for each filter skipped on a + * conflict we emit one alternative combination, built by the same greedy + * pass but with that filter forced in first. Each combination becomes its + * own path, and the join search keeps whichever it can realize. Filters + * dropped by the bloom_filter_pushdown_max cap alone get no alternative: + * they conflict with nothing, so the primary combination realizes fine + * without them. Different seeds can fill up to the same set (two disjoint + * filters both skipped in favor of a superset covering them: either seed + * pulls in the other), so duplicates are dropped; this yields at most one + * combination per interesting filter. + * + * The result is a list of combinations (each a list of ExpectedFilter). * * Note: A CustomScan provider can call it directly from its own * set_rel_pathlist_hook (see generate_expected_filter_paths above). @@ -1620,7 +1681,8 @@ find_bloom_filter_combinations(PlannerInfo *root, RelOptInfo *rel) { List *filters = find_interesting_bloom_filters(root, rel); List *sorted; - List *combo = NIL; + List *combo; + List *result; Relids used = NULL; ListCell *lc; @@ -1636,28 +1698,57 @@ find_bloom_filter_combinations(PlannerInfo *root, RelOptInfo *rel) sorted = list_copy(filters); list_sort(sorted, expected_filter_selectivity_cmp); + /* The primary combination: plain greedy pass, no seed. */ + combo = build_bloom_filter_combination(sorted, NULL); + if (combo == NIL) + { + list_free(sorted); + return NIL; + } + + result = list_make1(combo); + + /* Build sides covered by the primary combination. */ + foreach(lc, combo) + used = bms_add_members(used, + ((ExpectedFilter *) lfirst(lc))->build_relids); + + /* + * One alternative combination per filter skipped on a build-side + * conflict, greedy-filled around that filter (see the function comment). + */ foreach(lc, sorted) { ExpectedFilter *f = (ExpectedFilter *) lfirst(lc); + List *alt; + ListCell *lc2; + bool dup = false; - if (list_length(combo) >= bloom_filter_pushdown_max) - break; - - /* skip a filter whose build side overlaps one already chosen */ - if (bms_overlap(used, f->build_relids)) + if (list_member_ptr(combo, f)) continue; + if (!bms_overlap(used, f->build_relids)) + continue; /* dropped by the cap alone, no conflict */ - combo = lappend(combo, f); - used = bms_add_members(used, f->build_relids); + alt = build_bloom_filter_combination(sorted, f); + + foreach(lc2, result) + { + if (bloom_filter_combinations_equal((List *) lfirst(lc2), alt)) + { + dup = true; + break; + } + } + if (dup) + list_free(alt); + else + result = lappend(result, alt); } list_free(sorted); bms_free(used); - if (combo == NIL) - return NIL; - - return list_make1(combo); + return result; } /* diff --git a/src/test/regress/expected/hashjoin_bloom_snowflake.out b/src/test/regress/expected/hashjoin_bloom_snowflake.out index d953b247cd2..c2d627f2b7a 100644 --- a/src/test/regress/expected/hashjoin_bloom_snowflake.out +++ b/src/test/regress/expected/hashjoin_bloom_snowflake.out @@ -155,37 +155,41 @@ JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id) JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id) JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id) WHERE d1.r < 0.5; - QUERY PLAN ------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------- Hash Join - Hash Cond: (f.id2 = d2.id) + Hash Cond: (f.id1 = d1.id) -> Hash Join - Hash Cond: (f.id1 = d1.id) + Hash Cond: (f.id2 = d2.id) -> Seq Scan on bloom_snowflake_fact f + Bloom Filter 3: keys=(id1) -> Hash -> Hash Join - Hash Cond: (d12.id = d1.id12) - -> Seq Scan on bloom_snowflake_dim_1_2 d12 + Hash Cond: (d2.id22 = d22.id) + -> Hash Join + Hash Cond: (d2.id21 = d21.id) + -> Seq Scan on bloom_snowflake_dim_2 d2 + -> Hash + -> Seq Scan on bloom_snowflake_dim_2_1 d21 -> Hash - -> Hash Join - Hash Cond: (d11.id = d1.id11) - -> Seq Scan on bloom_snowflake_dim_1_1 d11 - Bloom Filter 1: keys=(id) - -> Hash - Bloom Filter 1 - -> Seq Scan on bloom_snowflake_dim_1 d1 - Filter: (r < '0.5'::double precision) + -> Seq Scan on bloom_snowflake_dim_2_2 d22 -> Hash + Bloom Filter 3 -> Hash Join - Hash Cond: (d2.id22 = d22.id) - -> Hash Join - Hash Cond: (d2.id21 = d21.id) - -> Seq Scan on bloom_snowflake_dim_2 d2 - -> Hash - -> Seq Scan on bloom_snowflake_dim_2_1 d21 + Hash Cond: (d12.id = d1.id12) + -> Seq Scan on bloom_snowflake_dim_1_2 d12 + Bloom Filter 2: keys=(id) -> Hash - -> Seq Scan on bloom_snowflake_dim_2_2 d22 -(28 rows) + Bloom Filter 2 + -> Hash Join + Hash Cond: (d11.id = d1.id11) + -> Seq Scan on bloom_snowflake_dim_1_1 d11 + Bloom Filter 1: keys=(id) + -> Hash + Bloom Filter 1 + -> Seq Scan on bloom_snowflake_dim_1 d1 + Filter: (r < '0.5'::double precision) +(32 rows) EXPLAIN (COSTS OFF) SELECT * @@ -197,39 +201,41 @@ JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id) JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id) JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id) WHERE d11.r < 0.5; - QUERY PLAN ------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------- Hash Join - Hash Cond: (f.id2 = d2.id) + Hash Cond: (f.id1 = d1.id) -> Hash Join - Hash Cond: (f.id1 = d1.id) + Hash Cond: (f.id2 = d2.id) -> Seq Scan on bloom_snowflake_fact f + Bloom Filter 3: keys=(id1) -> Hash -> Hash Join - Hash Cond: (d12.id = d1.id12) - -> Seq Scan on bloom_snowflake_dim_1_2 d12 - Bloom Filter 2: keys=(id) + Hash Cond: (d2.id22 = d22.id) + -> Hash Join + Hash Cond: (d2.id21 = d21.id) + -> Seq Scan on bloom_snowflake_dim_2 d2 + -> Hash + -> Seq Scan on bloom_snowflake_dim_2_1 d21 -> Hash - Bloom Filter 2 - -> Hash Join - Hash Cond: (d1.id11 = d11.id) - -> Seq Scan on bloom_snowflake_dim_1 d1 - Bloom Filter 1: keys=(id11) - -> Hash - Bloom Filter 1 - -> Seq Scan on bloom_snowflake_dim_1_1 d11 - Filter: (r < '0.5'::double precision) + -> Seq Scan on bloom_snowflake_dim_2_2 d22 -> Hash + Bloom Filter 3 -> Hash Join - Hash Cond: (d2.id22 = d22.id) - -> Hash Join - Hash Cond: (d2.id21 = d21.id) - -> Seq Scan on bloom_snowflake_dim_2 d2 - -> Hash - -> Seq Scan on bloom_snowflake_dim_2_1 d21 + Hash Cond: (d12.id = d1.id12) + -> Seq Scan on bloom_snowflake_dim_1_2 d12 + Bloom Filter 2: keys=(id) -> Hash - -> Seq Scan on bloom_snowflake_dim_2_2 d22 -(30 rows) + Bloom Filter 2 + -> Hash Join + Hash Cond: (d1.id11 = d11.id) + -> Seq Scan on bloom_snowflake_dim_1 d1 + Bloom Filter 1: keys=(id11) + -> Hash + Bloom Filter 1 + -> Seq Scan on bloom_snowflake_dim_1_1 d11 + Filter: (r < '0.5'::double precision) +(32 rows) -- increase the accepted build size (includes the fact) SET bloom_filter_pushdown_max_build_relids = 4; @@ -244,37 +250,41 @@ JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id) JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id) JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id) WHERE d1.r < 0.5; - QUERY PLAN ------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------- Hash Join - Hash Cond: (f.id2 = d2.id) + Hash Cond: (f.id1 = d1.id) -> Hash Join - Hash Cond: (f.id1 = d1.id) + Hash Cond: (f.id2 = d2.id) -> Seq Scan on bloom_snowflake_fact f + Bloom Filter 3: keys=(id1) -> Hash -> Hash Join - Hash Cond: (d12.id = d1.id12) - -> Seq Scan on bloom_snowflake_dim_1_2 d12 + Hash Cond: (d2.id22 = d22.id) + -> Hash Join + Hash Cond: (d2.id21 = d21.id) + -> Seq Scan on bloom_snowflake_dim_2 d2 + -> Hash + -> Seq Scan on bloom_snowflake_dim_2_1 d21 -> Hash - -> Hash Join - Hash Cond: (d11.id = d1.id11) - -> Seq Scan on bloom_snowflake_dim_1_1 d11 - Bloom Filter 1: keys=(id) - -> Hash - Bloom Filter 1 - -> Seq Scan on bloom_snowflake_dim_1 d1 - Filter: (r < '0.5'::double precision) + -> Seq Scan on bloom_snowflake_dim_2_2 d22 -> Hash + Bloom Filter 3 -> Hash Join - Hash Cond: (d2.id22 = d22.id) - -> Hash Join - Hash Cond: (d2.id21 = d21.id) - -> Seq Scan on bloom_snowflake_dim_2 d2 - -> Hash - -> Seq Scan on bloom_snowflake_dim_2_1 d21 + Hash Cond: (d12.id = d1.id12) + -> Seq Scan on bloom_snowflake_dim_1_2 d12 + Bloom Filter 2: keys=(id) -> Hash - -> Seq Scan on bloom_snowflake_dim_2_2 d22 -(28 rows) + Bloom Filter 2 + -> Hash Join + Hash Cond: (d11.id = d1.id11) + -> Seq Scan on bloom_snowflake_dim_1_1 d11 + Bloom Filter 1: keys=(id) + -> Hash + Bloom Filter 1 + -> Seq Scan on bloom_snowflake_dim_1 d1 + Filter: (r < '0.5'::double precision) +(32 rows) EXPLAIN (COSTS OFF) SELECT * @@ -286,39 +296,41 @@ JOIN bloom_snowflake_dim_1_2 d12 ON (d1.id12 = d12.id) JOIN bloom_snowflake_dim_2_1 d21 ON (d2.id21 = d21.id) JOIN bloom_snowflake_dim_2_2 d22 ON (d2.id22 = d22.id) WHERE d11.r < 0.5; - QUERY PLAN ------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------- Hash Join - Hash Cond: (f.id2 = d2.id) + Hash Cond: (f.id1 = d1.id) -> Hash Join - Hash Cond: (f.id1 = d1.id) + Hash Cond: (f.id2 = d2.id) -> Seq Scan on bloom_snowflake_fact f + Bloom Filter 3: keys=(id1) -> Hash -> Hash Join - Hash Cond: (d12.id = d1.id12) - -> Seq Scan on bloom_snowflake_dim_1_2 d12 - Bloom Filter 2: keys=(id) + Hash Cond: (d2.id22 = d22.id) + -> Hash Join + Hash Cond: (d2.id21 = d21.id) + -> Seq Scan on bloom_snowflake_dim_2 d2 + -> Hash + -> Seq Scan on bloom_snowflake_dim_2_1 d21 -> Hash - Bloom Filter 2 - -> Hash Join - Hash Cond: (d1.id11 = d11.id) - -> Seq Scan on bloom_snowflake_dim_1 d1 - Bloom Filter 1: keys=(id11) - -> Hash - Bloom Filter 1 - -> Seq Scan on bloom_snowflake_dim_1_1 d11 - Filter: (r < '0.5'::double precision) + -> Seq Scan on bloom_snowflake_dim_2_2 d22 -> Hash + Bloom Filter 3 -> Hash Join - Hash Cond: (d2.id22 = d22.id) - -> Hash Join - Hash Cond: (d2.id21 = d21.id) - -> Seq Scan on bloom_snowflake_dim_2 d2 - -> Hash - -> Seq Scan on bloom_snowflake_dim_2_1 d21 + Hash Cond: (d12.id = d1.id12) + -> Seq Scan on bloom_snowflake_dim_1_2 d12 + Bloom Filter 2: keys=(id) -> Hash - -> Seq Scan on bloom_snowflake_dim_2_2 d22 -(30 rows) + Bloom Filter 2 + -> Hash Join + Hash Cond: (d1.id11 = d11.id) + -> Seq Scan on bloom_snowflake_dim_1 d1 + Bloom Filter 1: keys=(id11) + -> Hash + Bloom Filter 1 + -> Seq Scan on bloom_snowflake_dim_1_1 d11 + Filter: (r < '0.5'::double precision) +(32 rows) -- needed to stabilize the join order SET join_collapse_limit = 1; diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index e896a61d386..e0e8b7ff7a6 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -7610,8 +7610,10 @@ where exists (select 1 from t t4 Hash Cond: (t6.b = t4.b) -> Seq Scan on pg_temp.t t6 Output: t6.a, t6.b + Bloom Filter 2: keys=(t6.b) expected=5.5% -> Hash Output: t4.b, t5.b, t5.a + Bloom Filter 2 -> Hash Join Output: t4.b, t5.b, t5.a Inner Unique: true @@ -7628,7 +7630,7 @@ where exists (select 1 from t t4 -> Index Only Scan using t_a_key on pg_temp.t t3 Output: t3.a Index Cond: (t3.a = t5.a) -(34 rows) +(36 rows) select t1.a from t t1 left join t t2 on t1.a = t2.a @@ -10261,20 +10263,22 @@ 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 - -> Hash + Hash Cond: (f3.x = f1.x) + -> Seq Scan on fkest f3 + Bloom Filter 1: keys=(x) + -> Hash + Bloom Filter 1 + -> Hash Join + Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10)) -> Seq Scan on fkest f2 Filter: (x100 = 2) - -> Hash - -> Seq Scan on fkest f1 - Filter: (x100 = 2) -(11 rows) + -> Hash + -> Seq Scan on fkest f1 + Filter: (x100 = 2) +(13 rows) rollback; -- diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index 2fa9ba14b2a..9cb1d87066a 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -2485,8 +2485,8 @@ SELECT * FROM cj_target; tid | balance | val -----+---------+---------------------------------- 3 | 400 | initial source2 updated by merge - 1 | 110 | initial source2 200 1 | 220 | initial source2 200 + 1 | 110 | initial source2 200 2 | 320 | initial source2 300 (4 rows) @@ -2501,8 +2501,8 @@ SELECT * FROM cj_target; tid | balance | val -----+---------+---------------------------------- 3 | 400 | initial source2 updated by merge - 1 | 110 | initial source2 200 1 | 220 | initial source2 200 + 1 | 110 | initial source2 200 2 | 320 | initial source2 300 10 | 100 | join input 10 | 400 | join input diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out index 93585f7f591..e2d154e77cd 100644 --- a/src/test/regress/expected/returning.out +++ b/src/test/regress/expected/returning.out @@ -723,30 +723,28 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57 Hash Cond: (joinme_1.f2j = foo_1.f2) -> Seq Scan on pg_temp.joinme joinme_1 Output: joinme_1.ctid, joinme_1.f2j + Bloom Filter 2: keys=(joinme_1.f2j) expected=3.0% -> Hash 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: foo_1.f2, foo_1.tableoid, foo_1.ctid, joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid Hash Cond: (joinme.f2j = foo_1.f2) - -> Hash Join - Output: joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid - Hash Cond: (joinme.f2j = foo_2.f2) - -> Seq Scan on pg_temp.joinme - Output: joinme.ctid, joinme.other, joinme.f2j - Bloom Filter 1: keys=(joinme.f2j) expected=0.5% - Bloom Filter 2: keys=(joinme.f2j) expected=2.0% - -> Hash - Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid - Bloom Filter 1 + -> Seq Scan on pg_temp.joinme + Output: joinme.ctid, joinme.other, joinme.f2j + Bloom Filter 1: keys=(joinme.f2j) expected=0.5% + -> Hash + 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) - -> Hash - Output: foo_1.f2, foo_1.tableoid, foo_1.ctid - Bloom Filter 2 - -> Seq Scan on pg_temp.foo foo_1 - Output: foo_1.f2, foo_1.tableoid, foo_1.ctid -(31 rows) + -> 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; -- 2.43.7