From 6d4a45b3ff5bb25f8a14a1c2c3e34a92994a25a3 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 v4 2/4] PoC: Bloom filter pushdown during path construction We can't decide which filters to push down during plan creation, it's too late. At that point we've already created paths with expected cardinalities, calculated the costs, etc. If we inject new filters to the scan nodes, the row counts won't match and it'll be confusing (effectively impossible to decide if a difference is a misestimate or just do to a pushed-down filter). It also means we can't consider the effect during the "bottom-up" phase, e.g. when picking algorithms for the other joins, etc. We would likely improve the current plan, but it prevents us from picking an alternative cheaper plan. To address this, add "expected filters" as a feature of a path, and propage them during joins etc. This is similar concept to path keys, tracking "interesting" orderings for a path. At the scan level, figure out a list of "interesting" filters. This is done by inspecting the joins the scanned relation participates in, and picking a limited number of sufficiently selective filters, and then it generates paths expecting different combinations of filters. The number of combinations grows with 2^n, so with 3 filters we get 9 paths (8 new ones), with 4 it's 16, etc. And this is for each scan node. For a query with many joins, it can grow pretty quick. So we need to be conservative, in order to prevent an explosion of the number of paths. The patch simply picks a limited number of the most selective filters (e.g. 3 filters, each eliminating at least 30% tuples). We could refine how this is decided, of course. For example, CustomScan could/should have a say in the costing, somehow. At the join level, we need to consider if a join is matching a filter expected by the input paths or not. * A join "matches" a filter of an input path, if it's the join from which the filter was derived. * A join can "satisfy" a filter expected by the outer innput path, if it matches it, and if it's a hash join. A join can never satisfy filters expected by the inner input. * A join has to satisfy all filters matching the inner/outer path. This implies that: * NestLoop/MergeJoin can never satisfy any filters. The places creating these joins have to ignore all paths with such filters. But these joins can still propagate all other filters, in case one of the later joins happens to be a hashjoin satisfying them. * HashJois can satisfy filters expected by outer input path, or propagate filters not matching the join. Notes: * We could also consider the "remaining row count" after a filter, and only consider filters if it's above some limit. E.g. it there's less than 1000 tuples, there's little point in pushing down additional filters. This would help with reducing the impact of path explosion. * When figuring out interesting filters, we need to be careful about outer joins. Outer joins are not a good filter source, because we can't eliminate any rows even if the join clause is very selective. * We still do the pushdown when creating the plan, but it's very mechanical - all the decisions have been made earlier. It's not possible (allowed) to mismatch - we can't pushdown a filter not expected by a scan, and the scan can't expect a filter that has not been pushdown by the plan. --- src/backend/commands/explain.c | 49 +- src/backend/executor/nodeHashjoin.c | 19 +- src/backend/optimizer/path/allpaths.c | 463 ++++++++++++++++ src/backend/optimizer/path/costsize.c | 17 + src/backend/optimizer/path/equivclass.c | 179 ++++++ src/backend/optimizer/path/joinpath.c | 524 +++++++++++++++--- src/backend/optimizer/plan/createplan.c | 257 ++++----- src/backend/optimizer/util/pathnode.c | 158 ++++++ src/backend/utils/misc/guc_parameters.dat | 20 + src/backend/utils/misc/postgresql.conf.sample | 2 + src/include/nodes/pathnodes.h | 66 +++ src/include/optimizer/cost.h | 2 + src/include/optimizer/pathnode.h | 5 + src/include/optimizer/paths.h | 6 + src/test/regress/expected/eager_aggregate.out | 178 ++---- src/test/regress/expected/graph_table.out | 14 +- src/test/regress/expected/join.out | 425 +++++++------- src/test/regress/expected/join_hash.out | 16 +- src/test/regress/expected/merge.out | 106 ++-- src/test/regress/expected/misc_functions.out | 14 +- .../regress/expected/partition_aggregate.out | 34 +- src/test/regress/expected/partition_join.out | 452 ++------------- src/test/regress/expected/predicate.out | 8 +- src/test/regress/expected/returning.out | 42 +- src/test/regress/expected/stats_ext.out | 23 +- src/test/regress/expected/subselect.out | 83 +-- src/test/regress/expected/updatable_views.out | 19 +- src/test/regress/expected/window.out | 15 +- src/test/regress/expected/with.out | 112 ++-- 29 files changed, 2004 insertions(+), 1304 deletions(-) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 8893e36c8aa..0ba1319d79f 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -2589,19 +2589,34 @@ show_upper_qual(List *qual, const char *qlabel, /* * show_bloom_filter_info - * Show info about every bloom filter pushed down to a scan node. + * Show info about every Bloom filter pushed down to a scan node. * * In TEXT format each filter is rendered on a single line, e.g. * - * Bloom Filter N: (a, b) producer=3 checked=99999 rejected=99990 + * Bloom Filter N: keys=(a, b) checked=99999 rejected=99990 * - * The checked/rejected fields are omitted outside of ANALYZE). In - * structured formats we emit a group per filter with the same fields - * broken out as properties. + * The checked/rejected fields are omitted outside of ANALYZE). In structured + * formats we emit a group per filter with the same fields broken out as + * properties. * - * Called from the per-recipient cases in ExplainNode; the recipient is - * expected to be a scan node in the current PoC, but nothing here - * depends on that. + * Called from the per-recipient cases in ExplainNode; the recipient is expected + * to be a scan node, but nothing here depends on that. We may choose to push + * filters to other nodes in the plan. + * + * This only prints information about the keys, and the checked/rejected counts. + * Detailed information about the filter itself (number of bits, number of hash + * functions, ...) is available on the producer side. + * + * XXX It might be a good idea to show the expected filter selectivity too, + * not just the one actually observed during execution. That'd make it easier + * to reason about the decisions and review the plan changes. + * + * XXX If we choose other filter types, we'd need some general way to show the + * information. I don't know if we should have a "generic" information provided + * by all the filters, or if we would need a way to print custom information. + * Chances are we'd have a limited number of supported filter types, in which + * case we can have a show_ function for each type. Only if users could inject + * arbitrary filters, that'd be an issue. But that seems unlikely. */ static void show_bloom_filter_info(PlanState *planstate, List *ancestors, @@ -3599,9 +3614,21 @@ show_hash_info(HashState *hashstate, ExplainState *es) } /* - * Show infromation about the bloom filter produced by this Hash node - * (if any). For plain EXPLAIN, the filter is not initialized / built, - * but we still show available plan-time metadata (at least the ID). + * Show infromation about the Bloom filter produced by this Hash node (if + * any). For plain EXPLAIN, the filter is not initialized / built, but we + * still show available plan-time metadata (at least the ID). + * + * Once the filter is built, we show the various filter parameters (number + * of bits, hash functions, ...). Note that even with EXPLAIN ANALYZE the + * filter may not be built, due to the hashjoin delaying the Hash build + * until on the first outer tuple. + * + * XXX We don't show the keys, because those are always the join keys. + * + * XXX I think it makes sense to show the checked/rejected counters both + * here and for the consumer. If/when we allow multiple consumers for the + * filter, then this would show the "summary" while the scan node would + * show just counters for that one consumer. */ if (hashstate->bloom_filter_id > 0) { diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index 8fa7af4cfef..e3467f14739 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -2019,15 +2019,16 @@ ExecHashJoinInitializeWorker(HashJoinState *state, * BLOOM FILTER PUSHDOWN * * The pushdown decision is done in try_push_bloom_filter, when constructing - * the plan from the selected paths (see createplan.c). It decides which scan - * node should receive the bloom filter (if any), and what expressions it - * should use to calculate the hash value. + * the plan from the selected paths. The input paths track filters "expected" + * by scan nodes included in that path (if any). The planner then ensures all + * expected filters are either satisfied (by matching joins) or propagated up. + * In a valid plan all expected filters are satisfied. * * Then at execution time: * * - ExecInitHashJoin registers itself in EState.es_bloom_producers * before recursing into child plans, so by the time a recipient's - * ExecInit runs, the producer is already discoverable by plan_node_id. + * ExecInit runs, the producer is already discoverable by filter ID. * This registration only happens when there's at least one consumer. * It also sets want_bloom_filter for the Hash node. * @@ -2035,25 +2036,25 @@ ExecHashJoinInitializeWorker(HashJoinState *state, * when HashState.want_bloom_filter is set (so no work happens when * nobody will probe). * - * - Nodes with non-NIL plan->bloom_filters (and supporting bloom + * - Nodes with non-NIL plan->bloom_filters (and supporting Bloom * filters) call ExecInitBloomFilters() during its own ExecInit, * which looks up the producer node (in the EState), compiles * ExprStates for the hash expressions, etc. The filter state * (BloomFilterState) gets added to ps->bloom_filters (a node may - * have multiple bloom filters from different hash joins). + * have multiple Bloom filters from different hash joins). * * - The per-tuple loop of the scan node calls ExecBloomFilters() (much * like ExecQual) to test the tuple against every attached filter, * dropping it on the first filter that excludes it. For scan nodes * this call happens in ExecScanExtended. * - * The scan nodes reach the bloom filter via the HashJoinState pointer + * The scan nodes reach the Bloom filter via the HashJoinState pointer * added to EState.es_bloom_producers, so that the rescans etc. (filter * freed + recreated when the hash table is destroyed and rebuilt) are - * transparent to the consumer. The bloom filter gets reallocated after + * transparent to the consumer. The Bloom filter gets reallocated after * a rescan, so the pointer to it may change. * - * XXX It's possible the bloom filter gets pushed down to a node that + * XXX It's possible the Bloom filter gets pushed down to a node that * fails to initialize/use it. It'll be added to the bloom_filters list, * but if the node does not call ExecInitBloomFilters, the filter will * be unused. diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index c134594a21a..8829c7c3108 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -107,6 +107,9 @@ static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static List *find_interesting_bloom_filters(PlannerInfo *root, + RelOptInfo *rel); +static void generate_expected_filter_paths(PlannerInfo *root, RelOptInfo *rel); static void set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, @@ -603,6 +606,16 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, !bms_equal(rel->relids, root->all_query_rels)) generate_useful_gather_paths(root, rel, false); + /* + * For plain base relations, consider generating additional scan paths + * that anticipate a Bloom filter being pushed down from a hash join above + * (see find_interesting_bloom_filters). These paths have reduced row + * estimates and are consumed by join path generation. + */ + if (rel->reloptkind == RELOPT_BASEREL && + rte->rtekind == RTE_RELATION) + generate_expected_filter_paths(root, rel); + /* Now find the cheapest of the paths for this rel */ set_cheapest(rel); @@ -884,6 +897,456 @@ create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel) add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers)); } +/* + * bloom_em_matches_anybarevar + * ec_matches_callback used by find_interesting_bloom_filters: accept any + * EquivalenceClass member that is a bare Var of the target relation. The + * Bloom filter pushdown logic (createplan.c) only supports recipients whose + * join keys are bare Vars, so we mirror that requirement here. + */ +static bool +bloom_em_matches_anybarevar(PlannerInfo *root, RelOptInfo *rel, + EquivalenceClass *ec, EquivalenceMember *em, + void *arg) +{ + Var *var; + + /* We're looking only for bare Var expressions. */ + if (!IsA(em->em_expr, Var)) + return false; + + /* + * Is the Var referencing a normal (non-system) attribute in the relation + * we're processing (generating scans for)? + * + * FIXME Can we have (varlevelsup != 0) for baserels? I don't think we can + * have outer referecenses in that place. + */ + var = (Var *) em->em_expr; + if (var->varno != rel->relid || + var->varattno <= 0 || + var->varlevelsup != 0) + return false; + + return true; +} + +/* + * bloom_filter_recipient_reachable + * Check that a Bloom filter owned by owner_relid and built from + * build_relids could actually be pushed to the owner's scan. + * + * A pushed-down filter removes owner tuples that have no match on the build + * side, so it can only be applied where the join that realizes it drops such + * unmatched owner (probe) tuples. If an outer join null-extends the owner + * before it can be joined to the build side, the filter would change the + * result and is therefore unusable: at plan time find_bloom_filter_recipient + * would refuse to descend into that side and find no recipient (see also + * bloom_join_side_preserved, which is checking for this situation). + * + * Picking such a filter only to throw it away later wastes planner effort, + * and we might also ignore some other filters because of that. It's better + * to eliminate it right away. + * + * This is primarily an optimization - we don't want to generate paths that + * would ultimately be useless, and possibly not generating paths for other + * filters. The correctness is still guaranteed by the propagation logic in + * compute_join_expected_filters(), which rejects cases that would carry a + * filter across a non-preserved join side. That guarantees we don't pick a + * plan with such filters, only to find about the issue in createplan.c. + */ +static bool +bloom_filter_recipient_reachable(PlannerInfo *root, Index owner_relid, + Relids build_relids) +{ + ListCell *lc; + + foreach(lc, root->join_info_list) + { + SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc); + + switch (sjinfo->jointype) + { + case JOIN_LEFT: + case JOIN_ANTI: + + /* + * The syntactic RHS is null-extended. If the owner is on it + * but the build side is reached from the preserved LHS, the + * owner must cross this outer join on its nullable side. + */ + if (bms_is_member(owner_relid, sjinfo->syn_righthand) && + !bms_is_subset(build_relids, sjinfo->syn_righthand)) + return false; + break; + case JOIN_FULL: + + /* + * Both sides are null-extended, so the filter is unusable + * whenever the owner and the build side sit on opposite sides + * of the join. + */ + if (bms_is_member(owner_relid, sjinfo->syn_lefthand) && + !bms_is_subset(build_relids, sjinfo->syn_lefthand)) + return false; + if (bms_is_member(owner_relid, sjinfo->syn_righthand) && + !bms_is_subset(build_relids, sjinfo->syn_righthand)) + return false; + break; + default: + /* INNER and SEMI joins never null-extend the owner. */ + break; + } + } + + return true; +} + +/* + * find_interesting_bloom_filters + * Identify Bloom filters that a hash join above this scan could push down, + * and that are selective enough to be worth costing for. + * + * We look for hashjoinable equality join clauses where this rel's side is a + * bare Var, derived both from EquivalenceClasses and from non-EC joininfo + * clauses. Clauses are grouped by the relids on the other ("build") side of + * the join; each group becomes a candidate filter whose expected surviving + * fraction is estimated as the semijoin selectivity of those clauses. + * + * A candidate is "interesting" only if it is expected to eliminate at least + * bloom_filter_pushdown_threshold of the rel's tuples. We keep at most + * bloom_filter_pushdown_max of the most selective candidates, and return them + * as a list of ExpectedFilter nodes. + * + * XXX This needs to be careful to not interfere with the general selectivity + * estimation, performed by clauselist_selectivity(). We'll estimate the filter + * selectivity using a made-up sjinfo with JOIN_INNER, which may not match + * the actual join. The selectivities must not leak - this is why this function + * does not collect the RestrictInfos but only the clauses. If we used the + * RestrictInfos, the clauselist_selectivity would cache the incorrect result + * in them, and it'd affect the planning in weird ways. + * + * FIXME Maybe there's a better way to calculate the filter selectivity? + */ +static List * +find_interesting_bloom_filters(PlannerInfo *root, RelOptInfo *rel) +{ + List *candidates; + List *group_relids = NIL; /* parallel: Relids per group */ + List *group_clauses = NIL; /* parallel: List of clauses */ + List *result = NIL; + ListCell *lc; + + if (!enable_hashjoin_bloom) + return NIL; + + if (bloom_filter_pushdown_max <= 0) + return NIL; + + if (rel->reloptkind != RELOPT_BASEREL) + return NIL; + + /* Collect candidate hashjoinable equality clauses for this rel. */ + candidates = generate_implied_equalities_for_all_columns(root, rel, + bloom_em_matches_anybarevar, + NULL, NULL); + + foreach(lc, rel->joininfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + /* EC-derived clauses are already covered above. */ + if (rinfo->parent_ec != NULL) + continue; + + candidates = lappend(candidates, rinfo->clause); + } + + /* Group candidate clauses by their build-side relids. */ + foreach(lc, candidates) + { + Node *clause = (Node *) lfirst(lc); + Node *left; + Node *right; + Node *ownerexpr; + Oid opno; + Relids clause_relids; + Relids build_relids; + int buildrel; + ListCell *lc2; + ListCell *lc3; + bool found; + + /* strip RestrictInfo (see comment above) */ + if (IsA(clause, RestrictInfo)) + clause = (Node *) ((RestrictInfo *) clause)->clause; + + /* + * Only care about (Expr op Expr) clauses. We know one side has to be + * a bare Var node, from the "owner" side (which is the scan node). + * The other side can be arbitrary expression on the other relation. + */ + if (!is_opclause(clause) || + list_length(((OpExpr *) clause)->args) != 2) + continue; + + opno = ((OpExpr *) clause)->opno; + left = get_leftop(clause); + right = get_rightop(clause); + + /* Identify which side is a bare Var of this rel (the owner side). */ + /* XXX replace this with a macro shared with bloom_em_matches_anybarevar */ + if (IsA(left, Var) && ((Var *) left)->varno == rel->relid && + ((Var *) left)->varattno > 0 && ((Var *) left)->varlevelsup == 0) + ownerexpr = left; + else if (IsA(right, Var) && ((Var *) right)->varno == rel->relid && + ((Var *) right)->varattno > 0 && + ((Var *) right)->varlevelsup == 0) + ownerexpr = right; + else + continue; + + /* Operator must be hashjoinable on the owner's input type. */ + if (!op_hashjoinable(opno, exprType(ownerexpr))) + continue; + + /* + * The build side must be a single base relation; that's what the + * recipient lookup and our selectivity estimate can handle. + * + * XXX I don't think this restriction is necessary. We can allow the + * build side to be a join. I don't see why that would be a problem. + */ + clause_relids = pull_varnos(root, (Node *) clause); + build_relids = bms_difference(clause_relids, rel->relids); + if (!bms_get_singleton_member(build_relids, &buildrel) || + buildrel >= root->simple_rel_array_size || + root->simple_rel_array[buildrel] == NULL || + root->simple_rel_array[buildrel]->reloptkind != RELOPT_BASEREL) + { + bms_free(build_relids); + continue; + } + + /* Add to an existing group, or start a new one. */ + /* XXX Maybe we sould have a HTAB with the relids as a key? But the + * lists should not be that long, I think. */ + found = false; + forboth(lc2, group_relids, lc3, group_clauses) + { + Relids grelids = (Relids) lfirst(lc2); + + if (bms_equal(grelids, build_relids)) + { + lfirst(lc3) = lappend((List *) lfirst(lc3), clause); + found = true; + + /* added to an existing group, don't keep the relids around */ + bms_free(build_relids); + + break; + } + } + + if (!found) /* new group */ + { + group_relids = lappend(group_relids, build_relids); + group_clauses = lappend(group_clauses, list_make1(clause)); + } + } + + /* + * We have collected all potentially intresting filters. Evaluate selectivity + * of each group and keep only the most interesting filters. Filters have to + * eliminate at least bloom_filter_pushdown_threshold tuples, and we keep + * only bloom_filter_pushdown_max most selective ones. + */ + { + ListCell *lcr = list_head(group_relids); + ListCell *lcc = list_head(group_clauses); + + while (lcr != NULL && lcc != NULL) + { + Relids build_relids = (Relids) lfirst(lcr); + List *clauses = (List *) lfirst(lcc); + SpecialJoinInfo sjinfo; + Selectivity sel; + + init_dummy_sjinfo(&sjinfo, rel->relids, build_relids); + sjinfo.jointype = JOIN_SEMI; + + sel = clauselist_selectivity(root, clauses, 0, JOIN_SEMI, &sjinfo); + + if ((sel <= 1.0 - bloom_filter_pushdown_threshold) && + (sel > 0.0) && /* XXX seems unnecessary */ + bloom_filter_recipient_reachable(root, rel->relid, build_relids)) + { + ExpectedFilter *f = makeNode(ExpectedFilter); + + f->owner_relid = rel->relid; + f->build_relids = build_relids; + f->clauses = clauses; + f->selectivity = sel; + result = lappend(result, f); + } + + lcr = lnext(group_relids, lcr); + lcc = lnext(group_clauses, lcc); + } + } + + /* + * We only connsider a limited number of interesting filters, to prevent + * path explosion. If we found too many, keep only the most selective ones + * (with smallest surviving fraction of tuples), to bound the number of + * generated paths. + * + * XXX This also aligns with good join orders - those tend to perform the + * most selective joins first. So we get to build the filters soon, even + * if the hashjoin optimization is not disabled. + */ + while (list_length(result) > bloom_filter_pushdown_max) + { + ExpectedFilter *worst = NULL; + ListCell *lcw; + + foreach(lcw, result) + { + ExpectedFilter *f = (ExpectedFilter *) lfirst(lcw); + + if (worst == NULL || f->selectivity > worst->selectivity) + worst = f; + } + result = list_delete_ptr(result, worst); + } + + return result; +} + +/* + * generate_expected_filter_paths + * Generate additional scan paths that anticipate one or more pushed-down + * Bloom filters. + * + * For each non-empty subset of the interesting filters, we clone every eligible + * existing scan path, reducing its row estimate by the combined selectivity and + * attaching the corresponding ExpectedFilter nodes. + * + * These paths are kept alongside the regular paths (add_path keeps paths with + * differing expected_filters) and are consumed by join path generation; + * set_cheapest never selects them. + * + * XXX We must not clone paths that already have expected filters. + * + * XXX The cloning is a rather dirty way to copy paths. It does not readjust the + * cost in a reasonable way. For example custom scans could do something smart + * with the filters, so it should have a chance to deal with that. A cleaner + * solution might be to actually pass the filters to the various "create" + * function, like create_seqscan_path/... For CustomScan nodes we can probably + * do most of this in the set_rel_pathlist_hook, somewhere. Maybe that needs + * some helper methods, though. And maybe it will need to pass some of the info + * through the callbacks? Not sure, someone has to try that. + * + * XXX This may need some major changes to work with custom scans. Right now we + * only consider filters exactly matching the hash keys, so if the hashjoin is + * on (t1.a = t2.a AND t1.b = t2.b), then the filter will be on (a,b). But a + * custom scan may prefer "split" filters on each column independently. We'd + * need a way for the custom scan to indicate that, and we'd need to apply this + * only to the "matching" scan paths (and not to any other scan paths). But + * we only look at the paths after selecting the "interesting" filters, so we'd + * need to rethink that - we'd need to make the "interesting" filters specific + * to a path, or something like that. + */ +static void +generate_expected_filter_paths(PlannerInfo *root, RelOptInfo *rel) +{ + List *filters; + List *basepaths = NIL; + int nfilters; + uint32 combo; + ListCell *lc; + + filters = find_interesting_bloom_filters(root, rel); + if (filters == NIL) + return; + + nfilters = list_length(filters); + + /* + * Snapshot the existing unparameterized, non-partial scan paths of a + * supported type. We must snapshot before calling add_path(), which + * mutates rel->pathlist. + */ + foreach(lc, rel->pathlist) + { + Path *path = (Path *) lfirst(lc); + + /* XXX Is parameterization really a problem? Always? */ + if (path->param_info != NULL || path->expected_filters != NIL) + continue; + + switch (nodeTag(path)) + { + case T_Path: + case T_IndexPath: + case T_BitmapHeapPath: + case T_TidPath: + case T_TidRangePath: + basepaths = lappend(basepaths, path); + break; + default: + break; + } + } + + if (basepaths == NIL) + return; + + /* + * Generate all combinations of the interesting filters. We do that by + * iterating 1 to (2^n-1), which generates all bitmask in between. Those + * are the subsets. + * + * XXX This is a good demonstration why we need to keep the number of + * filters low + * + * XXX Maybe we should also stop adding filters once the other filters + * already eliminate enought tuples. Say, we know F1 alone eliminates 99% + * tuples. Does it make sense to also consider [F1,F2]? Probably not. We + * could track "maximum" sets, and reject combinations containing one + * of those. We'd need to generate sets of increasing size, the iteration + * does not do that. But that's not hard. + */ + for (combo = 1; combo < ((uint32) 1 << nfilters); combo++) + { + List *subset = NIL; + int i = 0; + ListCell *lcf; + + foreach(lcf, filters) + { + if (combo & ((uint32) 1 << i)) + subset = lappend(subset, lfirst(lcf)); + i++; + } + + /* + * All filtered paths for this combo share the same expected_filters + * list. That's safe: the list is never modified, and add_path() only + * ever frees the Path node itself, not its expected_filters. + */ + foreach(lc, basepaths) + { + Path *base = (Path *) lfirst(lc); + Path *newpath; + + newpath = create_filtered_scan_path(root, base, subset); + if (newpath != NULL) + add_path(rel, newpath); + } + } +} + /* * set_tablesample_rel_size * Set size estimates for a sampled relation diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index c3072a29ccc..8740889094f 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -166,6 +166,23 @@ bool enable_partition_pruning = true; bool enable_presorted_aggregate = true; bool enable_async_append = true; +/* + * Minimum fraction of outer tuples a pushed-down hash-join Bloom filter must + * be expected to eliminate for the planner to treat it as "interesting" and + * generate filter-aware scan paths. A value of 0.3 means a filter is only + * considered if it is expected to discard at least 30% of the scanned tuples. + */ +double bloom_filter_pushdown_threshold = 0.3; + +/* + * Upper bound on the number of distinct interesting Bloom filters considered + * for a single scan relation. This bounds the number of additional paths + * generated per scan (the planner enumerates non-empty subsets of the + * interesting filters, i.e. up to 2^bloom_filter_pushdown_max - 1 extra + * paths per base scan path). + */ +int bloom_filter_pushdown_max = 3; + typedef struct { PlannerInfo *root; diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index e3697df51a2..6722b74f401 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -75,6 +75,15 @@ static RestrictInfo *create_join_clause(PlannerInfo *root, EquivalenceMember *leftem, EquivalenceMember *rightem, EquivalenceClass *parent_ec); +static int generate_implied_equalities_for_column_ec(PlannerInfo *root, + RelOptInfo *rel, + EquivalenceClass *cur_ec, + ec_matches_callback_type callback, + void *callback_arg, + Relids prohibited_rels, + Relids parent_relids, + bool is_child_rel, + List **result); static bool reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo, bool outer_on_left); @@ -3211,6 +3220,107 @@ eclass_member_iterator_next(EquivalenceMemberIterator *it) return NULL; } +/* + * generate_implied_equalities_for_column_ec + * Workhorse for generate_implied_equalities_for_column() and + * generate_implied_equalities_for_all_columns(). Considers a single + * EquivalenceClass cur_ec: if it has a member matching the target column + * (as identified by the callback), generate EC-derived joinclauses + * equating that member to each other-relation member, appending them to + * *result. Returns the number of clauses generated. + */ +static int +generate_implied_equalities_for_column_ec(PlannerInfo *root, + RelOptInfo *rel, + EquivalenceClass *cur_ec, + ec_matches_callback_type callback, + void *callback_arg, + Relids prohibited_rels, + Relids parent_relids, + bool is_child_rel, + List **result) +{ + EquivalenceMemberIterator it; + EquivalenceMember *cur_em; + ListCell *lc2; + int ngenerated = 0; + + /* + * Won't generate joinclauses if const or single-member (the latter test + * covers the volatile case too) + */ + if (cur_ec->ec_has_const || list_length(cur_ec->ec_members) <= 1) + return 0; + + /* + * Scan members, looking for a match to the target column. Note that + * child EC members are considered, but only when they belong to the + * target relation. (Unlike regular members, the same expression could be + * a child member of more than one EC. Therefore, it's potentially + * order-dependent which EC a child relation's target column gets matched + * to. This is annoying but it only happens in corner cases, so for now we + * live with just reporting the first match. See also + * get_eclass_for_sort_expr.) + */ + setup_eclass_member_iterator(&it, cur_ec, rel->relids); + while ((cur_em = eclass_member_iterator_next(&it)) != NULL) + { + if (bms_equal(cur_em->em_relids, rel->relids) && + callback(root, rel, cur_ec, cur_em, callback_arg)) + break; + } + + if (!cur_em) + return 0; + + /* + * Found our match. Scan the other EC members and attempt to generate + * joinclauses. Ignore children here. + */ + foreach(lc2, cur_ec->ec_members) + { + EquivalenceMember *other_em = (EquivalenceMember *) lfirst(lc2); + Oid eq_op; + RestrictInfo *rinfo; + + /* Child members should not exist in ec_members */ + Assert(!other_em->em_is_child); + + /* Make sure it'll be a join to a different rel */ + if (other_em == cur_em || + bms_overlap(other_em->em_relids, rel->relids)) + continue; + + /* Forget it if caller doesn't want joins to this rel */ + if (bms_overlap(other_em->em_relids, prohibited_rels)) + continue; + + /* + * Also, if this is a child rel, avoid generating a useless join to its + * parent rel(s). + */ + if (is_child_rel && + bms_overlap(parent_relids, other_em->em_relids)) + continue; + + eq_op = select_equality_operator(cur_ec, + cur_em->em_datatype, + other_em->em_datatype); + if (!OidIsValid(eq_op)) + continue; + + /* set parent_ec to mark as redundant with other joinclauses */ + rinfo = create_join_clause(root, cur_ec, eq_op, + cur_em, other_em, + cur_ec); + + *result = lappend(*result, rinfo); + ngenerated++; + } + + return ngenerated; +} + /* * generate_implied_equalities_for_column * Create EC-derived joinclauses usable with a specific column. @@ -3233,6 +3343,10 @@ eclass_member_iterator_next(EquivalenceMemberIterator *it) * * The caller can pass a Relids set of rels we aren't interested in joining * to, so as to save the work of creating useless clauses. + * + * XXX This could reuse generate_implied_equalities_for_column_ec for the + * inner loop, similarly to generate_implied_equalities_for_all_columns, but I + * chose to not do that for now. Better keep this as is. */ List * generate_implied_equalities_for_column(PlannerInfo *root, @@ -3353,6 +3467,71 @@ generate_implied_equalities_for_column(PlannerInfo *root, return result; } +/* + * generate_implied_equalities_for_all_columns + * Like generate_implied_equalities_for_column, but returns EC-derived + * joinclauses for *every* column of the relation, rather than stopping at + * the first column (EquivalenceClass) that yields any clauses. + * + * generate_implied_equalities_for_column() is designed for parameterized-path + * generation, where the goal is to find a single usable joinclause per column + * and there is no value in returning clauses for more than one column at a + * time. Some callers, however, are interested in joinclauses on all of the + * relation's columns simultaneously (for example, the Bloom filter pushdown + * logic, which may push down filters derived from several different columns at + * once). This variant therefore visits all of the relation's + * EquivalenceClasses and accumulates clauses from each. + * + * As with generate_implied_equalities_for_column(), the result for any single + * column is a redundant set of clauses equating that column to each of the + * other-relation values it is known to be equal to. + * + * XXX We don't really need the last two arguments, but we keep this as close + * to generate_implied_equalities_for_column as possible. + */ +List * +generate_implied_equalities_for_all_columns(PlannerInfo *root, + RelOptInfo *rel, + ec_matches_callback_type callback, + void *callback_arg, + Relids prohibited_rels) +{ + List *result = NIL; + bool is_child_rel = (rel->reloptkind == RELOPT_OTHER_MEMBER_REL); + Relids parent_relids; + int i; + + /* Should be OK to rely on eclass_indexes */ + Assert(root->ec_merging_done); + + /* Indexes are available only on base or "other" member relations. */ + Assert(IS_SIMPLE_REL(rel)); + + /* If it's a child rel, we'll need to know what its parent(s) are */ + if (is_child_rel) + parent_relids = find_childrel_parents(root, rel); + else + parent_relids = NULL; /* not used, but keep compiler quiet */ + + i = -1; + while ((i = bms_next_member(rel->eclass_indexes, i)) >= 0) + { + EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i); + + /* Sanity check eclass_indexes only contain ECs for rel */ + Assert(is_child_rel || bms_is_subset(rel->relids, cur_ec->ec_relids)); + + (void) generate_implied_equalities_for_column_ec(root, rel, cur_ec, + callback, callback_arg, + prohibited_rels, + parent_relids, + is_child_rel, + &result); + } + + return result; +} + /* * have_relevant_eclass_joinclause * Detect whether there is an EquivalenceClass that could produce diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 713283a73aa..5e65fda1419 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -869,6 +869,271 @@ get_memoize_path(PlannerInfo *root, RelOptInfo *innerrel, return NULL; } +/* + * bloom_join_side_preserved + * Can a pushed-down Bloom filter be applied below the given side of a + * join of this type without changing the join's result? + * + * A Bloom filter removes tuples from the scan it is pushed to. That is only + * safe on a side whose unmatched tuples the join would drop anyway: dropping + * a tuple early then matches the join's behaviour. On a null-extended + * (preserved-other-side) input it is unsafe, because removing a tuple there + * could suppress, or spuriously emit, null-extended rows. + * + * This is the path-time counterpart of find_bloom_filter_recipient() in + * createplan.c, which descends a plan tree toward the recipient scan: it may + * only descend into a join's outer (resp. inner) child when this function + * returns true for to_outer = true (resp. false). Keeping the two in sync is + * what guarantees that every filter we realize at path time has a reachable + * recipient at plan time. + */ +bool +bloom_join_side_preserved(JoinType jointype, bool to_outer) +{ + switch (jointype) + { + case JOIN_INNER: + return true; + case JOIN_LEFT: + case JOIN_SEMI: + case JOIN_ANTI: + return to_outer; + case JOIN_RIGHT: + case JOIN_RIGHT_SEMI: + case JOIN_RIGHT_ANTI: + return !to_outer; + case JOIN_FULL: + return false; + default: + return false; + } +} + +/* + * jointype_realizes_bloom_filter + * Can a hash join of this type build and push a Bloom filter to its + * outer (probe) side? + * + * This must match the join-type check in try_push_bloom_filter(), so that a + * filter we cost for is actually realized at plan time. Only join types that + * drop unmatched outer (probe) tuples are safe, since the filter eliminates + * probe tuples lacking an inner match. + * + * Note JOIN_RIGHT qualifies: it preserves unmatched tuples of the inner (build) + * side, not the outer (probe) side, so dropping unmatched probe tuples is still + * correct. + */ +static bool +jointype_realizes_bloom_filter(JoinType jointype) +{ + switch (jointype) + { + case JOIN_INNER: + case JOIN_RIGHT: + case JOIN_SEMI: + case JOIN_RIGHT_SEMI: + case JOIN_RIGHT_ANTI: + return true; + default: + return false; + } +} + +/* + * hashjoin_pushes_filter_to + * Would create_hashjoin_plan() be able to push a Bloom filter built from + * these hash clauses down to the scan of owner_relid? + * + * This mirrors try_push_bloom_filter()'s requirement that every hash key on + * the outer side be a bare Var of a single base relation. 'outer_relids' is + * the set of relids on the outer (probe) side of the join. + */ +static bool +hashjoin_pushes_filter_to(List *hashclauses, Relids outer_relids, + Index owner_relid) +{ + ListCell *lc; + + if (hashclauses == NIL) + return false; + + foreach(lc, hashclauses) + { + RestrictInfo *ri = (RestrictInfo *) lfirst(lc); + Node *outerside; + Var *var; + + if (!is_opclause(ri->clause) || + list_length(((OpExpr *) ri->clause)->args) != 2) + return false; + + /* Pick the side that belongs to the outer relation. */ + if (bms_is_subset(ri->left_relids, outer_relids)) + outerside = get_leftop(ri->clause); + else if (bms_is_subset(ri->right_relids, outer_relids)) + outerside = get_rightop(ri->clause); + else + return false; + + if (!IsA(outerside, Var)) + return false; + var = (Var *) outerside; + if (var->varno != owner_relid || + var->varattno <= 0 || + var->varlevelsup != 0) + return false; + } + + return true; +} + +/* + * compute_join_expected_filters + * Determine the expected Bloom filters a prospective join path should + * carry, applying the propagation and contradiction rules. + * + * Each filter expected by an input path is either: + * + * - propagated upward (its build side is not yet fully joined in), or + * + * - realized by this join (a hash join that is the filter's source and can + * push the filter to its outer side): it is dropped from the result, since + * it has now been applied. When 'realized' is non-NULL, such filters are + * appended to *realized so the caller can record them on the join path and + * later propagate the pushdown decision to the plan, or + * + * - contradicted: the join is the filter's source but cannot push it (it is + * not a suitable hash join). In that case the input path cannot be used + * for this join, so *contradicted is set true and NIL is returned. + * + * 'is_hashjoin'/'hashclauses' describe the join method; hashclauses is only + * meaningful for hash joins. + */ +static List * +compute_join_expected_filters(PlannerInfo *root, + Path *outer_path, Path *inner_path, + JoinType jointype, bool is_hashjoin, + List *hashclauses, bool *contradicted, + List **realized) +{ + List *result = NIL; + Relids outer_relids = outer_path->parent->relids; + Relids inner_relids = inner_path->parent->relids; + Relids join_relids; + int pass; + + *contradicted = false; + if (realized != NULL) + *realized = NIL; + + /* Fast path: neither input expects any filter. */ + if (outer_path->expected_filters == NIL && + inner_path->expected_filters == NIL) + return NIL; + + join_relids = bms_union(outer_relids, inner_relids); + + /* Examine the filters from both inputs. */ + for (pass = 0; pass < 2; pass++) + { + List *filters = (pass == 0) ? outer_path->expected_filters + : inner_path->expected_filters; + ListCell *lc; + + foreach(lc, filters) + { + ExpectedFilter *f = (ExpectedFilter *) lfirst(lc); + bool owner_in_outer; + Relids owner_relids; + Relids other_relids; + + owner_in_outer = bms_is_member(f->owner_relid, outer_relids); + owner_relids = owner_in_outer ? outer_relids : inner_relids; + other_relids = owner_in_outer ? inner_relids : outer_relids; + + if (bms_is_subset(f->build_relids, owner_relids)) + { + /* + * Build side already sits with the owner; this shouldn't + * normally happen (such a filter would have been resolved at a + * lower join), but if it does, just propagate it unchanged. + * + * We still must be able to reach the owner's scan from above, + * so the owner has to be on a side this join preserves (see + * bloom_join_side_preserved); otherwise the filter could not + * be pushed to a recipient and this path must be rejected. + */ + if (!bloom_join_side_preserved(jointype, owner_in_outer)) + goto contradiction; + result = lappend(result, f); + } + else if (bms_is_subset(f->build_relids, join_relids)) + { + /* This join is the source of the filter. */ + if (is_hashjoin && + owner_in_outer && + bms_is_subset(f->build_relids, other_relids) && + jointype_realizes_bloom_filter(jointype) && + hashjoin_pushes_filter_to(hashclauses, outer_relids, + f->owner_relid)) + { + /* Realized by this hash join; drop from propagation. */ + if (realized != NULL) + *realized = lappend(*realized, f); + continue; + } + else + { + /* Cannot realize the filter here: reject this path. */ + goto contradiction; + } + } + else + { + /* + * Build side not yet available; propagate. As above, the + * filter can only reach its recipient scan if the owner stays + * on a side this join preserves; if not, reject this path so + * we never realize a filter with no recipient at plan time. + */ + if (!bloom_join_side_preserved(jointype, owner_in_outer)) + goto contradiction; + result = lappend(result, f); + } + } + } + + bms_free(join_relids); + return result; + +contradiction: + *contradicted = true; + if (realized != NULL) + { + list_free(*realized); + *realized = NIL; + } + bms_free(join_relids); + list_free(result); + return NIL; +} + +/* + * set_join_path_expected_filters + * Attach the propagated expected filters to a freshly created join path + * and reduce its row estimate to reflect their combined selectivity. + */ +static void +set_join_path_expected_filters(Path *path, List *filters) +{ + if (filters == NIL) + return; + + path->expected_filters = filters; + path->rows = clamp_row_est(path->rows * + expected_filters_selectivity(filters)); +} + /* * try_nestloop_path * Consider a nestloop join path; if it appears useful, push it into @@ -967,26 +1232,71 @@ try_nestloop_path(PlannerInfo *root, nestloop_subtype | PGS_CONSIDER_NONPARTIAL, outer_path, inner_path, extra); - if (add_path_precheck(joinrel, workspace.disabled_nodes, - workspace.startup_cost, workspace.total_cost, - pathkeys, required_outer)) - { - add_path(joinrel, (Path *) - create_nestloop_path(root, - joinrel, - jointype, - &workspace, - extra, - outer_path, - inner_path, - extra->restrictlist, - pathkeys, - required_outer)); - } - else + /* + * Account for expected Bloom filters carried by the input paths. A + * nestloop never builds a Bloom filter, so if it is the source of any + * expected filter the path is contradicted and must be rejected; + * otherwise the filters propagate to the resulting path. + */ { - /* Waste no memory when we reject a path here */ - bms_free(required_outer); + bool contradicted; + List *jfilters; + + jfilters = compute_join_expected_filters(root, outer_path, inner_path, + jointype, false, NIL, + &contradicted, NULL); + + /* + * Contradicted means the inner/outer paths expect this join to realize + * one of the expected filters, but a nestloop can't do that. So these + * input paths are incompatible with a nestloop. + */ + if (contradicted) + { + bms_free(required_outer); + return; + } + + /* + * If the path expects any filters, it's excluded from the cost pruning + * performed by add_path (so don't bother with add_path_precheck either). + * Once a path has all filters satisfied (or there were no filters), do + * the pruning as usual. + * + * XXX We don't want the "regular" paths without filters to get removed, + * because we need the option to pick from join algorithms. Paths with + * filters would likely win (simply because there are fewer rows), but + * they only work with hashjoins. However, maybe the hashjoin won't work + * for some reason (e.g. it wouldn't fit into work_mem). + * + * XXX Maybe it'd be cleaner to do this in add_path_precheck (i.e. make + * it return true for paths with expected filters). + */ + if (jfilters != NIL || + add_path_precheck(joinrel, workspace.disabled_nodes, + workspace.startup_cost, workspace.total_cost, + pathkeys, required_outer)) + { + Path *nlpath; + + nlpath = (Path *) create_nestloop_path(root, + joinrel, + jointype, + &workspace, + extra, + outer_path, + inner_path, + extra->restrictlist, + pathkeys, + required_outer); + set_join_path_expected_filters(nlpath, jfilters); + add_path(joinrel, nlpath); + } + else + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + } } } @@ -1160,30 +1470,58 @@ try_mergejoin_path(PlannerInfo *root, outer_presorted_keys, extra); - if (add_path_precheck(joinrel, workspace.disabled_nodes, - workspace.startup_cost, workspace.total_cost, - pathkeys, required_outer)) - { - add_path(joinrel, (Path *) - create_mergejoin_path(root, - joinrel, - jointype, - &workspace, - extra, - outer_path, - inner_path, - extra->restrictlist, - pathkeys, - required_outer, - mergeclauses, - outersortkeys, - innersortkeys, - outer_presorted_keys)); - } - else + /* + * Account for expected Bloom filters carried by the input paths. A + * mergejoin never builds a Bloom filter, so it contradicts (and cannot + * use) any input path for which it would be the filter's source. + * Filter-bearing paths bypass the precheck, since their reduced cost + * isn't comparable to ordinary paths. + * + * XXX see the comments in try_nestloop_path + */ { - /* Waste no memory when we reject a path here */ - bms_free(required_outer); + bool contradicted; + List *jfilters; + + jfilters = compute_join_expected_filters(root, outer_path, inner_path, + jointype, false, NIL, + &contradicted, NULL); + if (contradicted) + { + bms_free(required_outer); + return; + } + + if (jfilters != NIL || + add_path_precheck(joinrel, workspace.disabled_nodes, + workspace.startup_cost, workspace.total_cost, + pathkeys, required_outer)) + { + Path *mjpath; + + mjpath = (Path *) create_mergejoin_path(root, + joinrel, + jointype, + &workspace, + extra, + outer_path, + inner_path, + extra->restrictlist, + pathkeys, + required_outer, + mergeclauses, + outersortkeys, + innersortkeys, + outer_presorted_keys); + set_join_path_expected_filters(mjpath, jfilters); + add_path(joinrel, mjpath); + } + else + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + } + } } @@ -1314,27 +1652,62 @@ try_hashjoin_path(PlannerInfo *root, initial_cost_hashjoin(root, &workspace, jointype, hashclauses, outer_path, inner_path, extra, false); - if (add_path_precheck(joinrel, workspace.disabled_nodes, - workspace.startup_cost, workspace.total_cost, - NIL, required_outer)) - { - add_path(joinrel, (Path *) - create_hashjoin_path(root, - joinrel, - jointype, - &workspace, - extra, - outer_path, - inner_path, - false, /* parallel_hash */ - extra->restrictlist, - required_outer, - hashclauses)); - } - else + /* + * Account for expected Bloom filters carried by the input paths. A hash + * join builds and pushes down a Bloom filter, so it realizes (and removes + * from propagation) any expected filter for which it is the source; other + * filters propagate upward. Filter-bearing paths bypass the precheck. + */ { - /* Waste no memory when we reject a path here */ - bms_free(required_outer); + bool contradicted; + List *jfilters; + List *realized; + + jfilters = compute_join_expected_filters(root, outer_path, inner_path, + jointype, true, hashclauses, + &contradicted, &realized); + + /* XXX Can a hashjoin contradict a filter? Probably not. */ + if (contradicted) + { + bms_free(required_outer); + return; + } + + if (jfilters != NIL || + add_path_precheck(joinrel, workspace.disabled_nodes, + workspace.startup_cost, workspace.total_cost, + NIL, required_outer)) + { + Path *hjpath; + + hjpath = (Path *) create_hashjoin_path(root, + joinrel, + jointype, + &workspace, + extra, + outer_path, + inner_path, + false, /* parallel_hash */ + extra->restrictlist, + required_outer, + hashclauses); + set_join_path_expected_filters(hjpath, jfilters); + + /* + * Record the filters this hash join realizes, so create_hashjoin_plan + * can push exactly those down (and no others) at plan-creation time. + */ + ((HashPath *) hjpath)->realized_filters = realized; + + add_path(joinrel, hjpath); + } + else + { + /* Waste no memory when we reject a path here */ + bms_free(required_outer); + return; + } } } @@ -2316,6 +2689,33 @@ hash_inner_and_outer(PlannerInfo *root, } } + /* + * Also consider outer paths that carry expected Bloom filters. These + * are deliberately excluded from cheapest_startup/total_path and from + * cheapest_parameterized_paths (see set_cheapest), so we must iterate + * the full outer pathlist to find them. A hash join is able to build + * and push down the filters, so these paths are useful here even when + * they would be contradicted at a non-hash join. + */ + foreach(lc1, outerrel->pathlist) + { + Path *outerpath = (Path *) lfirst(lc1); + + if (outerpath->expected_filters == NIL) + continue; + + if (PATH_PARAM_BY_REL(outerpath, innerrel)) + continue; + + try_hashjoin_path(root, + joinrel, + outerpath, + cheapest_total_inner, + hashclauses, + jointype, + extra); + } + /* * If the joinrel is parallel-safe, we may be able to consider a * partial hash join. diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 7ecb551aae6..51990b98419 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -4692,78 +4692,54 @@ create_mergejoin_plan(PlannerInfo *root, /* * BLOOM FILTER PUSHDOWN * - * When creating a hash join plan, consider building a bloom filter and - * pushing it down to the outer subtree. For now we only push filters to - * scan nodes containing all the join keys. When we find such scan node, - * we append the BloomFilter ID to the node's bloom_filters list, and - * increment the bloom_consumer_count for the hashjoin. + * When a hash join is created as a path, we decide whether it should build a + * Bloom filter and push it down to a scan on its outer (probe) side. That + * decision - which filters are selective enough to be worth building, and + * which scan they can be pushed to - is made entirely while creating paths + * (see find_interesting_bloom_filters and compute_join_expected_filters in the + * optimizer); and the chosen filters are recorded on the HashPath as its + * realized_filters. Here we merely propagate that decision into the plan: we + * never reconsider whether a filter is worthwhile, and in particular we never + * push a filter that was not selected as interesting when creating paths. * - * As setrefs hashn't run yet, the join keys are still the raw Vars. - * So it's safe to compare var->varno against the scanrelid, and copy - * the keys verbatim onto the recipient. setrefs will rewrite the Vars - * later as usual, just like for the recipient's qual. + * For each realized filter we locate the scan node for its owner relation in + * the outer subtree, append a BloomFilter (built from the hash keys belonging + * to that filter) to the scan's bloom_filters list, and increment the + * hashjoin's bloom_consumer_count. * - * XXX In most cases there'll be only a single consumer node. To get - * multiple consumers, we'd need either joins on the same keys, or - * ability to produce filters for subsets of the join keys (for cases - * where the join is more complex, and does not map to a single scan - * node directly). Seems like a possible future improvement. + * As setrefs hasn't run yet, the hash keys are still the raw Vars. So it's + * safe to compare var->varno against the scanrelid, and copy the keys verbatim + * onto the recipient. setrefs will rewrite the Vars later as usual, just like + * for the recipient's qual. * - * XXX Actually, we could have multiple consumer nodes for partitioned - * tables, where each partition gets a separate scan. Which seems like - * something we should support. + * XXX In most cases there'll be only a single consumer node. To get multiple + * consumers, we'd need either joins on the same keys, or ability to produce + * filters for subsets of the join keys (for cases where the join is more + * complex, and does not map to a single scan node directly). Seems like a + * possible future improvement. * - * XXX For simplicity, all outer join keys have to be bare Vars (from - * the same RTE). We could relax this later, and allow joins on more - * complex expressions. Not sure if that'll erase some of the benefits, - * which relies on filter probes being much cheaper hashtable probes. - * It also doesn't seem like a very common case. + * XXX Actually, we could have multiple consumer nodes for partitioned tables, + * where each partition gets a separate scan. Which seems like something we + * should support. * - * XXX The recipient node must be one of a small set of scan nodes. We - * could relax this, and allow pushing to other nodes (e.g. joins or - * aggregates). Future improvement. + * XXX The recipient node must be one of a small set of scan nodes. We could + * relax this, and allow pushing to other nodes (e.g. joins or aggregates). + * Future improvement. * - * XXX We don't currently push the same HashJoin to multiple recipients, - * but multiple HashJoins may attach a filter to the same scan node. + * XXX We don't currently push the same HashJoin to multiple recipients, but + * multiple HashJoins may attach a filter to the same scan node. * -------------------------------------------------------------------------- */ -/* - * bloom_join_side_preserved - * Decide if we can push filter to inner/outer side of a join. - * - * Outer joins that emit unmatched outer tuples (LEFT/ANTI/FULL) are - * skipped: dropping outer tuples there would be incorrect. - */ -static bool -bloom_join_side_preserved(JoinType jointype, bool to_outer) -{ - switch (jointype) - { - case JOIN_INNER: - return true; - case JOIN_LEFT: - case JOIN_SEMI: - case JOIN_ANTI: - return to_outer; - case JOIN_RIGHT: - case JOIN_RIGHT_SEMI: - case JOIN_RIGHT_ANTI: - return !to_outer; - case JOIN_FULL: - return false; - default: - return false; - } -} - /* * find_bloom_filter_recipient * Try to find a scan node to push filter to. * * We support pushing filter to a subset of scan nodes (could be extended * later). We support pushing filters through intermediate nodes (joins, - * sorts, ...). See bloom_join_side_preserved for joins. + * sorts, ...). See bloom_join_side_preserved (joinpath.c) for joins; the + * path-time propagation uses the same predicate, so any filter realized while + * costing paths is guaranteed a reachable recipient here. * * XXX We could push filters through more nodes - e.g. aggregates if the * hash keys match GROUP BY keys (are a subset of). @@ -4771,6 +4747,12 @@ bloom_join_side_preserved(JoinType jointype, bool to_outer) * XXX We could do pushdown to parallel parts of a query. But we'd need * a different way to communicate if a filter is built etc. (the worker * won't have access to the hashjoin state). + * + * XXX Not sure this handles partitioned tables correctly. Those will be below + * Append node, and we don't push through those. But the scans will still expect + * the filter, I think. Even if we pushed through Append node, it probably won't + * work because we expect a single consumer. But we'll have one consumer per + * scan of a partition. */ static Plan * find_bloom_filter_recipient(Plan *plan, Index target_relid) @@ -4842,142 +4824,92 @@ find_bloom_filter_recipient(Plan *plan, Index target_relid) /* * try_push_bloom_filter - * Attempt to pushdown a bloom filter for the current hashjoin. + * Push down the bloom filter the planner decided this hashjoin should + * build, recording it on the recipient scan node. * - * The filter pushdown happens during plan creation, i.e. after the plan was - * already selected. That is not entirely optimal, and it has a couple of - * annoying consequences. + * 'realized_filters' is the list of ExpectedFilter nodes the HashPath was + * found to realize while creating paths. We do not reconsider that decision + * here; if it is empty, this hashjoin pushes nothing. Otherwise we turn it + * into a concrete BloomFilter attached to the scan of the owner relation. * - * The main disadvantage is that injecting the filter to a scan node may - * significantly alter the number of tuples produced by that scan node. If a - * filter eliminates 99% of the rows, the scan produces 1/100 of the rows it - * was planned with. It would not affect the scan itself, but if there are - * other nodes (between the scan and the join), maybe we'd have planned them - * differently if we knew about the lower cardinality? + * A hash join builds a single bloom filter, populated with the combined hash + * value of all of its hash keys (see ExecHashTableInsert / bloom_add_element + * in nodeHash.c). The recipient must therefore probe with the matching full + * set of outer hash keys, so we build the filter from all of them. All + * realized filters of one hash join share the same owner relation (the outer + * side of every hash clause is a bare Var of that relation), so a single + * pushed-down filter covers them all. * - * Similarly, it's confusing in the explain. That is, we'll get "rows=N" - * with the planner cardinality (before the filter was pushed down), but - * then in EXPLAIN ANALYZE it'll get much lower values. It'd be easy to - * confuse with inaccurate estimates. - * - * It'd be better to know about the filter earlier, when constructing the scan - * path. But that's not quite feasible with our bottom-up planner. When planing - * the scan, we don't know which of the joins above it will be hashjoins, or - * if it can pushdown the filter. We'd have to speculate, or maybe build more - * paths with/without expectation of the bloom filter pushdown. But that seems - * not great, as it'd add overhead for everyone. + * Note the pushdown still happens during plan creation, i.e. after the plan was + * already selected. The selectivity of the filter was, however, accounted for + * while creating paths (the affected scan paths carry reduced row estimates), + * so the plan-time row counts already reflect the expected elimination. */ static void -try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan) +try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan, + List *realized_filters) { - List *hashkeys = hj->hashkeys; - List *hashops = hj->hashoperators; - List *hashcolls = hj->hashcollations; - ListCell *lc; - Index target_relid = 0; + ExpectedFilter *f; + Index owner_relid; Plan *recipient; BloomFilter *bf; - /* bail out if feature disabled. */ - if (!enable_hashjoin_bloom) - return; - - /* XXX shouldn't really happen, I think */ - if (hashkeys == NIL) + /* Nothing to do unless the planner chose to realize a filter here. */ + if (realized_filters == NIL) return; - Assert(list_length(hashkeys) == list_length(hashops)); - Assert(list_length(hashkeys) == list_length(hashcolls)); - /* - * Pushdown is unsafe for join types that emit unmatched outer tuples - * (LEFT/ANTI/FULL): we'd risk dropping outer tuples the join would - * otherwise have emitted (possibly NULL-extended). + * The feature must have been enabled when paths were built; otherwise no + * filter would have been realized. */ - switch (hj->join.jointype) - { - case JOIN_INNER: - case JOIN_RIGHT: - case JOIN_SEMI: - case JOIN_RIGHT_SEMI: - case JOIN_RIGHT_ANTI: - /* these join types are OK */ - break; - default: - return; - } + Assert(enable_hashjoin_bloom); + Assert(hj->hashkeys != NIL); + Assert(list_length(hj->hashkeys) == list_length(hj->hashoperators)); + Assert(list_length(hj->hashkeys) == list_length(hj->hashcollations)); /* - * All hashkeys must be bare Vars referencing the same base RTE. - * - * XXX We could be a bit less strict, and check that at least some of the - * hashkeys are bare Vars, not all of them. So with joins on multiple - * expressions we'd have better chance to push a filter down. Doesn't - * seem worth it, at least for now. + * All realized filters share the same owner relation (every hash clause's + * outer side is a bare Var of that relation). */ - foreach(lc, hashkeys) - { - Node *k = (Node *) lfirst(lc); - Var *var; - - /* not a plain Var */ - if (!IsA(k, Var)) - return; + f = (ExpectedFilter *) linitial(realized_filters); + owner_relid = f->owner_relid; - var = (Var *) k; +#ifdef USE_ASSERT_CHECKING + { + ListCell *lc; - /* - * Reject outer references, whole-row or system columns, and - * special varnos (not sure we can get them here, though). - */ - if ((var->varlevelsup != 0) || - (var->varattno <= 0) || - IS_SPECIAL_VARNO(var->varno)) - return; - - /* make sure all the vars are for the same relid */ - if (target_relid == 0) - target_relid = var->varno; - else if (var->varno != target_relid) - return; + foreach(lc, realized_filters) + Assert(((ExpectedFilter *) lfirst(lc))->owner_relid == owner_relid); } - - /* should have found at least one var */ - Assert(target_relid != 0); +#endif /* - * See if we can find the scan node for target_relid. It certainly is - * in the plan somewhere, but it may not be able to pushdown the filter - * to it (because of a join or so). + * Locate the scan node for the owner relation in the outer subtree. The + * path machinery guaranteed such a recipient exists (the filter could not + * have been realized otherwise), but stay defensive. */ - recipient = find_bloom_filter_recipient(outer_plan, target_relid); + recipient = find_bloom_filter_recipient(outer_plan, owner_relid); if (recipient == NULL) return; /* - * If we found a recipient, assign the filter an ID. We'll use it to - * register the filter in ExecRegisterBloomFilterProducer, and then - * for lookups in LookupBloomFilterProducer during execution. + * Assign the filter an ID. We'll use it to register the filter in + * ExecRegisterBloomFilterProducer, and then for lookups in + * LookupBloomFilterProducer during execution. * * XXX We can't use plan_node_id, as it's not assigned yet, that only - * happens in set_plan_refs. Also, if we ever allow multiple filters - * per hashtable (e.g. for different subsets of keys), it's not work. + * happens in set_plan_refs. */ hj->bloom_filter_id = ++root->glob->lastBloomFilterId; bf = makeNode(BloomFilter); - bf->filter_exprs = (List *) copyObject(hashkeys); - bf->hashops = list_copy(hashops); - bf->hashcollations = list_copy(hashcolls); + bf->filter_exprs = (List *) copyObject(hj->hashkeys); + bf->hashops = list_copy(hj->hashoperators); + bf->hashcollations = list_copy(hj->hashcollations); bf->producer_id = hj->bloom_filter_id; recipient->bloom_filters = lappend(recipient->bloom_filters, bf); - /* - * XXX We've manged to push the filter to the scan node, but maybe - * we should wait with updating bloom_consumer_count when it actually - * initializes the filters in ExecInit()? - */ hj->bloom_consumer_count++; } @@ -5152,11 +5084,14 @@ create_hashjoin_plan(PlannerInfo *root, copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path); /* - * Try to push the bloom filter for the hashtable down to nodes in the outer - * subtree. If a suitable scan node exists, add the filter to bloom_filters, - * and bump our bloom_consumer_count. + * Propagate the bloom filter pushdown decision made while creating paths: + * if this hash join was found to realize one or more bloom filters, push + * the corresponding filter down to the recipient scan in the outer + * subtree, and bump our bloom_consumer_count. No filter that was not + * selected as interesting during path creation is pushed here. */ - try_push_bloom_filter(root, join_plan, outer_plan); + try_push_bloom_filter(root, join_plan, outer_plan, + best_path->realized_filters); return join_plan; } diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 73518c8f870..9cd9188a1cf 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -286,6 +286,16 @@ set_cheapest(RelOptInfo *parent_rel) Path *path = (Path *) lfirst(p); int cmp; + /* + * Paths that expect a pushed-down Bloom filter are speculative: their + * rows/cost estimates assume a hash join above will build and push a + * filter to them. They must never be chosen as the cheapest startup, + * total, or parameterized path; they are only consumed explicitly by + * join path generation (see joinpath.c). Skip them here. + */ + if (path->expected_filters != NIL) + continue; + if (path->param_info) { /* Parameterized path, so add it to parameterized_paths */ @@ -383,6 +393,129 @@ set_cheapest(RelOptInfo *parent_rel) parent_rel->cheapest_parameterized_paths = parameterized_paths; } +/* + * expected_filters_equal + * Return true if the two lists of ExpectedFilter nodes denote the same + * set of expected Bloom filters (order-independent). + */ +bool +expected_filters_equal(List *a, List *b) +{ + ListCell *lc; + + if (a == NIL && b == NIL) + return true; + if (list_length(a) != list_length(b)) + return false; + + foreach(lc, a) + { + ExpectedFilter *fa = (ExpectedFilter *) lfirst(lc); + ListCell *lc2; + bool found = false; + + foreach(lc2, b) + { + ExpectedFilter *fb = (ExpectedFilter *) lfirst(lc2); + + if (fa->owner_relid == fb->owner_relid && + bms_equal(fa->build_relids, fb->build_relids) && + equal(fa->clauses, fb->clauses)) + { + found = true; + break; + } + } + if (!found) + return false; + } + return true; +} + +/* + * expected_filters_selectivity + * Combined surviving fraction of a set of expected filters, assuming + * independence. Returns a value in (0, 1]. + */ +double +expected_filters_selectivity(List *filters) +{ + double sel = 1.0; + ListCell *lc; + + foreach(lc, filters) + { + ExpectedFilter *f = (ExpectedFilter *) lfirst(lc); + + sel *= f->selectivity; + } + + /* clamp to a sane range */ + if (sel < 0.0) + sel = 0.0; + if (sel > 1.0) + sel = 1.0; + + return sel; +} + +/* + * create_filtered_scan_path + * Build a copy of a base-relation scan path that additionally expects the + * given set of pushed-down Bloom filters. + * + * The clone shares all substructure with the original path (parent, + * pathtarget, clauses, etc.); only the rows estimate is reduced to reflect + * the filters' combined selectivity, and expected_filters is set. This is + * safe because create_plan() treats the clone identically to the original + * (it ignores expected_filters), and add_path() may freely pfree the clone. + * + * Only the plain scan path node types that can receive a pushed-down filter + * are supported (matching find_bloom_filter_recipient in createplan.c). + * Returns NULL for unsupported path types. + * + * XXX This should probably adjust the CPU cost in some way. It assumes the + * filter checks are free, which does not seem right. + */ +Path * +create_filtered_scan_path(PlannerInfo *root, Path *subpath, List *filters) +{ + Path *newpath; + size_t sz; + + switch (nodeTag(subpath)) + { + case T_Path: + /* plain seqscan/samplescan etc. */ + sz = sizeof(Path); + break; + case T_IndexPath: + sz = sizeof(IndexPath); + break; + case T_BitmapHeapPath: + sz = sizeof(BitmapHeapPath); + break; + case T_TidPath: + sz = sizeof(TidPath); + break; + case T_TidRangePath: + sz = sizeof(TidRangePath); + break; + default: + /* unsupported scan path type */ + return NULL; + } + + newpath = (Path *) palloc(sz); + memcpy(newpath, subpath, sz); + + newpath->expected_filters = filters; + newpath->rows = clamp_row_est(subpath->rows * + expected_filters_selectivity(filters)); + + return newpath; +} + /* * add_path * Consider a potential implementation path for the specified parent rel, @@ -485,6 +618,17 @@ add_path(RelOptInfo *parent_rel, Path *new_path) PathKeysComparison keyscmp; BMS_Comparison outercmp; + /* + * Paths carrying different sets of expected Bloom filters serve + * different purposes (each may be consumed by a different parent join, + * or none at all), and their cost/row estimates aren't directly + * comparable. So if the two paths don't expect the same filters, keep + * both and don't let either dominate the other. + */ + if (!expected_filters_equal(new_path->expected_filters, + old_path->expected_filters)) + continue; + /* * Do a fuzzy cost comparison with standard fuzziness limit. */ @@ -702,6 +846,20 @@ add_path_precheck(RelOptInfo *parent_rel, int disabled_nodes, Path *old_path = (Path *) lfirst(p1); PathKeysComparison keyscmp; + /* + * Paths carrying expected Bloom filters serve a different purpose and + * are not directly cost-comparable with ordinary paths, exactly as in + * add_path (which keeps both when the expected filter sets differ). + * The candidates submitted to this precheck never carry expected + * filters of their own, so any filter-bearing old path is a + * non-comparable speculative path and must not be allowed to dominate + * (and thereby suppress) the new path. Skipping them here also + * guarantees that a join relation always retains at least one ordinary, + * filter-free path to serve as cheapest_total_path. + */ + if (old_path->expected_filters != NIL) + continue; + /* * Since the pathlist is sorted by disabled_nodes and then by * total_cost, we can stop looking once we reach a path with more diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index c9dcb294d4d..9ea40bcb798 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -380,6 +380,26 @@ max => 'BLCKSZ', }, +{ name => 'bloom_filter_pushdown_max', type => 'int', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER', + short_desc => 'Maximum number of pushed-down hash join bloom filters considered per scan.', + long_desc => 'Bounds how many interesting bloom filters the planner enumerates subsets of when building filter-aware scan paths.', + flags => 'GUC_EXPLAIN', + variable => 'bloom_filter_pushdown_max', + boot_val => '3', + min => '0', + max => '10', +}, + +{ name => 'bloom_filter_pushdown_threshold', type => 'real', context => 'PGC_USERSET', group => 'QUERY_TUNING_OTHER', + short_desc => 'Minimum fraction of tuples a pushed-down hash join bloom filter must be expected to eliminate.', + long_desc => 'A bloom filter is only considered during planning if it is expected to discard at least this fraction of the scanned tuples.', + flags => 'GUC_EXPLAIN', + variable => 'bloom_filter_pushdown_threshold', + boot_val => '0.3', + min => '0.0', + max => '1.0', +}, + { name => 'bonjour', type => 'bool', context => 'PGC_POSTMASTER', group => 'CONN_AUTH_SETTINGS', short_desc => 'Enables advertising the server via Bonjour.', variable => 'enable_bonjour', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 34f98b42ff6..faf01bf7e43 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -485,6 +485,8 @@ # - Other Planner Options - +#bloom_filter_pushdown_max = 3 # range 0-10 +#bloom_filter_pushdown_threshold = 0.3 # range 0.0-1.0 #default_statistics_target = 100 # range 1-10000 #constraint_exclusion = partition # on, off, or partition #cursor_tuple_fraction = 0.1 # range 0.0-1.0 diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 69f9ad2d5e3..f2f21f4ade1 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1836,6 +1836,54 @@ typedef struct GroupByOrdering List *clauses; } GroupByOrdering; +/* + * ExpectedFilter + * + * Represents the planner's assumption that a scan path will receive a Bloom + * filter pushed down at plan-creation time by some hash join above it (see + * the bloom filter pushdown logic in createplan.c and nodeHashjoin.c). When + * a scan participates in a hashjoinable equality join, the hash join may build + * a Bloom filter on the inner ("build") side keys and push it to the scan on + * the outer ("probe") side, eliminating outer tuples that cannot match. + * + * Because that pushdown happens after path selection, the row/cost estimates + * of the affected paths would normally ignore the filter's selectivity. To + * account for it during planning, we generate additional scan paths that carry + * one or more ExpectedFilter nodes and whose rows/cost reflect the expected + * elimination. These filters are propagated up the join tree much like + * pathkeys, until the hash join that is their "source" realizes them. + * + * 'owner_relid' is the base relation the filter would be pushed down to. + * 'build_relids' is the set of base relids on the other (build) side of the + * join clause(s); the filter is "sourced" by the join that first brings + * those relids together with the owner. + * 'clauses' is the list of hashjoinable equality RestrictInfos defining the + * filter keys (the owner side of each clause is a bare Var of owner_relid). + * 'selectivity' is the expected surviving fraction of owner rows (in (0,1]). + * + * XXX The "owner_relid" may be a bit misleading, particularly if we allow + * pushing a filter to multiple nodes (e.g. scans on a partition). In that case + * we'd have multiple "owners", but ownership suggests there's just one. And + * some places already use "consumer" when referencing to the scan nodes, so + * maybe we should just use that? + * + * XXX What if we allow pushdown to non-scan nodes, e.g. above a join when + * pushdown to a scan is not possible (e.g. because the join clause is complex + * and references multiple relations)? Or maybe we could push to ForeignScan, + * and I'm not sure if those have a valid relid in all cases. + */ +typedef struct ExpectedFilter +{ + pg_node_attr(no_read, no_query_jumble) + + NodeTag type; + + Index owner_relid; + Relids build_relids; + List *clauses pg_node_attr(copy_as_scalar, equal_as_scalar); + Selectivity selectivity; +} ExpectedFilter; + /* * VolatileFunctionStatus -- allows nodes to cache their * contain_volatile_functions properties. VOLATILITY_UNKNOWN means not yet @@ -2012,6 +2060,14 @@ typedef struct Path /* sort ordering of path's output; a List of PathKey nodes; see above */ List *pathkeys; + + /* + * Bloom filters this path expects to receive from some hash join above + * it (a List of ExpectedFilter nodes; see below). An empty list means + * the path makes no such assumption. The path's rows/cost estimates + * already reflect the expected selectivity of these filters. + */ + List *expected_filters; } Path; /* Macro for extracting a path's parameterization relids; beware double eval */ @@ -2493,6 +2549,16 @@ typedef struct HashPath List *path_hashclauses; /* join clauses used for hashing */ int num_batches; /* number of batches expected */ Cardinality inner_rows_total; /* total inner rows expected */ + + /* + * Bloom filters this hash join "realizes": expected filters (see + * ExpectedFilter) carried by an input path for which this hash join is the + * source, and which it can push down to the scan of the owner relation. + * The pushdown decision is made here, while building paths; create_plan() + * merely propagates it to the plan (see create_hashjoin_plan). NIL means + * this hash join pushes no filter. + */ + List *realized_filters; } HashPath; /* diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 7339979c008..35ef6bddab2 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -71,6 +71,8 @@ extern PGDLLIMPORT bool enable_parallel_hash; extern PGDLLIMPORT bool enable_partition_pruning; extern PGDLLIMPORT bool enable_presorted_aggregate; extern PGDLLIMPORT bool enable_async_append; +extern PGDLLIMPORT double bloom_filter_pushdown_threshold; +extern PGDLLIMPORT int bloom_filter_pushdown_max; extern PGDLLIMPORT int constraint_exclusion; extern double index_pages_fetched(double tuples_fetched, BlockNumber pages, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index e8db321f92b..0dcae2ae3b3 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -64,6 +64,11 @@ extern bool add_partial_path_precheck(RelOptInfo *parent_rel, int disabled_nodes, Cost startup_cost, Cost total_cost, List *pathkeys); +extern bool expected_filters_equal(List *a, List *b); +extern double expected_filters_selectivity(List *filters); +extern Path *create_filtered_scan_path(PlannerInfo *root, Path *subpath, + List *filters); + extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer, int parallel_workers); extern Path *create_samplescan_path(PlannerInfo *root, RelOptInfo *rel, diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 17f2099ec3b..636761f2e94 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -104,6 +104,7 @@ extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, SpecialJoinInfo *sjinfo, List *restrictlist); +extern bool bloom_join_side_preserved(JoinType jointype, bool to_outer); /* * joinrels.c @@ -198,6 +199,11 @@ extern List *generate_implied_equalities_for_column(PlannerInfo *root, ec_matches_callback_type callback, void *callback_arg, Relids prohibited_rels); +extern List *generate_implied_equalities_for_all_columns(PlannerInfo *root, + RelOptInfo *rel, + ec_matches_callback_type callback, + void *callback_arg, + Relids prohibited_rels); extern bool have_relevant_eclass_joinclause(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2); extern bool has_relevant_eclass_joinclause(PlannerInfo *root, diff --git a/src/test/regress/expected/eager_aggregate.out b/src/test/regress/expected/eager_aggregate.out index bbdb9526471..90578629061 100644 --- a/src/test/regress/expected/eager_aggregate.out +++ b/src/test/regress/expected/eager_aggregate.out @@ -147,15 +147,15 @@ GROUP BY t1.a ORDER BY t1.a; Group Key: t2.b -> Hash Join Output: t2.c, t2.b, t3.c - Hash Cond: (t3.a = t2.a) - -> Seq Scan on public.eager_agg_t3 t3 - Output: t3.a, t3.b, t3.c - Bloom Filter 1: keys=(t3.a) + Hash Cond: (t2.a = t3.a) + -> Seq Scan on public.eager_agg_t2 t2 + Output: t2.a, t2.b, t2.c + Bloom Filter 1: keys=(t2.a) -> Hash - Output: t2.c, t2.b, t2.a + Output: t3.c, t3.a Bloom Filter 1 - -> Seq Scan on public.eager_agg_t2 t2 - Output: t2.c, t2.b, t2.a + -> Seq Scan on public.eager_agg_t3 t3 + Output: t3.c, t3.a (29 rows) SELECT t1.a, avg(t2.c + t3.c) @@ -209,15 +209,15 @@ GROUP BY t1.a ORDER BY t1.a; Sort Key: t2.b -> Hash Join Output: t2.c, t2.b, t3.c - Hash Cond: (t3.a = t2.a) - -> Seq Scan on public.eager_agg_t3 t3 - Output: t3.a, t3.b, t3.c - Bloom Filter 1: keys=(t3.a) + Hash Cond: (t2.a = t3.a) + -> Seq Scan on public.eager_agg_t2 t2 + Output: t2.a, t2.b, t2.c + Bloom Filter 1: keys=(t2.a) -> Hash - Output: t2.c, t2.b, t2.a + Output: t3.c, t3.a Bloom Filter 1 - -> Seq Scan on public.eager_agg_t2 t2 - Output: t2.c, t2.b, t2.a + -> Seq Scan on public.eager_agg_t3 t3 + Output: t3.c, t3.a (32 rows) SELECT t1.a, avg(t2.c + t3.c) @@ -261,16 +261,14 @@ GROUP BY t1.a ORDER BY t1.a; Hash Cond: (t1.b = t2.b) -> Seq Scan on public.eager_agg_t1 t1 Output: t1.a, t1.b, t1.c - Bloom Filter 1: keys=(t1.b) -> Hash Output: t2.b, (PARTIAL avg(t2.c)) - Bloom Filter 1 -> Partial HashAggregate Output: t2.b, PARTIAL avg(t2.c) Group Key: t2.b -> Seq Scan on public.eager_agg_t2 t2 Output: t2.a, t2.b, t2.c -(20 rows) +(18 rows) SELECT t1.a, avg(t2.c) FROM eager_agg_t1 t1 @@ -309,13 +307,11 @@ GROUP BY t2.b ORDER BY t2.b; Hash Cond: (t2.b = t1.b) -> Seq Scan on public.eager_agg_t2 t2 Output: t2.a, t2.b, t2.c - Bloom Filter 1: keys=(t2.b) -> Hash Output: t1.b - Bloom Filter 1 -> Seq Scan on public.eager_agg_t1 t1 Output: t1.b -(17 rows) +(15 rows) SELECT t2.b, avg(t2.c) FROM eager_agg_t1 t1 @@ -460,12 +456,12 @@ GROUP BY t1.a ORDER BY t1.a; -> Sort Sort Key: t1.a -> Hash Join - Hash Cond: (t2.b = t1.b) - -> Seq Scan on eager_agg_t2 t2 + Hash Cond: (t1.b = t2.b) + -> Seq Scan on eager_agg_t1 t1 Bloom Filter 1: keys=(b) -> Hash Bloom Filter 1 - -> Seq Scan on eager_agg_t1 t1 + -> Seq Scan on eager_agg_t2 t2 (11 rows) EXPLAIN (COSTS OFF) @@ -480,12 +476,12 @@ GROUP BY t1.a ORDER BY t1.a; -> Sort Sort Key: t1.a -> Hash Join - Hash Cond: (t2.b = t1.b) - -> Seq Scan on eager_agg_t2 t2 + Hash Cond: (t1.b = t2.b) + -> Seq Scan on eager_agg_t1 t1 Bloom Filter 1: keys=(b) -> Hash Bloom Filter 1 - -> Seq Scan on eager_agg_t1 t1 + -> Seq Scan on eager_agg_t2 t2 (11 rows) -- Eager aggregation must not push a partial aggregate onto the inner side of a @@ -552,14 +548,16 @@ GROUP BY t2.b ORDER BY t2.b; Hash Cond: (t1.b = t2.b) -> Seq Scan on public.eager_agg_t1 t1 Output: t1.a, t1.b, t1.c + Bloom Filter 1: keys=(t1.b) -> Hash Output: t2.b, (PARTIAL count(*)) + Bloom Filter 1 -> Partial HashAggregate Output: t2.b, PARTIAL count(*) Group Key: t2.b -> Seq Scan on public.eager_agg_t2 t2 Output: t2.a, t2.b, t2.c -(18 rows) +(20 rows) SELECT t2.b, count(*) FROM eager_agg_t2 t2 @@ -621,10 +619,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2.y = t1.x) -> Seq Scan on public.eager_agg_tab2_p1 t2 Output: t2.y - Bloom Filter 1: keys=(t2.y) -> Hash Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*)) - Bloom Filter 1 -> Partial HashAggregate Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*) Group Key: t1.x @@ -638,10 +634,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_1.y = t1_1.x) -> Seq Scan on public.eager_agg_tab2_p2 t2_1 Output: t2_1.y - Bloom Filter 2: keys=(t2_1.y) -> Hash Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*)) - Bloom Filter 2 -> Partial HashAggregate Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*) Group Key: t1_1.x @@ -655,16 +649,14 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_2.y = t1_2.x) -> Seq Scan on public.eager_agg_tab2_p3 t2_2 Output: t2_2.y - Bloom Filter 3: keys=(t2_2.y) -> Hash Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*)) - Bloom Filter 3 -> Partial HashAggregate Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*) Group Key: t1_2.x -> Seq Scan on public.eager_agg_tab1_p3 t1_2 Output: t1_2.x, t1_2.y -(55 rows) +(49 rows) SELECT t1.x, sum(t1.y), count(*) FROM eager_agg_tab1 t1 @@ -709,10 +701,8 @@ GROUP BY t2.y ORDER BY t2.y; Hash Cond: (t2.y = t1.x) -> Seq Scan on public.eager_agg_tab2_p1 t2 Output: t2.y - Bloom Filter 1: keys=(t2.y) -> Hash Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*)) - Bloom Filter 1 -> Partial HashAggregate Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*) Group Key: t1.x @@ -726,10 +716,8 @@ GROUP BY t2.y ORDER BY t2.y; Hash Cond: (t2_1.y = t1_1.x) -> Seq Scan on public.eager_agg_tab2_p2 t2_1 Output: t2_1.y - Bloom Filter 2: keys=(t2_1.y) -> Hash Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*)) - Bloom Filter 2 -> Partial HashAggregate Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*) Group Key: t1_1.x @@ -743,16 +731,14 @@ GROUP BY t2.y ORDER BY t2.y; Hash Cond: (t2_2.y = t1_2.x) -> Seq Scan on public.eager_agg_tab2_p3 t2_2 Output: t2_2.y - Bloom Filter 3: keys=(t2_2.y) -> Hash Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*)) - Bloom Filter 3 -> Partial HashAggregate Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*) Group Key: t1_2.x -> Seq Scan on public.eager_agg_tab1_p3 t1_2 Output: t1_2.y, t1_2.x -(55 rows) +(49 rows) SELECT t2.y, sum(t1.y), count(*) FROM eager_agg_tab1 t1 @@ -799,10 +785,8 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x; Hash Cond: (t2.y = t1.x) -> Seq Scan on public.eager_agg_tab2_p1 t2 Output: t2.x, t2.y - Bloom Filter 1: keys=(t2.y) -> Hash Output: t1.x, (PARTIAL sum(t1.x)), (PARTIAL count(*)), (PARTIAL avg(t1.x)) - Bloom Filter 1 -> Partial HashAggregate Output: t1.x, PARTIAL sum(t1.x), PARTIAL count(*), PARTIAL avg(t1.x) Group Key: t1.x @@ -813,10 +797,8 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x; Hash Cond: (t2_1.y = t1_1.x) -> Seq Scan on public.eager_agg_tab2_p2 t2_1 Output: t2_1.x, t2_1.y - Bloom Filter 2: keys=(t2_1.y) -> Hash Output: t1_1.x, (PARTIAL sum(t1_1.x)), (PARTIAL count(*)), (PARTIAL avg(t1_1.x)) - Bloom Filter 2 -> Partial HashAggregate Output: t1_1.x, PARTIAL sum(t1_1.x), PARTIAL count(*), PARTIAL avg(t1_1.x) Group Key: t1_1.x @@ -827,16 +809,14 @@ GROUP BY t2.x HAVING avg(t1.x) > 5 ORDER BY t2.x; Hash Cond: (t2_2.y = t1_2.x) -> Seq Scan on public.eager_agg_tab2_p3 t2_2 Output: t2_2.x, t2_2.y - Bloom Filter 3: keys=(t2_2.y) -> Hash Output: t1_2.x, (PARTIAL sum(t1_2.x)), (PARTIAL count(*)), (PARTIAL avg(t1_2.x)) - Bloom Filter 3 -> Partial HashAggregate Output: t1_2.x, PARTIAL sum(t1_2.x), PARTIAL count(*), PARTIAL avg(t1_2.x) Group Key: t1_2.x -> Seq Scan on public.eager_agg_tab1_p3 t1_2 Output: t1_2.x -(50 rows) +(44 rows) SELECT t2.x, sum(t1.x), count(*) FROM eager_agg_tab1 t1 @@ -878,10 +858,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1.x = t2.x) -> Seq Scan on public.eager_agg_tab1_p1 t1 Output: t1.x - Bloom Filter 2: keys=(t1.x) -> Hash Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y))) - Bloom Filter 2 -> Partial HashAggregate Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y)) Group Key: t2.x @@ -890,10 +868,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2.x = t3.x) -> Seq Scan on public.eager_agg_tab1_p1 t2 Output: t2.y, t2.x - Bloom Filter 1: keys=(t2.x) -> Hash Output: t3.y, t3.x - Bloom Filter 1 -> Seq Scan on public.eager_agg_tab1_p1 t3 Output: t3.y, t3.x -> Finalize HashAggregate @@ -904,10 +880,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_1.x = t2_1.x) -> Seq Scan on public.eager_agg_tab1_p2 t1_1 Output: t1_1.x - Bloom Filter 4: keys=(t1_1.x) -> Hash Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))) - Bloom Filter 4 -> Partial HashAggregate Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)) Group Key: t2_1.x @@ -916,10 +890,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_1.x = t3_1.x) -> Seq Scan on public.eager_agg_tab1_p2 t2_1 Output: t2_1.y, t2_1.x - Bloom Filter 3: keys=(t2_1.x) -> Hash Output: t3_1.y, t3_1.x - Bloom Filter 3 -> Seq Scan on public.eager_agg_tab1_p2 t3_1 Output: t3_1.y, t3_1.x -> Finalize HashAggregate @@ -930,10 +902,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_2.x = t2_2.x) -> Seq Scan on public.eager_agg_tab1_p3 t1_2 Output: t1_2.x - Bloom Filter 6: keys=(t1_2.x) -> Hash Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))) - Bloom Filter 6 -> Partial HashAggregate Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)) Group Key: t2_2.x @@ -942,13 +912,11 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_2.x = t3_2.x) -> Seq Scan on public.eager_agg_tab1_p3 t2_2 Output: t2_2.y, t2_2.x - Bloom Filter 5: keys=(t2_2.x) -> Hash Output: t3_2.y, t3_2.x - Bloom Filter 5 -> Seq Scan on public.eager_agg_tab1_p3 t3_2 Output: t3_2.y, t3_2.x -(82 rows) +(70 rows) SELECT t1.x, sum(t2.y + t3.y) FROM eager_agg_tab1 t1 @@ -1118,10 +1086,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2.y = t1.x) -> Seq Scan on public.eager_agg_tab2_p1 t2 Output: t2.y - Bloom Filter 1: keys=(t2.y) -> Hash Output: t1.x, (PARTIAL sum(t1.y)), (PARTIAL count(*)) - Bloom Filter 1 -> Partial HashAggregate Output: t1.x, PARTIAL sum(t1.y), PARTIAL count(*) Group Key: t1.x @@ -1135,10 +1101,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_1.y = t1_1.x) -> Seq Scan on public.eager_agg_tab2_p2 t2_1 Output: t2_1.y - Bloom Filter 2: keys=(t2_1.y) -> Hash Output: t1_1.x, (PARTIAL sum(t1_1.y)), (PARTIAL count(*)) - Bloom Filter 2 -> Partial HashAggregate Output: t1_1.x, PARTIAL sum(t1_1.y), PARTIAL count(*) Group Key: t1_1.x @@ -1152,16 +1116,14 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_2.y = t1_2.x) -> Seq Scan on public.eager_agg_tab2_p3 t2_2 Output: t2_2.y - Bloom Filter 3: keys=(t2_2.y) -> Hash Output: t1_2.x, (PARTIAL sum(t1_2.y)), (PARTIAL count(*)) - Bloom Filter 3 -> Partial HashAggregate Output: t1_2.x, PARTIAL sum(t1_2.y), PARTIAL count(*) Group Key: t1_2.x -> Seq Scan on public.eager_agg_tab1_p3 t1_2 Output: t1_2.x, t1_2.y -(55 rows) +(49 rows) SELECT t1.x, sum(t1.y), count(*) FROM eager_agg_tab1 t1 @@ -1224,10 +1186,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1.x = t2.x) -> Seq Scan on public.eager_agg_tab_ml_p1 t1 Output: t1.x - Bloom Filter 1: keys=(t1.x) -> Hash Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*)) - Bloom Filter 1 -> Partial HashAggregate Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*) Group Key: t2.x @@ -1241,10 +1201,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_1.x = t2_1.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1 Output: t1_1.x - Bloom Filter 2: keys=(t1_1.x) -> Hash Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*)) - Bloom Filter 2 -> Partial HashAggregate Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*) Group Key: t2_1.x @@ -1258,10 +1216,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_2.x = t2_2.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2 Output: t1_2.x - Bloom Filter 3: keys=(t1_2.x) -> Hash Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*)) - Bloom Filter 3 -> Partial HashAggregate Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*) Group Key: t2_2.x @@ -1275,10 +1231,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_3.x = t2_3.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3 Output: t1_3.x - Bloom Filter 4: keys=(t1_3.x) -> Hash Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*)) - Bloom Filter 4 -> Partial HashAggregate Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*) Group Key: t2_3.x @@ -1292,16 +1246,14 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_4.x = t2_4.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4 Output: t1_4.x - Bloom Filter 5: keys=(t1_4.x) -> Hash Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*)) - Bloom Filter 5 -> Partial HashAggregate Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*) Group Key: t2_4.x -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4 Output: t2_4.y, t2_4.x -(89 rows) +(79 rows) SELECT t1.x, sum(t2.y), count(*) FROM eager_agg_tab_ml t1 @@ -1362,10 +1314,8 @@ GROUP BY t1.y ORDER BY t1.y; Hash Cond: (t1.x = t2.x) -> Seq Scan on public.eager_agg_tab_ml_p1 t1 Output: t1.y, t1.x - Bloom Filter 1: keys=(t1.x) -> Hash Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*)) - Bloom Filter 1 -> Partial HashAggregate Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*) Group Key: t2.x @@ -1376,10 +1326,8 @@ GROUP BY t1.y ORDER BY t1.y; Hash Cond: (t1_1.x = t2_1.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1 Output: t1_1.y, t1_1.x - Bloom Filter 2: keys=(t1_1.x) -> Hash Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*)) - Bloom Filter 2 -> Partial HashAggregate Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*) Group Key: t2_1.x @@ -1390,10 +1338,8 @@ GROUP BY t1.y ORDER BY t1.y; Hash Cond: (t1_2.x = t2_2.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2 Output: t1_2.y, t1_2.x - Bloom Filter 3: keys=(t1_2.x) -> Hash Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*)) - Bloom Filter 3 -> Partial HashAggregate Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*) Group Key: t2_2.x @@ -1404,10 +1350,8 @@ GROUP BY t1.y ORDER BY t1.y; Hash Cond: (t1_3.x = t2_3.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3 Output: t1_3.y, t1_3.x - Bloom Filter 4: keys=(t1_3.x) -> Hash Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*)) - Bloom Filter 4 -> Partial HashAggregate Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*) Group Key: t2_3.x @@ -1418,16 +1362,14 @@ GROUP BY t1.y ORDER BY t1.y; Hash Cond: (t1_4.x = t2_4.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4 Output: t1_4.y, t1_4.x - Bloom Filter 5: keys=(t1_4.x) -> Hash Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*)) - Bloom Filter 5 -> Partial HashAggregate Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*) Group Key: t2_4.x -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4 Output: t2_4.y, t2_4.x -(77 rows) +(67 rows) SELECT t1.y, sum(t2.y), count(*) FROM eager_agg_tab_ml t1 @@ -1489,10 +1431,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1.x = t2.x) -> Seq Scan on public.eager_agg_tab_ml_p1 t1 Output: t1.x - Bloom Filter 2: keys=(t1.x) -> Hash Output: t2.x, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*)) - Bloom Filter 2 -> Partial HashAggregate Output: t2.x, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*) Group Key: t2.x @@ -1501,10 +1441,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2.x = t3.x) -> Seq Scan on public.eager_agg_tab_ml_p1 t2 Output: t2.y, t2.x - Bloom Filter 1: keys=(t2.x) -> Hash Output: t3.y, t3.x - Bloom Filter 1 -> Seq Scan on public.eager_agg_tab_ml_p1 t3 Output: t3.y, t3.x -> Finalize HashAggregate @@ -1515,10 +1453,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_1.x = t2_1.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1 Output: t1_1.x - Bloom Filter 4: keys=(t1_1.x) -> Hash Output: t2_1.x, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*)) - Bloom Filter 4 -> Partial HashAggregate Output: t2_1.x, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*) Group Key: t2_1.x @@ -1527,10 +1463,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_1.x = t3_1.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1 Output: t2_1.y, t2_1.x - Bloom Filter 3: keys=(t2_1.x) -> Hash Output: t3_1.y, t3_1.x - Bloom Filter 3 -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1 Output: t3_1.y, t3_1.x -> Finalize HashAggregate @@ -1541,10 +1475,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_2.x = t2_2.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2 Output: t1_2.x - Bloom Filter 6: keys=(t1_2.x) -> Hash Output: t2_2.x, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*)) - Bloom Filter 6 -> Partial HashAggregate Output: t2_2.x, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*) Group Key: t2_2.x @@ -1553,10 +1485,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_2.x = t3_2.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2 Output: t2_2.y, t2_2.x - Bloom Filter 5: keys=(t2_2.x) -> Hash Output: t3_2.y, t3_2.x - Bloom Filter 5 -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2 Output: t3_2.y, t3_2.x -> Finalize HashAggregate @@ -1567,10 +1497,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_3.x = t2_3.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3 Output: t1_3.x - Bloom Filter 8: keys=(t1_3.x) -> Hash Output: t2_3.x, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*)) - Bloom Filter 8 -> Partial HashAggregate Output: t2_3.x, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*) Group Key: t2_3.x @@ -1579,10 +1507,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_3.x = t3_3.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3 Output: t2_3.y, t2_3.x - Bloom Filter 7: keys=(t2_3.x) -> Hash Output: t3_3.y, t3_3.x - Bloom Filter 7 -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3 Output: t3_3.y, t3_3.x -> Finalize HashAggregate @@ -1593,10 +1519,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_4.x = t2_4.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4 Output: t1_4.x - Bloom Filter 10: keys=(t1_4.x) -> Hash Output: t2_4.x, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*)) - Bloom Filter 10 -> Partial HashAggregate Output: t2_4.x, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*) Group Key: t2_4.x @@ -1605,13 +1529,11 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t2_4.x = t3_4.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4 Output: t2_4.y, t2_4.x - Bloom Filter 9: keys=(t2_4.x) -> Hash Output: t3_4.y, t3_4.x - Bloom Filter 9 -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4 Output: t3_4.y, t3_4.x -(134 rows) +(114 rows) SELECT t1.x, sum(t2.y + t3.y), count(*) FROM eager_agg_tab_ml t1 @@ -1673,10 +1595,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t1.x = t2.x) -> Seq Scan on public.eager_agg_tab_ml_p1 t1 Output: t1.x - Bloom Filter 2: keys=(t1.x) -> Hash Output: t2.x, t3.y, t3.x, (PARTIAL sum((t2.y + t3.y))), (PARTIAL count(*)) - Bloom Filter 2 -> Partial HashAggregate Output: t2.x, t3.y, t3.x, PARTIAL sum((t2.y + t3.y)), PARTIAL count(*) Group Key: t2.x, t3.y, t3.x @@ -1685,10 +1605,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t2.x = t3.x) -> Seq Scan on public.eager_agg_tab_ml_p1 t2 Output: t2.y, t2.x - Bloom Filter 1: keys=(t2.x) -> Hash Output: t3.y, t3.x - Bloom Filter 1 -> Seq Scan on public.eager_agg_tab_ml_p1 t3 Output: t3.y, t3.x -> Hash Join @@ -1696,10 +1614,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t1_1.x = t2_1.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1 Output: t1_1.x - Bloom Filter 4: keys=(t1_1.x) -> Hash Output: t2_1.x, t3_1.y, t3_1.x, (PARTIAL sum((t2_1.y + t3_1.y))), (PARTIAL count(*)) - Bloom Filter 4 -> Partial HashAggregate Output: t2_1.x, t3_1.y, t3_1.x, PARTIAL sum((t2_1.y + t3_1.y)), PARTIAL count(*) Group Key: t2_1.x, t3_1.y, t3_1.x @@ -1708,10 +1624,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t2_1.x = t3_1.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t2_1 Output: t2_1.y, t2_1.x - Bloom Filter 3: keys=(t2_1.x) -> Hash Output: t3_1.y, t3_1.x - Bloom Filter 3 -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t3_1 Output: t3_1.y, t3_1.x -> Hash Join @@ -1719,10 +1633,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t1_2.x = t2_2.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2 Output: t1_2.x - Bloom Filter 6: keys=(t1_2.x) -> Hash Output: t2_2.x, t3_2.y, t3_2.x, (PARTIAL sum((t2_2.y + t3_2.y))), (PARTIAL count(*)) - Bloom Filter 6 -> Partial HashAggregate Output: t2_2.x, t3_2.y, t3_2.x, PARTIAL sum((t2_2.y + t3_2.y)), PARTIAL count(*) Group Key: t2_2.x, t3_2.y, t3_2.x @@ -1731,10 +1643,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t2_2.x = t3_2.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t2_2 Output: t2_2.y, t2_2.x - Bloom Filter 5: keys=(t2_2.x) -> Hash Output: t3_2.y, t3_2.x - Bloom Filter 5 -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t3_2 Output: t3_2.y, t3_2.x -> Hash Join @@ -1742,10 +1652,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t1_3.x = t2_3.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3 Output: t1_3.x - Bloom Filter 8: keys=(t1_3.x) -> Hash Output: t2_3.x, t3_3.y, t3_3.x, (PARTIAL sum((t2_3.y + t3_3.y))), (PARTIAL count(*)) - Bloom Filter 8 -> Partial HashAggregate Output: t2_3.x, t3_3.y, t3_3.x, PARTIAL sum((t2_3.y + t3_3.y)), PARTIAL count(*) Group Key: t2_3.x, t3_3.y, t3_3.x @@ -1754,10 +1662,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t2_3.x = t3_3.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t2_3 Output: t2_3.y, t2_3.x - Bloom Filter 7: keys=(t2_3.x) -> Hash Output: t3_3.y, t3_3.x - Bloom Filter 7 -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t3_3 Output: t3_3.y, t3_3.x -> Hash Join @@ -1765,10 +1671,8 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t1_4.x = t2_4.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4 Output: t1_4.x - Bloom Filter 10: keys=(t1_4.x) -> Hash Output: t2_4.x, t3_4.y, t3_4.x, (PARTIAL sum((t2_4.y + t3_4.y))), (PARTIAL count(*)) - Bloom Filter 10 -> Partial HashAggregate Output: t2_4.x, t3_4.y, t3_4.x, PARTIAL sum((t2_4.y + t3_4.y)), PARTIAL count(*) Group Key: t2_4.x, t3_4.y, t3_4.x @@ -1777,13 +1681,11 @@ GROUP BY t3.y ORDER BY t3.y; Hash Cond: (t2_4.x = t3_4.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4 Output: t2_4.y, t2_4.x - Bloom Filter 9: keys=(t2_4.x) -> Hash Output: t3_4.y, t3_4.x - Bloom Filter 9 -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t3_4 Output: t3_4.y, t3_4.x -(122 rows) +(102 rows) SELECT t3.y, sum(t2.y + t3.y), count(*) FROM eager_agg_tab_ml t1 @@ -1846,10 +1748,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1.x = t2.x) -> Seq Scan on public.eager_agg_tab_ml_p1 t1 Output: t1.x - Bloom Filter 1: keys=(t1.x) -> Hash Output: t2.x, (PARTIAL sum(t2.y)), (PARTIAL count(*)) - Bloom Filter 1 -> Partial HashAggregate Output: t2.x, PARTIAL sum(t2.y), PARTIAL count(*) Group Key: t2.x @@ -1863,10 +1763,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_1.x = t2_1.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s1 t1_1 Output: t1_1.x - Bloom Filter 2: keys=(t1_1.x) -> Hash Output: t2_1.x, (PARTIAL sum(t2_1.y)), (PARTIAL count(*)) - Bloom Filter 2 -> Partial HashAggregate Output: t2_1.x, PARTIAL sum(t2_1.y), PARTIAL count(*) Group Key: t2_1.x @@ -1880,10 +1778,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_2.x = t2_2.x) -> Seq Scan on public.eager_agg_tab_ml_p2_s2 t1_2 Output: t1_2.x - Bloom Filter 3: keys=(t1_2.x) -> Hash Output: t2_2.x, (PARTIAL sum(t2_2.y)), (PARTIAL count(*)) - Bloom Filter 3 -> Partial HashAggregate Output: t2_2.x, PARTIAL sum(t2_2.y), PARTIAL count(*) Group Key: t2_2.x @@ -1897,10 +1793,8 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_3.x = t2_3.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s1 t1_3 Output: t1_3.x - Bloom Filter 4: keys=(t1_3.x) -> Hash Output: t2_3.x, (PARTIAL sum(t2_3.y)), (PARTIAL count(*)) - Bloom Filter 4 -> Partial HashAggregate Output: t2_3.x, PARTIAL sum(t2_3.y), PARTIAL count(*) Group Key: t2_3.x @@ -1914,16 +1808,14 @@ GROUP BY t1.x ORDER BY t1.x; Hash Cond: (t1_4.x = t2_4.x) -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t1_4 Output: t1_4.x - Bloom Filter 5: keys=(t1_4.x) -> Hash Output: t2_4.x, (PARTIAL sum(t2_4.y)), (PARTIAL count(*)) - Bloom Filter 5 -> Partial HashAggregate Output: t2_4.x, PARTIAL sum(t2_4.y), PARTIAL count(*) Group Key: t2_4.x -> Seq Scan on public.eager_agg_tab_ml_p3_s2 t2_4 Output: t2_4.y, t2_4.x -(89 rows) +(79 rows) SELECT t1.x, sum(t2.y), count(*) FROM eager_agg_tab_ml t1 diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out index 70d986e8ab0..14fbdcc645a 100644 --- a/src/test/regress/expected/graph_table.out +++ b/src/test/regress/expected/graph_table.out @@ -250,8 +250,8 @@ SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'U SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g; a | customer_name | cid | order_id ---+---------------+-----+---------- - 1 | customer1 | 1 | 1 2 | customer1 | 1 | 1 + 1 | customer1 | 1 | 1 (2 rows) -- lateral reference with multi-label pattern, which is rewritten as UNION of @@ -407,8 +407,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname, a.vprop1) SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, b.ename AS conn, c.vname AS dest, c.lprop1, c.vprop2, c.vprop1)); src | conn | dest | lprop1 | vprop2 | vprop1 -----+------+------+----------+--------+-------- - v12 | e122 | v21 | vl2_prop | 1100 | 1010 v11 | e121 | v22 | vl2_prop | 1200 | 1020 + v12 | e122 | v21 | vl2_prop | 1100 | 1010 v11 | e131 | v33 | vl3_prop | | 2030 v11 | e132 | v31 | vl3_prop | | 2010 (4 rows) @@ -417,16 +417,16 @@ SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-[conn]-(v2) COLUMNS (v1.vname AS v1name, conn.ename AS cname, v2.vname AS v2name)); v1name | cname | v2name --------+-------+-------- - v21 | e122 | v12 v22 | e121 | v11 + v21 | e122 | v12 v22 | e231 | v32 (3 rows) SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-(v2) COLUMNS (v1.vname AS v1name, v2.vname AS v2name)); v1name | v2name --------+-------- - v21 | v12 v22 | v11 + v21 | v12 v22 | v32 (3 rows) @@ -487,8 +487,8 @@ LINE 1: SELECT * FROM GRAPH_TABLE (g1 MATCH (WHERE b.eprop1 = 10001)... SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname, src.vprop1 AS svp1, src.vprop2 AS svp2, src.lprop1 AS slp1, dest.vprop1 AS dvp1, dest.vprop2 AS dvp2, dest.lprop1 AS dlp1, conn.eprop1 AS cep1, conn.lprop2 AS clp2)); svname | cename | dvname | svp1 | svp2 | slp1 | dvp1 | dvp2 | dlp1 | cep1 | clp2 --------+--------+--------+------+------+----------+------+------+----------+-------+-------- - v12 | e122 | v21 | 20 | | | 1010 | 1100 | vl2_prop | 10002 | v11 | e121 | v22 | 10 | | | 1020 | 1200 | vl2_prop | 10001 | + v12 | e122 | v21 | 20 | | | 1010 | 1100 | vl2_prop | 10002 | v11 | e131 | v33 | 10 | | | 2030 | | vl3_prop | 10003 | v11 | e132 | v31 | 10 | | | 2010 | | vl3_prop | 10004 | v22 | e231 | v32 | 1020 | 1200 | vl2_prop | 2020 | | vl3_prop | | 100050 @@ -498,8 +498,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS s SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 | vl3)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname)); svname | cename | dvname --------+--------+-------- - v12 | e122 | v21 v11 | e121 | v22 + v12 | e122 | v21 v11 | e131 | v33 v11 | e132 | v31 v22 | e231 | v32 @@ -519,8 +519,8 @@ SELECT vn FROM all_vertices EXCEPT (SELECT svn FROM all_connected_vertices UNION SELECT sn, cn, dn FROM GRAPH_TABLE (g1 MATCH (src IS l1)-[conn IS l1]->(dest IS l1) COLUMNS (src.elname AS sn, conn.elname AS cn, dest.elname AS dn)); sn | cn | dn -----+------+----- - v12 | e122 | v21 v11 | e121 | v22 + v12 | e122 | v21 v11 | e131 | v33 v11 | e132 | v31 v22 | e231 | v32 diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 4fccc7c4057..a04e99f1ed4 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -218,13 +218,13 @@ SELECT t1.a, t2.e WHERE t1.a = t2.d; a | e ---+---- - 0 | 1 | -1 2 | 2 - 2 | 4 3 | -3 + 2 | 4 5 | -5 5 | -5 + 0 | (7 rows) -- @@ -1573,13 +1573,13 @@ SELECT * FROM J1_TBL INNER JOIN J2_TBL USING (i); i | j | t | k ---+---+-------+---- - 0 | | zero | 1 | 4 | one | -1 2 | 3 | two | 2 - 2 | 3 | two | 4 3 | 2 | three | -3 + 2 | 3 | two | 4 5 | 0 | five | -5 5 | 0 | five | -5 + 0 | | zero | (7 rows) -- Same as above, slightly different syntax @@ -1587,13 +1587,13 @@ SELECT * FROM J1_TBL JOIN J2_TBL USING (i); i | j | t | k ---+---+-------+---- - 0 | | zero | 1 | 4 | one | -1 2 | 3 | two | 2 - 2 | 3 | two | 4 3 | 2 | three | -3 + 2 | 3 | two | 4 5 | 0 | five | -5 5 | 0 | five | -5 + 0 | | zero | (7 rows) SELECT * @@ -1681,35 +1681,35 @@ SELECT * FROM J1_TBL NATURAL JOIN J2_TBL; i | j | t | k ---+---+-------+---- - 0 | | zero | 1 | 4 | one | -1 2 | 3 | two | 2 - 2 | 3 | two | 4 3 | 2 | three | -3 + 2 | 3 | two | 4 5 | 0 | five | -5 5 | 0 | five | -5 + 0 | | zero | (7 rows) SELECT * FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d); a | b | c | d ---+---+-------+---- - 0 | | zero | 1 | 4 | one | -1 2 | 3 | two | 2 - 2 | 3 | two | 4 3 | 2 | three | -3 + 2 | 3 | two | 4 5 | 0 | five | -5 5 | 0 | five | -5 + 0 | | zero | (7 rows) SELECT * FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a); a | b | c | d ---+---+------+--- - 0 | | zero | 2 | 3 | two | 2 4 | 1 | four | 2 + 0 | | zero | (3 rows) -- mismatch number of columns @@ -1718,13 +1718,13 @@ SELECT * FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a); a | b | t | k ---+---+-------+---- - 0 | | zero | 1 | 4 | one | -1 2 | 3 | two | 2 - 2 | 3 | two | 4 3 | 2 | three | -3 + 2 | 3 | two | 4 5 | 0 | five | -5 5 | 0 | five | -5 + 0 | | zero | (7 rows) -- @@ -1734,22 +1734,22 @@ SELECT * FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i); i | j | t | i | k ---+---+-------+---+---- - 0 | | zero | 0 | 1 | 4 | one | 1 | -1 2 | 3 | two | 2 | 2 - 2 | 3 | two | 2 | 4 3 | 2 | three | 3 | -3 + 2 | 3 | two | 2 | 4 5 | 0 | five | 5 | -5 5 | 0 | five | 5 | -5 + 0 | | zero | 0 | (7 rows) SELECT * FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k); i | j | t | i | k ---+---+------+---+--- - 0 | | zero | | 0 2 | 3 | two | 2 | 2 4 | 1 | four | 2 | 4 + 0 | | zero | | 0 (3 rows) -- @@ -1909,8 +1909,8 @@ select * from tenk1 a, tenk1 b where exists(select * from tenk1 c where b.twothousand = c.twothousand and b.fivethous <> c.fivethous) and a.tenthous = b.tenthous and a.tenthous < 5000; - QUERY PLAN --------------------------------------------------- + QUERY PLAN +----------------------------------------------- Hash Semi Join Hash Cond: (b.twothousand = c.twothousand) Join Filter: (b.fivethous <> c.fivethous) @@ -1918,15 +1918,13 @@ where exists(select * from tenk1 c Hash Cond: (b.tenthous = a.tenthous) -> Seq Scan on tenk1 b Bloom Filter 1: keys=(tenthous) - Bloom Filter 2: keys=(twothousand) -> Hash Bloom Filter 1 -> Seq Scan on tenk1 a Filter: (tenthous < 5000) -> Hash - Bloom Filter 2 -> Seq Scan on tenk1 c -(15 rows) +(13 rows) -- -- More complicated constructs @@ -2604,14 +2602,12 @@ select * from int4_tbl t1 Join Filter: (t2.f1 > 0) Filter: (t3.f1 IS NULL) -> Seq Scan on int4_tbl t2 - Bloom Filter 1: keys=(f1) -> Materialize -> Seq Scan on int4_tbl t3 -> Seq Scan on tenk1 t4 -> Hash - Bloom Filter 1 -> Seq Scan on int4_tbl t1 -(15 rows) +(13 rows) explain (costs off) select * from int4_tbl t1 @@ -2630,15 +2626,13 @@ select * from int4_tbl t1 Join Filter: (t2.f1 > 0) Filter: (t2.f1 <> COALESCE(t3.f1, '-1'::integer)) -> Seq Scan on int4_tbl t2 - Bloom Filter 1: keys=(f1) -> Materialize -> Seq Scan on int4_tbl t3 -> Hash - Bloom Filter 1 -> Seq Scan on int4_tbl t1 -> Materialize -> Seq Scan on tenk1 t4 -(16 rows) +(14 rows) explain (costs off) select * from onek t1 @@ -3126,17 +3120,17 @@ set enable_memoize to off; explain (costs off) select count(*) from tenk1 a, tenk1 b where a.hundred = b.thousand and (b.fivethous % 10) < 10; - QUERY PLAN ------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------ Aggregate -> Hash Join - Hash Cond: (a.hundred = b.thousand) - -> Index Only Scan using tenk1_hundred on tenk1 a - Bloom Filter 1: keys=(hundred) + Hash Cond: (b.thousand = a.hundred) + -> Seq Scan on tenk1 b + Filter: ((fivethous % 10) < 10) + Bloom Filter 1: keys=(thousand) -> Hash Bloom Filter 1 - -> Seq Scan on tenk1 b - Filter: ((fivethous % 10) < 10) + -> Index Only Scan using tenk1_hundred on tenk1 a (9 rows) select count(*) from tenk1 a, tenk1 b @@ -3180,13 +3174,11 @@ ORDER BY 1; Hash Cond: (b.f1 = c.f1) Filter: (COALESCE(c.f1, 0) = 0) -> Seq Scan on tt3 b - Bloom Filter 1: keys=(f1) -> Hash -> Seq Scan on tt3 c -> Hash - Bloom Filter 1 -> Seq Scan on tt4 a -(15 rows) +(13 rows) SELECT a.f1 FROM tt4 a @@ -3224,12 +3216,10 @@ where t1.filt = 5; Hash Join Hash Cond: (t2.val = t1.val) -> Seq Scan on skewedtable t2 - Bloom Filter 1: keys=(val) -> Hash - Bloom Filter 1 -> Seq Scan on skewedtable t1 Filter: (filt = 5) -(8 rows) +(6 rows) drop table skewedtable; -- @@ -3243,11 +3233,9 @@ where unique1 in (select unique2 from tenk1 b); Hash Semi Join Hash Cond: (a.unique1 = b.unique2) -> Seq Scan on tenk1 a - Bloom Filter 1: keys=(unique1) -> Hash - Bloom Filter 1 -> Index Only Scan using tenk1_unique2 on tenk1 b -(7 rows) +(5 rows) -- sadly, this is not an antijoin explain (costs off) @@ -3269,11 +3257,9 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2); Hash Semi Join Hash Cond: (a.unique1 = b.unique2) -> Seq Scan on tenk1 a - Bloom Filter 1: keys=(unique1) -> Hash - Bloom Filter 1 -> Index Only Scan using tenk1_unique2 on tenk1 b -(7 rows) +(5 rows) explain (costs off) select a.* from tenk1 a @@ -3306,17 +3292,15 @@ select 1 from tenk1 where (hundred, thousand) in (select twothousand, twothousand from onek); QUERY PLAN ------------------------------------------------- - Hash Join - Hash Cond: (tenk1.hundred = onek.twothousand) - -> Seq Scan on tenk1 - Filter: (hundred = thousand) - Bloom Filter 1: keys=(hundred) + Hash Right Semi Join + Hash Cond: (onek.twothousand = tenk1.hundred) + -> Seq Scan on onek + Bloom Filter 1: keys=(twothousand) -> Hash Bloom Filter 1 - -> HashAggregate - Group Key: onek.twothousand - -> Seq Scan on onek -(10 rows) + -> Seq Scan on tenk1 + Filter: (hundred = thousand) +(8 rows) reset enable_memoize; -- @@ -3333,19 +3317,17 @@ where t2.a is null; Hash Right Anti Join Hash Cond: (t2.b = t1.unique1) -> Seq Scan on tbl_anti t2 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on tenk1 t1 -(7 rows) +(5 rows) -- this is an antijoin, as t2.a is non-null for any matching row explain (costs off) select * from tenk1 t1 left join (tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b where t2.a is null; - QUERY PLAN ----------------------------------------------- + QUERY PLAN +------------------------------------------- Hash Right Anti Join Hash Cond: (t2.b = t1.unique1) -> Merge Left Join @@ -3353,22 +3335,20 @@ where t2.a is null; -> Sort Sort Key: t2.c -> Seq Scan on tbl_anti t2 - Bloom Filter 1: keys=(b) -> Sort Sort Key: t3.c -> Seq Scan on tbl_anti t3 -> Hash - Bloom Filter 1 -> Seq Scan on tenk1 t1 -(14 rows) +(12 rows) -- this is not an antijoin, as t3.a can be nulled by t2/t3 join explain (costs off) select * from tenk1 t1 left join (tbl_anti t2 left join tbl_anti t3 on t2.c = t3.c) on t1.unique1 = t2.b where t3.a is null; - QUERY PLAN ----------------------------------------------- + QUERY PLAN +------------------------------------------- Hash Right Join Hash Cond: (t2.b = t1.unique1) Filter: (t3.a IS NULL) @@ -3377,14 +3357,12 @@ where t3.a is null; -> Sort Sort Key: t2.c -> Seq Scan on tbl_anti t2 - Bloom Filter 1: keys=(b) -> Sort Sort Key: t3.c -> Seq Scan on tbl_anti t3 -> Hash - Bloom Filter 1 -> Seq Scan on tenk1 t1 -(15 rows) +(13 rows) rollback; -- @@ -3398,11 +3376,9 @@ where exists (select 1 from tenk1 b where a.unique1 = b.unique2 group by b.uniqu Hash Semi Join Hash Cond: (a.unique1 = b.unique2) -> Seq Scan on tenk1 a - Bloom Filter 1: keys=(unique1) -> Hash - Bloom Filter 1 -> Index Only Scan using tenk1_unique2 on tenk1 b -(7 rows) +(5 rows) -- -- regression test for proper handling of outer joins within antijoins @@ -3572,14 +3548,16 @@ create temp table tidv (idv mycomptype); create index on tidv (idv); explain (costs off) select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv; - QUERY PLAN ----------------------------------------------------------- - Merge Join - Merge Cond: (a.idv = b.idv) - -> Index Only Scan using tidv_idv_idx on tidv a - -> Materialize - -> Index Only Scan using tidv_idv_idx on tidv b -(5 rows) + QUERY PLAN +------------------------------------ + Hash Join + Hash Cond: (a.idv = b.idv) + -> Seq Scan on tidv a + Bloom Filter 1: keys=(idv) + -> Hash + Bloom Filter 1 + -> Seq Scan on tidv b +(7 rows) set enable_mergejoin = 0; set enable_hashjoin = 0; @@ -4019,13 +3997,11 @@ where q1 = thousand or q2 = thousand; -> Seq Scan on q2 -> Bitmap Heap Scan on tenk1 Recheck Cond: ((q1.q1 = thousand) OR (q2.q2 = thousand)) - Bloom Filter 1: keys=(twothousand) -> Bitmap Index Scan on tenk1_thous_tenthous Index Cond: (thousand = ANY (ARRAY[q1.q1, q2.q2])) -> Hash - Bloom Filter 1 -> Seq Scan on int4_tbl -(14 rows) +(12 rows) explain (costs off) select * from @@ -4042,13 +4018,11 @@ where thousand = (q1 + q2); -> Seq Scan on q2 -> Bitmap Heap Scan on tenk1 Recheck Cond: (thousand = (q1.q1 + q2.q2)) - Bloom Filter 1: keys=(twothousand) -> Bitmap Index Scan on tenk1_thous_tenthous Index Cond: (thousand = (q1.q1 + q2.q2)) -> Hash - Bloom Filter 1 -> Seq Scan on int4_tbl -(14 rows) +(12 rows) -- -- test ability to generate a suitable plan for a star-schema query @@ -4154,10 +4128,8 @@ where t1.unique1 < i4.f1; Hash Cond: (t2.ten = t1.tenthous) -> Seq Scan on public.tenk1 t2 Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4 - Bloom Filter 1: keys=(t2.ten) -> Hash Output: t1.tenthous, t1.unique1 - Bloom Filter 1 -> Nested Loop Output: t1.tenthous, t1.unique1 -> Subquery Scan on ss0 @@ -4173,7 +4145,7 @@ where t1.unique1 < i4.f1; -> Seq Scan on public.int8_tbl i8 Output: i8.q1, i8.q2 Filter: (i8.q1 = ((64)::information_schema.cardinal_number)::integer) -(35 rows) +(33 rows) select ss1.d1 from tenk1 as t1 @@ -5270,7 +5242,6 @@ order by i0.f1, x; Output: i1.f1, i2.q1, i2.q2, '123'::bigint -> Seq Scan on public.int4_tbl i1 Output: i1.f1 - Bloom Filter 1: keys=(i1.f1) -> Materialize Output: i2.q1, i2.q2 -> Seq Scan on public.int8_tbl i2 @@ -5278,10 +5249,9 @@ order by i0.f1, x; Filter: (123 = i2.q2) -> Hash Output: i0.f1 - Bloom Filter 1 -> Seq Scan on public.int4_tbl i0 Output: i0.f1 -(21 rows) +(19 rows) select * from int4_tbl i0 left join @@ -5335,10 +5305,8 @@ select t1.* from Hash Cond: (i8.q1 = i8b2.q1) -> Seq Scan on public.int8_tbl i8 Output: i8.q1, i8.q2 - Bloom Filter 1: keys=(i8.q1) -> Hash Output: i8b2.q1, (NULL::integer) - Bloom Filter 1 -> Seq Scan on public.int8_tbl i8b2 Output: i8b2.q1, NULL::integer -> Hash @@ -5349,7 +5317,7 @@ select t1.* from Output: i4.f1 -> Seq Scan on public.int4_tbl i4 Output: i4.f1 -(32 rows) +(30 rows) select t1.* from text_tbl t1 @@ -5400,12 +5368,10 @@ select t1.* from Output: i8b2.q1, NULL::integer -> Seq Scan on public.int8_tbl i8b2 Output: i8b2.q1, i8b2.q2 - Bloom Filter 1: keys=(i8b2.q1) -> Materialize -> Seq Scan on public.int4_tbl i4b2 -> Hash Output: i8.q1, i8.q2 - Bloom Filter 1 -> Seq Scan on public.int8_tbl i8 Output: i8.q1, i8.q2 -> Hash @@ -5416,7 +5382,7 @@ select t1.* from Output: i4.f1 -> Seq Scan on public.int4_tbl i4 Output: i4.f1 -(36 rows) +(34 rows) select t1.* from text_tbl t1 @@ -5469,16 +5435,12 @@ select t1.* from Hash Cond: (i8b2.q1 = i4b2.f1) -> Seq Scan on public.int8_tbl i8b2 Output: i8b2.q1, i8b2.q2 - Bloom Filter 1: keys=(i8b2.q1) - Bloom Filter 2: keys=(i8b2.q1) -> Hash Output: i4b2.f1 - Bloom Filter 1 -> Seq Scan on public.int4_tbl i4b2 Output: i4b2.f1 -> Hash Output: i8.q1, i8.q2 - Bloom Filter 2 -> Seq Scan on public.int8_tbl i8 Output: i8.q1, i8.q2 -> Hash @@ -5489,7 +5451,7 @@ select t1.* from Output: i4.f1 -> Seq Scan on public.int4_tbl i4 Output: i4.f1 -(41 rows) +(37 rows) select t1.* from text_tbl t1 @@ -5840,17 +5802,15 @@ where ss1.c2 = 0; Filter: (i43.f1 = 0) -> Seq Scan on public.int4_tbl i41 Output: i41.f1 - Bloom Filter 1: keys=(i41.f1) -> Hash Output: i42.f1 - Bloom Filter 1 -> Seq Scan on public.int4_tbl i42 Output: i42.f1 -> Limit Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42)) -> Seq Scan on public.text_tbl Output: i41.f1, i8.q1, i8.q2, i42.f1, i43.f1, (42) -(27 rows) +(25 rows) select ss2.* from int4_tbl i41 @@ -5976,19 +5936,19 @@ explain (costs off) select a.unique1, b.unique2 from onek a left join onek b on a.unique1 = b.unique2 where (b.unique2, random() > 0) = any (select q1, random() > 0 from int8_tbl c where c.q1 < b.unique1); - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------- Hash Join - Hash Cond: (b.unique2 = a.unique1) - -> Seq Scan on onek b - Filter: (ANY ((unique2 = (SubPlan any_1).col1) AND ((random() > '0'::double precision) = (SubPlan any_1).col2))) - Bloom Filter 1: keys=(unique2) - SubPlan any_1 - -> Seq Scan on int8_tbl c - Filter: (q1 < b.unique1) + Hash Cond: (a.unique1 = b.unique2) + -> Index Only Scan using onek_unique1 on onek a + Bloom Filter 1: keys=(unique1) -> Hash Bloom Filter 1 - -> Index Only Scan using onek_unique1 on onek a + -> Seq Scan on onek b + Filter: (ANY ((unique2 = (SubPlan any_1).col1) AND ((random() > '0'::double precision) = (SubPlan any_1).col2))) + SubPlan any_1 + -> Seq Scan on int8_tbl c + Filter: (q1 < b.unique1) (11 rows) select a.unique1, b.unique2 @@ -6142,16 +6102,14 @@ explain (costs off) select id from a where id in ( select b.id from b left join c on b.id = c.id ); - QUERY PLAN ------------------------------------ + QUERY PLAN +---------------------------- Hash Join Hash Cond: (a.id = b.id) -> Seq Scan on a - Bloom Filter 1: keys=(id) -> Hash - Bloom Filter 1 -> Seq Scan on b -(7 rows) +(5 rows) -- check optimization with oddly-nested outer joins explain (costs off) @@ -6574,18 +6532,16 @@ explain (costs off) select c.id, ss.a from c left join (select d.a from onerow, d left join b on d.a = b.id) ss on c.id = ss.a; - QUERY PLAN ----------------------------------------- + QUERY PLAN +-------------------------------- Hash Right Join Hash Cond: (d.a = c.id) -> Nested Loop -> Seq Scan on onerow -> Seq Scan on d - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on c -(9 rows) +(7 rows) -- check the case when the placeholder relates to an outer join and its -- inner in the press field but actually uses only the outer side of the join @@ -8254,33 +8210,32 @@ JOIN ( ) ) _t2t3t4 ON sj_t1.id = _t2t3t4.id; - QUERY PLAN -------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------- Nested Loop - Join Filter: (sj_t3.id = sj_t1.id) + Join Filter: (sj_t1.id = sj_t3.id) -> Nested Loop - Join Filter: (sj_t2.id = sj_t3.id) - -> Nested Loop Semi Join + Join Filter: (sj_t3.id = sj_t2_1.id) + -> Nested Loop + Join Filter: (sj_t2.id = sj_t3.id) -> Nested Loop - -> HashAggregate - Group Key: sj_t3.id + -> Unique + -> Nested Loop + -> Index Only Scan using sj_t3_a_id_idx on sj_t3 sj_t3_1 + Index Cond: (a = 1) + -> Seq Scan on sj_t4 sj_t4_1 + -> Index Only Scan using sj_t2_id_idx on sj_t2 + Index Cond: (id = sj_t3_1.id) + -> Materialize + -> Unique -> Nested Loop + -> Index Only Scan using sj_t3_a_id_idx on sj_t3 + Index Cond: (a = 1) -> Seq Scan on sj_t4 - -> Materialize - -> Bitmap Heap Scan on sj_t3 - Recheck Cond: (a = 1) - -> Bitmap Index Scan on sj_t3_a_id_idx - Index Cond: (a = 1) - -> Index Only Scan using sj_t2_id_idx on sj_t2 sj_t2_1 - Index Cond: (id = sj_t3.id) - -> Nested Loop - -> Index Only Scan using sj_t3_a_id_idx on sj_t3 sj_t3_1 - Index Cond: ((a = 1) AND (id = sj_t3.id)) - -> Seq Scan on sj_t4 sj_t4_1 - -> Index Only Scan using sj_t2_id_idx on sj_t2 - Index Cond: (id = sj_t2_1.id) + -> Index Only Scan using sj_t2_id_idx on sj_t2 sj_t2_1 + Index Cond: (id = sj_t2.id) -> Seq Scan on sj_t1 -(24 rows) +(23 rows) -- -- Test RowMarks-related code @@ -9143,15 +9098,13 @@ select * from Output: b.q1, COALESCE(b.q2, '42'::bigint) -> Seq Scan on public.int8_tbl d Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2) - Bloom Filter 1: keys=(d.q1) -> Hash Output: c.q1, c.q2 - Bloom Filter 1 -> Seq Scan on public.int8_tbl c Output: c.q1, c.q2 -> Result Output: (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)) -(26 rows) +(24 rows) -- another case requiring nested PlaceHolderVars explain (verbose, costs off) @@ -9210,29 +9163,25 @@ select c.*,a.*,ss1.q1,ss2.q1,ss3.* from Join Filter: (b.q1 < b2.f1) -> Seq Scan on public.int8_tbl b Output: b.q1, b.q2 - Bloom Filter 1: keys=(b.q1) -> Materialize Output: b2.f1 -> Seq Scan on public.int4_tbl b2 Output: b2.f1 -> Hash Output: a.q1, a.q2 - Bloom Filter 1 -> Seq Scan on public.int8_tbl a Output: a.q1, a.q2 -> Seq Scan on public.int8_tbl d Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2) - Bloom Filter 2: keys=(d.q1) -> Hash Output: c.q1, c.q2 - Bloom Filter 2 -> Seq Scan on public.int8_tbl c Output: c.q1, c.q2 -> Materialize Output: i.f1 -> Seq Scan on public.int4_tbl i Output: i.f1 -(38 rows) +(34 rows) -- check processing of postponed quals (bug #9041) explain (verbose, costs off) @@ -9539,10 +9488,8 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral Hash Cond: (t3.b = t2.a) -> Seq Scan on public.join_ut1 t3 Output: t3.a, t3.b, t3.c - Bloom Filter 1: keys=(t3.b) -> Hash Output: t2.a - Bloom Filter 1 -> Append -> Seq Scan on public.join_pt1p1p1 t2_1 Output: t2_1.a @@ -9550,7 +9497,7 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral -> Seq Scan on public.join_pt1p2 t2_2 Output: t2_2.a Filter: (t1.a = t2_2.a) -(23 rows) +(21 rows) select t1.b, ss.phv from join_ut1 t1 left join lateral (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv @@ -9600,22 +9547,21 @@ select * from fkest f1 join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) join fkest f3 on f1.x = f3.x where f1.x100 = 2; - QUERY PLAN ------------------------------------------------------ + QUERY PLAN +--------------------------------------------------------------- Hash Join Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10)) - -> Hash Join - Hash Cond: (f3.x = f2.x) - -> Seq Scan on fkest f3 - Bloom Filter 1: keys=(x) - -> Hash - Bloom Filter 1 - -> Seq Scan on fkest f2 - Filter: (x100 = 2) + -> Nested Loop + -> Seq Scan on fkest f2 + Filter: (x100 = 2) + Bloom Filter 1: keys=(x, x10b) + -> Index Scan using fkest_x_x10_x100_idx on fkest f3 + Index Cond: (x = f2.x) -> Hash + Bloom Filter 1 -> Seq Scan on fkest f1 Filter: (x100 = 2) -(13 rows) +(12 rows) rollback; -- @@ -9668,21 +9614,19 @@ analyze j3; -- ensure join is properly marked as unique explain (verbose, costs off) select * from j1 inner join j2 on j1.id = j2.id; - QUERY PLAN --------------------------------------- + QUERY PLAN +----------------------------------- Hash Join Output: j1.id, j2.id Inner Unique: true Hash Cond: (j1.id = j2.id) -> Seq Scan on public.j1 Output: j1.id - Bloom Filter 1: keys=(j1.id) -> Hash Output: j2.id - Bloom Filter 1 -> Seq Scan on public.j2 Output: j2.id -(12 rows) +(10 rows) -- ensure join is not unique when not an equi-join explain (verbose, costs off) @@ -9707,17 +9651,16 @@ select * from j1 inner join j3 on j1.id = j3.id; -------------------------------------- Hash Join Output: j1.id, j3.id - Inner Unique: true - Hash Cond: (j3.id = j1.id) - -> Seq Scan on public.j3 - Output: j3.id - Bloom Filter 1: keys=(j3.id) - -> Hash + Hash Cond: (j1.id = j3.id) + -> Seq Scan on public.j1 Output: j1.id + Bloom Filter 1: keys=(j1.id) + -> Hash + Output: j3.id Bloom Filter 1 - -> Seq Scan on public.j1 - Output: j1.id -(12 rows) + -> Seq Scan on public.j3 + Output: j3.id +(11 rows) -- ensure left join is marked as unique explain (verbose, costs off) @@ -9788,64 +9731,70 @@ select * from j1 cross join j2; -- ensure a natural join is marked as unique explain (verbose, costs off) select * from j1 natural join j2; - QUERY PLAN --------------------------------------- + QUERY PLAN +----------------------------------- Hash Join Output: j1.id Inner Unique: true Hash Cond: (j1.id = j2.id) -> Seq Scan on public.j1 Output: j1.id - Bloom Filter 1: keys=(j1.id) -> Hash Output: j2.id - Bloom Filter 1 -> Seq Scan on public.j2 Output: j2.id -(12 rows) +(10 rows) -- ensure a distinct clause allows the inner to become unique explain (verbose, costs off) select * from j1 inner join (select distinct id from j3) j3 on j1.id = j3.id; - QUERY PLAN ------------------------------------------ - Nested Loop + QUERY PLAN +----------------------------------------------- + Hash Join Output: j1.id, j3.id Inner Unique: true - Join Filter: (j1.id = j3.id) - -> Unique + Hash Cond: (j1.id = j3.id) + -> Seq Scan on public.j1 + Output: j1.id + Bloom Filter 1: keys=(j1.id) + -> Hash Output: j3.id - -> Sort + Bloom Filter 1 + -> Unique Output: j3.id - Sort Key: j3.id - -> Seq Scan on public.j3 + -> Sort Output: j3.id - -> Seq Scan on public.j1 - Output: j1.id -(13 rows) + Sort Key: j3.id + -> Seq Scan on public.j3 + Output: j3.id +(17 rows) -- ensure group by clause allows the inner to become unique explain (verbose, costs off) select * from j1 inner join (select id from j3 group by id) j3 on j1.id = j3.id; - QUERY PLAN ------------------------------------------ - Nested Loop + QUERY PLAN +----------------------------------------------- + Hash Join Output: j1.id, j3.id Inner Unique: true - Join Filter: (j1.id = j3.id) - -> Group + Hash Cond: (j1.id = j3.id) + -> Seq Scan on public.j1 + Output: j1.id + Bloom Filter 1: keys=(j1.id) + -> Hash Output: j3.id - Group Key: j3.id - -> Sort + Bloom Filter 1 + -> Group Output: j3.id - Sort Key: j3.id - -> Seq Scan on public.j3 + Group Key: j3.id + -> Sort Output: j3.id - -> Seq Scan on public.j1 - Output: j1.id -(14 rows) + Sort Key: j3.id + -> Seq Scan on public.j3 + Output: j3.id +(18 rows) drop table j1; drop table j2; @@ -9959,14 +9908,16 @@ create index j2_id1_idx on j2 (id1) where id1 % 1000 = 1; explain (costs off) select * from j1 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1; - QUERY PLAN ------------------------------------------ - Merge Join - Merge Cond: (j1.id1 = j2.id1) - Join Filter: (j2.id2 = j1.id2) - -> Index Scan using j1_id1_idx on j1 - -> Index Scan using j2_id1_idx on j2 -(5 rows) + QUERY PLAN +---------------------------------------------------------- + Nested Loop + Disabled: true + Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2)) + -> Seq Scan on j1 + Filter: ((id1 % 1000) = 1) + -> Seq Scan on j2 + Filter: ((id1 % 1000) = 1) +(7 rows) select * from j1 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 @@ -9981,15 +9932,16 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1; explain (costs off) select * from j1 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]); - QUERY PLAN ----------------------------------------------------- - Merge Join - Merge Cond: (j1.id1 = j2.id1) - Join Filter: (j2.id2 = j1.id2) - -> Index Scan using j1_id1_idx on j1 - -> Index Scan using j2_id1_idx on j2 - Index Cond: (id1 = ANY ('{1}'::integer[])) -(6 rows) + QUERY PLAN +------------------------------------------------------------------------- + Nested Loop + Disabled: true + Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2)) + -> Seq Scan on j1 + Filter: ((id1 % 1000) = 1) + -> Seq Scan on j2 + Filter: ((id1 = ANY ('{1}'::integer[])) AND ((id1 % 1000) = 1)) +(7 rows) select * from j1 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 @@ -10004,15 +9956,16 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]); explain (costs off) select * from j1 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]); - QUERY PLAN -------------------------------------------------------- - Merge Join - Merge Cond: (j1.id1 = j2.id1) - Join Filter: (j2.id2 = j1.id2) - -> Index Scan using j1_id1_idx on j1 - -> Index Scan using j2_id1_idx on j2 - Index Cond: (id1 >= ANY ('{1,5}'::integer[])) -(6 rows) + QUERY PLAN +---------------------------------------------------------------------------- + Nested Loop + Disabled: true + Join Filter: ((j2.id1 = j1.id1) AND (j2.id2 = j1.id2)) + -> Seq Scan on j1 + Filter: ((id1 % 1000) = 1) + -> Seq Scan on j2 + Filter: ((id1 >= ANY ('{1,5}'::integer[])) AND ((id1 % 1000) = 1)) +(7 rows) select * from j1 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 diff --git a/src/test/regress/expected/join_hash.out b/src/test/regress/expected/join_hash.out index 0a8ade8b961..21900564149 100644 --- a/src/test/regress/expected/join_hash.out +++ b/src/test/regress/expected/join_hash.out @@ -90,17 +90,15 @@ set local work_mem = '4MB'; set local hash_mem_multiplier = 1.0; explain (costs off) select count(*) from simple r join simple s using (id); - QUERY PLAN ------------------------------------------ + QUERY PLAN +---------------------------------------- Aggregate -> Hash Join Hash Cond: (r.id = s.id) -> Seq Scan on simple r - Bloom Filter 1: keys=(id) -> Hash - Bloom Filter 1 -> Seq Scan on simple s -(8 rows) +(6 rows) select count(*) from simple r join simple s using (id); count @@ -205,17 +203,15 @@ set local work_mem = '128kB'; set local hash_mem_multiplier = 1.0; explain (costs off) select count(*) from simple r join simple s using (id); - QUERY PLAN ------------------------------------------ + QUERY PLAN +---------------------------------------- Aggregate -> Hash Join Hash Cond: (r.id = s.id) -> Seq Scan on simple r - Bloom Filter 1: keys=(id) -> Hash - Bloom Filter 1 -> Seq Scan on simple s -(8 rows) +(6 rows) select count(*) from simple r join simple s using (id); count diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index c5aa11cd249..40461acf17c 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -39,18 +39,17 @@ USING source AS s ON t.tid = s.sid WHEN MATCHED THEN DELETE; - QUERY PLAN ----------------------------------------- + QUERY PLAN +------------------------------------------ Merge on target t - -> Merge Join - Merge Cond: (t.tid = s.sid) - -> Sort - Sort Key: t.tid - -> Seq Scan on target t - -> Sort - Sort Key: s.sid + -> Hash Join + Hash Cond: (t.tid = s.sid) + -> Seq Scan on target t + Bloom Filter 1: keys=(tid) + -> Hash + Bloom Filter 1 -> Seq Scan on source s -(9 rows) +(8 rows) -- -- Errors @@ -1640,42 +1639,38 @@ SELECT explain_merge(' MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a WHEN MATCHED THEN UPDATE SET b = t.b + 1'); - explain_merge -------------------------------------------------------------------------- + explain_merge +------------------------------------------------------------------------------------------ Merge on ex_mtarget t (actual rows=0.00 loops=1) Tuples: updated=50 - -> Merge Join (actual rows=50.00 loops=1) - Merge Cond: (t.a = s.a) - -> Sort (actual rows=50.00 loops=1) - Sort Key: t.a - Sort Method: quicksort Memory: xxx - -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1) - -> Sort (actual rows=100.00 loops=1) - Sort Key: s.a - Sort Method: quicksort Memory: xxx + -> Hash Join (actual rows=50.00 loops=1) + Hash Cond: (t.a = s.a) + -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1) + Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%) + -> Hash (actual rows=100.00 loops=1) + Buckets: xxx Batches: xxx Memory Usage: xxx + Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0 -> Seq Scan on ex_msource s (actual rows=100.00 loops=1) -(12 rows) +(10 rows) -- only updates to selected tuples SELECT explain_merge(' MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a WHEN MATCHED AND t.a < 10 THEN UPDATE SET b = t.b + 1'); - explain_merge -------------------------------------------------------------------------- + explain_merge +------------------------------------------------------------------------------------------ Merge on ex_mtarget t (actual rows=0.00 loops=1) Tuples: updated=5 skipped=45 - -> Merge Join (actual rows=50.00 loops=1) - Merge Cond: (t.a = s.a) - -> Sort (actual rows=50.00 loops=1) - Sort Key: t.a - Sort Method: quicksort Memory: xxx - -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1) - -> Sort (actual rows=100.00 loops=1) - Sort Key: s.a - Sort Method: quicksort Memory: xxx + -> Hash Join (actual rows=50.00 loops=1) + Hash Cond: (t.a = s.a) + -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1) + Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%) + -> Hash (actual rows=100.00 loops=1) + Buckets: xxx Batches: xxx Memory Usage: xxx + Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0 -> Seq Scan on ex_msource s (actual rows=100.00 loops=1) -(12 rows) +(10 rows) -- updates + deletes SELECT explain_merge(' @@ -1684,21 +1679,19 @@ WHEN MATCHED AND t.a < 10 THEN UPDATE SET b = t.b + 1 WHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN DELETE'); - explain_merge -------------------------------------------------------------------------- + explain_merge +------------------------------------------------------------------------------------------ Merge on ex_mtarget t (actual rows=0.00 loops=1) Tuples: updated=5 deleted=5 skipped=40 - -> Merge Join (actual rows=50.00 loops=1) - Merge Cond: (t.a = s.a) - -> Sort (actual rows=50.00 loops=1) - Sort Key: t.a - Sort Method: quicksort Memory: xxx - -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1) - -> Sort (actual rows=100.00 loops=1) - Sort Key: s.a - Sort Method: quicksort Memory: xxx + -> Hash Join (actual rows=50.00 loops=1) + Hash Cond: (t.a = s.a) + -> Seq Scan on ex_mtarget t (actual rows=50.00 loops=1) + Bloom Filter 1: keys=(a) checked=49 rejected=0 (0.0%) + -> Hash (actual rows=100.00 loops=1) + Buckets: xxx Batches: xxx Memory Usage: xxx + Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=49 rejected=0 -> Seq Scan on ex_msource s (actual rows=100.00 loops=1) -(12 rows) +(10 rows) -- only inserts SELECT explain_merge(' @@ -1795,21 +1788,20 @@ SELECT explain_merge(' MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000 WHEN MATCHED AND t.a < 10 THEN DO NOTHING'); - explain_merge ------------------------------------------------------------------------ + explain_merge +----------------------------------------------------------------------------------------- Merge on ex_mtarget t (actual rows=0.00 loops=1) - -> Merge Join (actual rows=0.00 loops=1) - Merge Cond: (t.a = s.a) - -> Sort (actual rows=0.00 loops=1) - Sort Key: t.a - Sort Method: quicksort Memory: xxx + -> Hash Join (actual rows=0.00 loops=1) + Hash Cond: (s.a = t.a) + -> Seq Scan on ex_msource s (actual rows=1.00 loops=1) + Bloom Filter 1: keys=(a) checked=0 rejected=0 (0.0%) + -> Hash (actual rows=0.00 loops=1) + Buckets: xxx Batches: xxx Memory Usage: xxx + Bloom Filter 1: bits=8388608 hashes=10 memory=1024kB checked=0 rejected=0 -> Seq Scan on ex_mtarget t (actual rows=0.00 loops=1) Filter: (a < '-1000'::integer) Rows Removed by Filter: 54 - -> Sort (never executed) - Sort Key: s.a - -> Seq Scan on ex_msource s (never executed) -(12 rows) +(11 rows) DROP TABLE ex_msource, ex_mtarget; DROP FUNCTION explain_merge(text); diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out index b52528870ef..7fff6a720aa 100644 --- a/src/test/regress/expected/misc_functions.out +++ b/src/test/regress/expected/misc_functions.out @@ -614,14 +614,16 @@ CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer SUPPORT test_support_func; EXPLAIN (COSTS OFF) SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g; - QUERY PLAN ----------------------------------------- + QUERY PLAN +---------------------------------------------- Hash Join - Hash Cond: (g.g = a.unique1) - -> Function Scan on my_gen_series g + Hash Cond: (a.unique1 = g.g) + -> Seq Scan on tenk1 a + Bloom Filter 1: keys=(unique1) -> Hash - -> Seq Scan on tenk1 a -(5 rows) + Bloom Filter 1 + -> Function Scan on my_gen_series g +(7 rows) EXPLAIN (COSTS OFF) SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g; diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index be56036461b..c30304b99c7 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -460,29 +460,23 @@ SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2 -> Hash Join Hash Cond: (t1.x = t2.y) -> Seq Scan on pagg_tab1_p1 t1 - Bloom Filter 1: keys=(x) -> Hash - Bloom Filter 1 -> Seq Scan on pagg_tab2_p1 t2 -> HashAggregate Group Key: t1_1.x -> Hash Join Hash Cond: (t1_1.x = t2_1.y) -> Seq Scan on pagg_tab1_p2 t1_1 - Bloom Filter 2: keys=(x) -> Hash - Bloom Filter 2 -> Seq Scan on pagg_tab2_p2 t2_1 -> HashAggregate Group Key: t1_2.x -> Hash Join Hash Cond: (t2_2.y = t1_2.x) -> Seq Scan on pagg_tab2_p3 t2_2 - Bloom Filter 3: keys=(y) -> Hash - Bloom Filter 3 -> Seq Scan on pagg_tab1_p3 t1_2 -(30 rows) +(24 rows) SELECT t1.x, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.x ORDER BY 1, 2, 3; x | sum | count @@ -539,29 +533,23 @@ SELECT t2.y, sum(t1.y), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2 -> Hash Join Hash Cond: (t1.x = t2.y) -> Seq Scan on pagg_tab1_p1 t1 - Bloom Filter 1: keys=(x) -> Hash - Bloom Filter 1 -> Seq Scan on pagg_tab2_p1 t2 -> HashAggregate Group Key: t2_1.y -> Hash Join Hash Cond: (t1_1.x = t2_1.y) -> Seq Scan on pagg_tab1_p2 t1_1 - Bloom Filter 2: keys=(x) -> Hash - Bloom Filter 2 -> Seq Scan on pagg_tab2_p2 t2_1 -> HashAggregate Group Key: t2_2.y -> Hash Join Hash Cond: (t2_2.y = t1_2.x) -> Seq Scan on pagg_tab2_p3 t2_2 - Bloom Filter 3: keys=(y) -> Hash - Bloom Filter 3 -> Seq Scan on pagg_tab1_p3 t1_2 -(30 rows) +(24 rows) -- When GROUP BY clause does not match; partial aggregation is performed for each partition. -- Also test GroupAggregate paths by disabling hash aggregates. @@ -584,9 +572,7 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2 -> Hash Join Hash Cond: (t1.x = t2.y) -> Seq Scan on pagg_tab1_p1 t1 - Bloom Filter 1: keys=(x) -> Hash - Bloom Filter 1 -> Seq Scan on pagg_tab2_p1 t2 -> Partial GroupAggregate Group Key: t1_1.y @@ -595,9 +581,7 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2 -> Hash Join Hash Cond: (t1_1.x = t2_1.y) -> Seq Scan on pagg_tab1_p2 t1_1 - Bloom Filter 2: keys=(x) -> Hash - Bloom Filter 2 -> Seq Scan on pagg_tab2_p2 t2_1 -> Partial GroupAggregate Group Key: t1_2.y @@ -606,11 +590,9 @@ SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2 -> Hash Join Hash Cond: (t2_2.y = t1_2.x) -> Seq Scan on pagg_tab2_p3 t2_2 - Bloom Filter 3: keys=(y) -> Hash - Bloom Filter 3 -> Seq Scan on pagg_tab1_p3 t1_2 -(40 rows) +(34 rows) SELECT t1.y, sum(t1.x), count(*) FROM pagg_tab1 t1, pagg_tab2 t2 WHERE t1.x = t2.y GROUP BY t1.y HAVING avg(t1.x) > 10 ORDER BY 1, 2, 3; y | sum | count @@ -656,11 +638,9 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP B -> Hash Right Join Hash Cond: (b_2.y = a_2.x) -> Seq Scan on pagg_tab2_p3 b_2 - Bloom Filter 1: keys=(y) -> Hash - Bloom Filter 1 -> Seq Scan on pagg_tab1_p3 a_2 -(28 rows) +(26 rows) SELECT b.y, sum(a.y) FROM pagg_tab1 a LEFT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; y | sum @@ -687,18 +667,14 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP -> Hash Right Join Hash Cond: (a.x = b.y) -> Seq Scan on pagg_tab1_p1 a - Bloom Filter 1: keys=(x) -> Hash - Bloom Filter 1 -> Seq Scan on pagg_tab2_p1 b -> HashAggregate Group Key: b_1.y -> Hash Right Join Hash Cond: (a_1.x = b_1.y) -> Seq Scan on pagg_tab1_p2 a_1 - Bloom Filter 2: keys=(x) -> Hash - Bloom Filter 2 -> Seq Scan on pagg_tab2_p2 b_1 -> HashAggregate Group Key: b_2.y @@ -707,7 +683,7 @@ SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP -> Seq Scan on pagg_tab2_p3 b_2 -> Hash -> Seq Scan on pagg_tab1_p3 a_2 -(28 rows) +(24 rows) SELECT b.y, sum(a.y) FROM pagg_tab1 a RIGHT JOIN pagg_tab2 b ON a.x = b.y GROUP BY b.y ORDER BY 1 NULLS LAST; y | sum diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 1906b3641a3..38643d41fd7 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -36,28 +36,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = -> Hash Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Hash Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -> Hash Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -83,28 +77,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a = -> Hash Join Hash Cond: (t1_1.a = t2_1.a) -> Seq Scan on prt1_p1 t1_1 - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt2_p1 t2_1 Filter: (a = b) -> Hash Join Hash Cond: (t1_2.a = t2_2.a) -> Seq Scan on prt1_p2 t1_2 - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Seq Scan on prt2_p2 t2_2 Filter: (a = b) -> Hash Join Hash Cond: (t1_3.a = t2_3.a) -> Seq Scan on prt1_p3 t1_3 - Bloom Filter 3: keys=(a) -> Hash - Bloom Filter 3 -> Seq Scan on prt2_p3 t2_3 Filter: (a = b) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.a AND t1.a = t2.b ORDER BY t1.a, t2.b; a | c | b | c @@ -214,17 +202,13 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE -> Hash Right Join Hash Cond: (t1_1.a = t2_1.b) -> Seq Scan on prt1_p1 t1_1 - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt2_p1 t2_1 Filter: (a = 0) -> Hash Right Join Hash Cond: (t1_2.a = t2_2.b) -> Seq Scan on prt1_p2 t1_2 - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Seq Scan on prt2_p2 t2_2 Filter: (a = 0) -> Nested Loop Left Join @@ -232,7 +216,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE Filter: (a = 0) -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 Index Cond: (a = t2_3.b) -(24 rows) +(20 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -299,12 +283,10 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < Hash Cond: (t2.b = t1.a) -> Seq Scan on prt2_p2 t2 Filter: (b > 250) - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_p2 t1 Filter: ((a < 450) AND (b = 0)) -(11 rows) +(9 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -400,18 +382,14 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) Hash Cond: (t1_1.a = t2_1.b) -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt2_p1 t2_1 Filter: (a = 0) -> Hash Semi Join Hash Cond: (t1_2.a = t2_2.b) -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Seq Scan on prt2_p2 t2_2 Filter: (a = 0) -> Nested Loop Semi Join @@ -421,7 +399,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) -> Materialize -> Seq Scan on prt2_p3 t2_3 Filter: (a = 0) -(28 rows) +(24 rows) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; a | b | c @@ -537,25 +515,19 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL -> Hash Join Hash Cond: (t2_1.a = t3_1.b) -> Seq Scan on prt1_p1 t2_1 - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt2_p1 t3_1 -> Hash Join Hash Cond: (t2_2.a = t3_2.b) -> Seq Scan on prt1_p2 t2_2 - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Seq Scan on prt2_p2 t3_2 -> Hash Join Hash Cond: (t2_3.a = t3_3.b) -> Seq Scan on prt1_p3 t2_3 - Bloom Filter 3: keys=(a) -> Hash - Bloom Filter 3 -> Seq Scan on prt2_p3 t3_3 -(32 rows) +(26 rows) SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss @@ -756,41 +728,29 @@ SELECT * FROM prt1 t1 JOIN prt1 t2 ON t1.a = t2.a WHERE t1.a IN (SELECT a FROM p -> Hash Join Hash Cond: (t1_1.a = t2_1.a) -> Seq Scan on prt1_p1 t1_1 - Bloom Filter 1: keys=(a) - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_p1 t2_1 -> Hash - Bloom Filter 2 -> Seq Scan on prt1_p1 t3_1 -> Hash Semi Join Hash Cond: (t1_2.a = t3_2.a) -> Hash Join Hash Cond: (t1_2.a = t2_2.a) -> Seq Scan on prt1_p2 t1_2 - Bloom Filter 3: keys=(a) - Bloom Filter 4: keys=(a) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_p2 t2_2 -> Hash - Bloom Filter 4 -> Seq Scan on prt1_p2 t3_2 -> Hash Semi Join Hash Cond: (t1_3.a = t3_3.a) -> Hash Join Hash Cond: (t1_3.a = t2_3.a) -> Seq Scan on prt1_p3 t1_3 - Bloom Filter 5: keys=(a) - Bloom Filter 6: keys=(a) -> Hash - Bloom Filter 5 -> Seq Scan on prt1_p3 t2_3 -> Hash - Bloom Filter 6 -> Seq Scan on prt1_p3 t3_3 -(40 rows) +(28 rows) -- -- partitioned by expression @@ -861,9 +821,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t -> Hash Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3_1 @@ -873,9 +831,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t -> Hash Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2 @@ -885,14 +841,12 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t -> Hash Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_p3 t1_3 Filter: (b = 0) -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3 Index Cond: (((a + b) / 2) = t2_3.b) -(39 rows) +(33 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c | ?column? | c @@ -917,9 +871,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 -> Hash Right Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Hash Right Join @@ -929,9 +881,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 -> Hash Right Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -> Hash Right Join @@ -941,12 +891,10 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 -> Hash Right Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_p3 t1_3 Filter: (b = 0) -(39 rows) +(33 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; a | c | b | c | ?column? | c @@ -976,9 +924,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 -> Hash Right Join Hash Cond: (t1_1.a = ((t3_1.a + t3_1.b) / 2)) -> Seq Scan on prt1_p1 t1_1 - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_e_p1 t3_1 Filter: (c = 0) -> Index Scan using iprt2_p1_b on prt2_p1 t2_1 @@ -987,9 +933,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 -> Hash Right Join Hash Cond: (t1_2.a = ((t3_2.a + t3_2.b) / 2)) -> Seq Scan on prt1_p2 t1_2 - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_e_p2 t3_2 Filter: (c = 0) -> Index Scan using iprt2_p2_b on prt2_p2 t2_2 @@ -998,14 +942,12 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 -> Hash Right Join Hash Cond: (t1_3.a = ((t3_3.a + t3_3.b) / 2)) -> Seq Scan on prt1_p3 t1_3 - Bloom Filter 3: keys=(a) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_e_p3 t3_3 Filter: (c = 0) -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 Index Cond: (b = t1_3.a) -(36 rows) +(30 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; a | c | b | c | ?column? | c @@ -1263,9 +1205,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN ( -> Hash Semi Join Hash Cond: (t1_6.b = ((t1_9.a + t1_9.b) / 2)) -> Seq Scan on prt2_p1 t1_6 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_e_p1 t1_9 Filter: (c = 0) -> Index Scan using iprt1_p1_a on prt1_p1 t1_3 @@ -1278,9 +1218,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN ( -> Hash Semi Join Hash Cond: (t1_7.b = ((t1_10.a + t1_10.b) / 2)) -> Seq Scan on prt2_p2 t1_7 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_e_p2 t1_10 Filter: (c = 0) -> Index Scan using iprt1_p2_a on prt1_p2 t1_4 @@ -1293,15 +1231,13 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN ( -> Hash Semi Join Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2)) -> Seq Scan on prt2_p3 t1_8 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_e_p3 t1_11 Filter: (c = 0) -> Index Scan using iprt1_p3_a on prt1_p3 t1_5 Index Cond: (a = t1_8.b) Filter: (b = 0) -(47 rows) +(41 rows) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; a | b | c @@ -1631,41 +1567,29 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, pl -> Hash Join Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c)) -> Seq Scan on plt1_p1 t1_1 - Bloom Filter 1: keys=(b, c) - Bloom Filter 2: keys=(c) -> Hash - Bloom Filter 1 -> Seq Scan on plt2_p1 t2_1 -> Hash - Bloom Filter 2 -> Seq Scan on plt1_e_p1 t3_1 -> Hash Join Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text)) -> Hash Join Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c)) -> Seq Scan on plt1_p2 t1_2 - Bloom Filter 3: keys=(b, c) - Bloom Filter 4: keys=(c) -> Hash - Bloom Filter 3 -> Seq Scan on plt2_p2 t2_2 -> Hash - Bloom Filter 4 -> Seq Scan on plt1_e_p2 t3_2 -> Hash Join Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text)) -> Hash Join Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c)) -> Seq Scan on plt1_p3 t1_3 - Bloom Filter 5: keys=(b, c) - Bloom Filter 6: keys=(c) -> Hash - Bloom Filter 5 -> Seq Scan on plt2_p3 t2_3 -> Hash - Bloom Filter 6 -> Seq Scan on plt1_e_p3 t3_3 -(44 rows) +(32 rows) SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; avg | avg | avg | c | c | c @@ -1713,29 +1637,23 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 -> Hash Join Hash Cond: (t3_1.a = t2_1.b) -> Seq Scan on prt1_p1 t3_1 - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt2_p1 t2_1 -> Hash Join Hash Cond: (t3_2.a = t2_2.b) -> Seq Scan on prt1_p2 t3_2 - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Seq Scan on prt2_p2 t2_2 -> Hash Join Hash Cond: (t3_3.a = t2_3.b) -> Seq Scan on prt1_p3 t3_3 - Bloom Filter 3: keys=(a) -> Hash - Bloom Filter 3 -> Seq Scan on prt2_p3 t2_3 -> Hash -> Result Replaces: Scan on prt1 One-Time Filter: false -(28 rows) +(22 rows) EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; @@ -1797,41 +1715,29 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, ph -> Hash Join Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c)) -> Seq Scan on pht1_p1 t1_1 - Bloom Filter 1: keys=(b, c) - Bloom Filter 2: keys=(c) -> Hash - Bloom Filter 1 -> Seq Scan on pht2_p1 t2_1 -> Hash - Bloom Filter 2 -> Seq Scan on pht1_e_p1 t3_1 -> Hash Join Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text)) -> Hash Join Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c)) -> Seq Scan on pht1_p2 t1_2 - Bloom Filter 3: keys=(b, c) - Bloom Filter 4: keys=(c) -> Hash - Bloom Filter 3 -> Seq Scan on pht2_p2 t2_2 -> Hash - Bloom Filter 4 -> Seq Scan on pht1_e_p2 t3_2 -> Hash Join Hash Cond: (t1_3.c = ltrim(t3_3.c, 'A'::text)) -> Hash Join Hash Cond: ((t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c)) -> Seq Scan on pht1_p3 t1_3 - Bloom Filter 5: keys=(b, c) - Bloom Filter 6: keys=(c) -> Hash - Bloom Filter 5 -> Seq Scan on pht2_p3 t2_3 -> Hash - Bloom Filter 6 -> Seq Scan on pht1_e_p3 t3_3 -(44 rows) +(32 rows) SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; avg | avg | avg | c | c | c @@ -1861,28 +1767,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = -> Hash Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Hash Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -> Hash Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) -- test default partition behavior for list ALTER TABLE plt1 DETACH PARTITION plt1_p3; @@ -1903,28 +1803,22 @@ SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c -> Hash Join Hash Cond: (t2_1.c = t1_1.c) -> Seq Scan on plt2_p1 t2_1 - Bloom Filter 1: keys=(c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_p1 t1_1 Filter: ((a % 25) = 0) -> Hash Join Hash Cond: (t2_2.c = t1_2.c) -> Seq Scan on plt2_p2 t2_2 - Bloom Filter 2: keys=(c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_p2 t1_2 Filter: ((a % 25) = 0) -> Hash Join Hash Cond: (t2_3.c = t1_3.c) -> Seq Scan on plt2_p3 t2_3 - Bloom Filter 3: keys=(c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_p3 t1_3 Filter: ((a % 25) = 0) -(29 rows) +(23 rows) -- -- multiple levels of partitioning @@ -1960,9 +1854,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1 -> Hash Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_l_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_l_p1 t1_1 Filter: (b = 0) -> Hash Join @@ -1984,7 +1876,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1 -> Hash -> Seq Scan on prt1_l_p3_p1 t1_5 Filter: (b = 0) -(30 rows) +(28 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -2463,25 +2355,19 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a -> Hash Join Hash Cond: (t1_1.a = t3_1.b) -> Seq Scan on prt1_p1 t1_1 - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt2_p1 t3_1 -> Hash Join Hash Cond: (t1_2.a = t3_2.b) -> Seq Scan on prt1_p2 t1_2 - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Seq Scan on prt2_p2 t3_2 -> Hash Join Hash Cond: (t1_3.a = t3_3.b) -> Seq Scan on prt1_p3 t1_3 - Bloom Filter 3: keys=(a) -> Hash - Bloom Filter 3 -> Seq Scan on prt2_p3 t3_3 -(29 rows) +(23 rows) -- partitionwise join can not be applied if there are no equi-join conditions -- between partition keys @@ -2753,28 +2639,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: (b = 0) -> Hash Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: (b = 0) -> Hash Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_adv_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -2800,28 +2680,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a -> Hash Right Semi Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: (b = 0) -> Hash Right Semi Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: (b = 0) -> Hash Right Semi Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_adv_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; a | b | c @@ -2847,28 +2721,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = -> Hash Right Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: (b = 0) -> Hash Right Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: (b = 0) -> Hash Right Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_adv_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -2898,28 +2766,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t -> Hash Right Anti Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: (b = 0) -> Hash Right Anti Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: (b = 0) -> Hash Right Anti Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_adv_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; a | b | c @@ -2986,28 +2848,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: (b = 0) -> Hash Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: (b = 0) -> Hash Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_adv_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -3033,28 +2889,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a -> Hash Right Semi Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: (b = 0) -> Hash Right Semi Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: (b = 0) -> Hash Right Semi Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_adv_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; a | b | c @@ -3080,28 +2930,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = -> Hash Right Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: (b = 0) -> Hash Right Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: (b = 0) -> Hash Right Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_adv_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -3157,28 +3001,22 @@ SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t -> Hash Right Anti Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: (b = 0) -> Hash Right Anti Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: (b = 0) -> Hash Right Anti Join Hash Cond: (t2_3.b = t1_3.a) -> Seq Scan on prt2_adv_p3 t2_3 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p3 t1_3 Filter: (b = 0) -(27 rows) +(21 rows) SELECT t1.* FROM prt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a; a | b | c @@ -3264,32 +3102,24 @@ SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 -> Hash Right Join Hash Cond: (t2_2.a = t1_2.b) -> Seq Scan on prt1_adv_p2 t2_2 - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Hash Join Hash Cond: (t3_2.a = t1_2.b) -> Seq Scan on prt1_adv_p2 t3_2 - Bloom Filter 1: keys=(a) -> Hash - Bloom Filter 1 -> Seq Scan on prt2_adv_p2 t1_2 Filter: (a = 0) -> Hash Right Join Hash Cond: (t2_3.a = t1_3.b) -> Seq Scan on prt1_adv_p3 t2_3 - Bloom Filter 4: keys=(a) -> Hash - Bloom Filter 4 -> Hash Join Hash Cond: (t3_3.a = t1_3.b) -> Seq Scan on prt1_adv_p3 t3_3 - Bloom Filter 3: keys=(a) -> Hash - Bloom Filter 3 -> Seq Scan on prt2_adv_p3 t1_3 Filter: (a = 0) -(39 rows) +(31 rows) SELECT t1.b, t1.c, t2.a, t2.c, t3.a, t3.c FROM prt2_adv t1 LEFT JOIN prt1_adv t2 ON (t1.b = t2.a) INNER JOIN prt1_adv t3 ON (t1.b = t3.a) WHERE t1.a = 0 ORDER BY t1.b, t2.a, t3.a; b | c | a | c | a | c @@ -3459,20 +3289,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: (t2_1.b = t1_2.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_2 Filter: (b = 0) -> Hash Join Hash Cond: (t2_2.b = t1_1.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_1 Filter: (b = 0) -(19 rows) +(15 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -3564,32 +3390,24 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 -> Hash Right Join Hash Cond: (t3_1.a = t1_1.a) -> Seq Scan on prt3_adv_p1 t3_1 - Bloom Filter 2: keys=(a) -> Hash - Bloom Filter 2 -> Hash Right Join Hash Cond: (t2_2.b = t1_1.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p2 t1_1 Filter: (b = 0) -> Hash Right Join Hash Cond: (t3_2.a = t1_2.a) -> Seq Scan on prt3_adv_p2 t3_2 - Bloom Filter 4: keys=(a) -> Hash - Bloom Filter 4 -> Hash Right Join Hash Cond: (t2_1.b = t1_2.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 3: keys=(b) -> Hash - Bloom Filter 3 -> Seq Scan on prt1_adv_p1 t1_2 Filter: (b = 0) -(31 rows) +(23 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a, t3.c FROM prt1_adv t1 LEFT JOIN prt2_adv t2 ON (t1.a = t2.b) LEFT JOIN prt3_adv t3 ON (t1.a = t3.a) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a; a | c | b | c | a | c @@ -3631,20 +3449,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: ((a < 300) AND (b = 0)) -> Hash Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: ((a < 300) AND (b = 0)) -(19 rows) +(15 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -3674,20 +3488,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: (t2_1.b = t1_1.a) -> Seq Scan on prt2_adv_p1 t2_1 - Bloom Filter 1: keys=(b) -> Hash - Bloom Filter 1 -> Seq Scan on prt1_adv_p1 t1_1 Filter: ((a >= 100) AND (a < 300) AND (b = 0)) -> Hash Join Hash Cond: (t2_2.b = t1_2.a) -> Seq Scan on prt2_adv_p2 t2_2 - Bloom Filter 2: keys=(b) -> Hash - Bloom Filter 2 -> Seq Scan on prt1_adv_p2 t1_2 Filter: ((a >= 100) AND (a < 300) AND (b = 0)) -(19 rows) +(15 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b; a | c | b | c @@ -3728,28 +3538,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -3771,28 +3575,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a -> Hash Right Semi Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Right Semi Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Semi Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; a | b | c @@ -3814,28 +3612,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = -> Hash Right Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -3859,28 +3651,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t -> Hash Right Anti Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Right Anti Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Anti Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; a | b | c @@ -3945,28 +3731,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -3988,28 +3768,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a -> Hash Right Semi Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Right Semi Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Semi Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; a | b | c @@ -4031,28 +3805,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = -> Hash Right Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -4102,28 +3870,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t -> Hash Right Anti Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Right Anti Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Anti Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; a | b | c @@ -4336,28 +4098,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1_null t1_1 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3_null t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -4379,28 +4135,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a -> Hash Right Semi Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1_null t1_1 Filter: (b < 10) -> Hash Right Semi Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Semi Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3_null t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.* FROM plt1_adv t1 WHERE EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; a | b | c @@ -4422,28 +4172,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = -> Hash Right Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1_null t1_1 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3_null t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -4468,28 +4212,22 @@ SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t -> Hash Right Anti Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1_null t1_1 Filter: (b < 10) -> Hash Right Anti Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Anti Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3_null t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.* FROM plt1_adv t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv t2 WHERE t1.a = t2.a AND t1.c = t2.c) AND t1.b < 10 ORDER BY t1.a; a | b | c @@ -4565,28 +4303,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -4662,28 +4394,22 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -(27 rows) +(21 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -4705,25 +4431,19 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = -> Hash Right Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -> Nested Loop Left Join @@ -4731,7 +4451,7 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = -> Seq Scan on plt1_adv_extra t1_4 Filter: (b < 10) -> Seq Scan on plt2_adv_extra t2_4 -(32 rows) +(26 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -4805,43 +4525,31 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 -> Hash Right Join Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c)) -> Seq Scan on plt1_adv_p1 t3_1 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Hash Right Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_1 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c)) -> Seq Scan on plt1_adv_p2 t3_2 - Bloom Filter 4: keys=(a, c) -> Hash - Bloom Filter 4 -> Hash Right Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p2 t1_2 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c)) -> Seq Scan on plt1_adv_p3 t3_3 - Bloom Filter 6: keys=(a, c) -> Hash - Bloom Filter 6 -> Hash Right Join Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c)) -> Seq Scan on plt2_adv_p3 t2_3 - Bloom Filter 5: keys=(a, c) -> Hash - Bloom Filter 5 -> Seq Scan on plt1_adv_p3 t1_3 Filter: (b < 10) -> Nested Loop Left Join @@ -4852,7 +4560,7 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 Filter: (b < 10) -> Seq Scan on plt2_adv_extra t2_4 -> Seq Scan on plt1_adv_extra t3_4 -(53 rows) +(41 rows) SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt1_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c | a | c @@ -4888,20 +4596,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_2 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_1 Filter: (b < 10) -(19 rows) +(15 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -4983,32 +4687,24 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 -> Hash Right Join Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c)) -> Seq Scan on plt3_adv_p1 t3_1 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Hash Right Join Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p2 t1_1 Filter: (b < 10) -> Hash Right Join Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c)) -> Seq Scan on plt3_adv_p2 t3_2 - Bloom Filter 4: keys=(a, c) -> Hash - Bloom Filter 4 -> Hash Right Join Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c)) -> Seq Scan on plt2_adv_p1 t2_1 - Bloom Filter 3: keys=(a, c) -> Hash - Bloom Filter 3 -> Seq Scan on plt1_adv_p1 t1_2 Filter: (b < 10) -(31 rows) +(23 rows) SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) LEFT JOIN plt3_adv t3 ON (t1.a = t3.a AND t1.c = t3.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c | a | c @@ -5037,20 +4733,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_2.a) AND (t2_1.c = t1_2.c)) -> Seq Scan on plt2_adv_p1_null t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p1 t1_2 Filter: (b < 10) -> Hash Join Hash Cond: ((t2_2.a = t1_1.a) AND (t2_2.c = t1_1.c)) -> Seq Scan on plt2_adv_p2 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p2 t1_1 Filter: (b < 10) -(19 rows) +(15 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -5075,12 +4767,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) -> Seq Scan on plt2_adv_p2 t2 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p2 t1 Filter: (b < 10) -(10 rows) +(8 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -5119,20 +4809,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p3 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p3 t1_1 Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[]))) -> Hash Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p4 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p4 t1_2 Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[]))) -(19 rows) +(15 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -5151,12 +4837,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = -> Hash Right Join Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) -> Seq Scan on plt2_adv_p4 t2 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p4 t1 Filter: ((c IS NULL) AND (b < 10)) -(10 rows) +(8 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -5178,20 +4862,16 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = -> Hash Join Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c)) -> Seq Scan on plt2_adv_p3 t2_1 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p3 t1_1 Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[]))) -> Hash Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c)) -> Seq Scan on plt2_adv_p4 t2_2 - Bloom Filter 2: keys=(a, c) -> Hash - Bloom Filter 2 -> Seq Scan on plt1_adv_p4 t1_2 Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[]))) -(19 rows) +(15 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -5210,12 +4890,10 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = -> Hash Right Join Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c)) -> Seq Scan on plt2_adv_p4 t2 - Bloom Filter 1: keys=(a, c) -> Hash - Bloom Filter 1 -> Seq Scan on plt1_adv_p4 t1 Filter: ((c IS NULL) AND (b < 10)) -(10 rows) +(8 rows) SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 LEFT JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IS NULL AND t1.b < 10 ORDER BY t1.a; a | c | a | c @@ -5337,16 +5015,12 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2 Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b)) -> Seq Scan on alpha_neg_p1 t1_1 Filter: ((b >= 125) AND (b < 225)) - Bloom Filter 1: keys=(a, b) -> Hash - Bloom Filter 1 -> Seq Scan on beta_neg_p1 t2_1 -> Hash Join Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b)) -> Seq Scan on beta_neg_p2 t2_2 - Bloom Filter 2: keys=(a, b) -> Hash - Bloom Filter 2 -> Seq Scan on alpha_neg_p2 t1_2 Filter: ((b >= 125) AND (b < 225)) -> Hash Join @@ -5363,7 +5037,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2 Filter: ((b >= 125) AND (b < 225)) -> Seq Scan on alpha_pos_p3 t1_6 Filter: ((b >= 125) AND (b < 225)) -(33 rows) +(29 rows) SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b; a | b | c | a | b | c @@ -5476,18 +5150,14 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2 Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c)) -> Seq Scan on alpha_neg_p1 t1_1 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - Bloom Filter 1: keys=(a, b, c) -> Hash - Bloom Filter 1 -> Seq Scan on beta_neg_p1 t2_1 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) -> Hash Join Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c)) -> Seq Scan on alpha_neg_p2 t1_2 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) - Bloom Filter 2: keys=(a, b, c) -> Hash - Bloom Filter 2 -> Seq Scan on beta_neg_p2 t2_2 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) -> Nested Loop @@ -5502,7 +5172,7 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))) -> Seq Scan on beta_pos_p3 t2_4 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))) -(33 rows) +(29 rows) SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b; a | b | c | a | b | c @@ -5646,25 +5316,19 @@ EXPLAIN (COSTS OFF) SELECT * FROM pht1 p1 JOIN pht1 p2 USING (c) LIMIT 1000; -> Hash Join Hash Cond: (p1_1.c = p2_1.c) -> Seq Scan on pht1_p1 p1_1 - Bloom Filter 1: keys=(c) -> Hash - Bloom Filter 1 -> Seq Scan on pht1_p1 p2_1 -> Hash Join Hash Cond: (p1_2.c = p2_2.c) -> Seq Scan on pht1_p2 p1_2 - Bloom Filter 2: keys=(c) -> Hash - Bloom Filter 2 -> Seq Scan on pht1_p2 p2_2 -> Hash Join Hash Cond: (p1_3.c = p2_3.c) -> Seq Scan on pht1_p3 p1_3 - Bloom Filter 3: keys=(c) -> Hash - Bloom Filter 3 -> Seq Scan on pht1_p3 p2_3 -(23 rows) +(17 rows) RESET enable_mergejoin; SET max_parallel_workers_per_gather = 1; diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out index 079f6422fdc..feae77cb840 100644 --- a/src/test/regress/expected/predicate.out +++ b/src/test/regress/expected/predicate.out @@ -748,16 +748,14 @@ SELECT id FROM dist_tab WHERE row_nn IS DISTINCT FROM ROW(1, 5)::dist_row_t; SET enable_nestloop TO off; EXPLAIN (COSTS OFF) SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn; - QUERY PLAN ---------------------------------------- + QUERY PLAN +-------------------------------------- Hash Join Hash Cond: (t1.val_nn = t2.val_nn) -> Seq Scan on dist_tab t1 - Bloom Filter 1: keys=(val_nn) -> Hash - Bloom Filter 1 -> Seq Scan on dist_tab t2 -(7 rows) +(5 rows) SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn; id | val_nn | val_null | row_nn | id | val_nn | val_null | row_nn diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out index dc44871b682..50cd3be8030 100644 --- a/src/test/regress/expected/returning.out +++ b/src/test/regress/expected/returning.out @@ -713,33 +713,31 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57 Update on pg_temp.foo foo_1 -> Hash Join Output: foo_2.f1, (foo_2.f3 + 1), joinme.ctid, foo_2.ctid, joinme_1.ctid, joinme.other, foo_1.tableoid, foo_1.ctid, foo_2.tableoid - Hash Cond: (foo_1.f2 = joinme.f2j) - -> Hash Join - Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, joinme_1.ctid, joinme_1.f2j - Hash Cond: (joinme_1.f2j = foo_1.f2) - -> Seq Scan on pg_temp.joinme joinme_1 - Output: joinme_1.ctid, joinme_1.f2j - Bloom Filter 1: keys=(joinme_1.f2j) - -> Hash - Output: foo_1.f2, foo_1.tableoid, foo_1.ctid - Bloom Filter 1 - -> Seq Scan on pg_temp.foo foo_1 - Output: foo_1.f2, foo_1.tableoid, foo_1.ctid + Hash Cond: (joinme_1.f2j = foo_1.f2) + -> Seq Scan on pg_temp.joinme joinme_1 + Output: joinme_1.ctid, joinme_1.f2j + Bloom Filter 2: keys=(joinme_1.f2j) -> Hash - Output: joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid + Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid + Bloom Filter 2 -> Hash Join - Output: joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid - Hash Cond: (joinme.f2j = foo_2.f2) + Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid + Hash Cond: (joinme.f2j = foo_1.f2) -> Seq Scan on pg_temp.joinme Output: joinme.ctid, joinme.other, joinme.f2j - Bloom Filter 2: keys=(joinme.f2j) + Bloom Filter 1: keys=(joinme.f2j) -> Hash - Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid - Bloom Filter 2 - -> Seq Scan on pg_temp.foo foo_2 - Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid - Filter: (foo_2.f3 = 57) -(31 rows) + Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid + Bloom Filter 1 + -> Nested Loop + Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid + Join Filter: (foo_1.f2 = foo_2.f2) + -> Seq Scan on pg_temp.foo foo_2 + Output: foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid + Filter: (foo_2.f3 = 57) + -> Seq Scan on pg_temp.foo foo_1 + Output: foo_1.f2, foo_1.tableoid, foo_1.ctid +(29 rows) UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57 RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3; diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 11cf56fdba4..07854247020 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -3610,17 +3610,16 @@ ANALYZE sb_1, sb_2; -- bucket size is quite big because there are possibly many correlations. EXPLAIN (COSTS OFF) -- Choose merge join SELECT * FROM sb_1 a, sb_2 b WHERE a.x = b.x AND a.y = b.y AND a.z = b.z; - QUERY PLAN -------------------------------------------------------------- - Merge Join - Merge Cond: ((a.z = b.z) AND (a.x = b.x) AND (a.y = b.y)) - -> Sort - Sort Key: a.z, a.x, a.y + QUERY PLAN +------------------------------------------------------------ + Hash Join + Hash Cond: ((b.x = a.x) AND (b.y = a.y) AND (b.z = a.z)) + -> Seq Scan on sb_2 b + Bloom Filter 1: keys=(x, y, z) + -> Hash + Bloom Filter 1 -> Seq Scan on sb_1 a - -> Sort - Sort Key: b.z, b.x, b.y - -> Seq Scan on sb_2 b -(8 rows) +(7 rows) -- The ndistinct extended statistics on (x, y, z) provides more reliable value -- of bucket size. @@ -3633,11 +3632,9 @@ SELECT * FROM sb_1 a, sb_2 b WHERE a.x = b.x AND a.y = b.y AND a.z = b.z; Hash Join Hash Cond: ((a.x = b.x) AND (a.y = b.y) AND (a.z = b.z)) -> Seq Scan on sb_1 a - Bloom Filter 1: keys=(x, y, z) -> Hash - Bloom Filter 1 -> Seq Scan on sb_2 b -(7 rows) +(5 rows) -- Check that the Hash Join bucket size estimator detects equal clauses correctly. SET enable_nestloop = 'off'; diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 5c7d49050db..3519942030b 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -406,11 +406,9 @@ select * from int4_tbl o where exists Hash Semi Join Hash Cond: (o.f1 = i.f1) -> Seq Scan on int4_tbl o - Bloom Filter 1: keys=(f1) -> Hash - Bloom Filter 1 -> Seq Scan on int4_tbl i -(7 rows) +(5 rows) explain (costs off) select * from int4_tbl o where not exists @@ -1673,7 +1671,7 @@ select * from int4_tbl where --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Semi Join Output: int4_tbl.f1 - Join Filter: (CASE WHEN (ANY (int4_tbl.f1 = (hashed SubPlan any_1).col1)) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten) + Join Filter: (b.ten = CASE WHEN (ANY (int4_tbl.f1 = (hashed SubPlan any_1).col1)) THEN int4_tbl.f1 ELSE NULL::integer END) -> Seq Scan on public.int4_tbl Output: int4_tbl.f1 -> Seq Scan on public.tenk1 b @@ -2180,13 +2178,11 @@ order by t1.ten; Hash Cond: (t2.fivethous = t1.unique1) -> Seq Scan on public.tenk1 t2 Output: t2.unique1, t2.unique2, t2.two, t2.four, t2.ten, t2.twenty, t2.hundred, t2.thousand, t2.twothousand, t2.fivethous, t2.tenthous, t2.odd, t2.even, t2.stringu1, t2.stringu2, t2.string4 - Bloom Filter 1: keys=(t2.fivethous) -> Hash Output: t1.ten, t1.unique1 - Bloom Filter 1 -> Seq Scan on public.tenk1 t1 Output: t1.ten, t1.unique1 -(17 rows) +(15 rows) select t1.ten, sum(x) from tenk1 t1 left join lateral ( @@ -2281,19 +2277,15 @@ order by 1, 2; Hash Cond: (t2.q2 = t3.q2) -> Seq Scan on public.int8_tbl t2 Output: t2.q1, t2.q2 - Bloom Filter 1: keys=(t2.q2) - Bloom Filter 2: keys=(t2.q2) -> Hash Output: t3.q2 - Bloom Filter 1 -> Seq Scan on public.int8_tbl t3 Output: t3.q2 -> Hash Output: t1.q1, t1.q2 - Bloom Filter 2 -> Seq Scan on public.int8_tbl t1 Output: t1.q1, t1.q2 -(23 rows) +(19 rows) select t1.q1, x from int8_tbl t1 left join @@ -2336,16 +2328,14 @@ order by 1, 2; Output: t2.q2, ((t2.q1 + 1)) -> Seq Scan on public.int8_tbl t2 Output: t2.q1, t2.q2 - Bloom Filter 1: keys=(t2.q2) -> Seq Scan on public.int8_tbl t3 Output: t3.q2, (t2.q1 + 1) Filter: (t2.q2 = t3.q2) -> Hash Output: t1.q1, t1.q2 - Bloom Filter 1 -> Seq Scan on public.int8_tbl t1 Output: t1.q1, t1.q2 -(19 rows) +(17 rows) select t1.q1, x from int8_tbl t1 left join @@ -2390,19 +2380,15 @@ order by 1, 2; Hash Cond: (t2.q2 = t3.q1) -> Seq Scan on public.int8_tbl t2 Output: t2.q1, t2.q2 - Bloom Filter 1: keys=(t2.q2) - Bloom Filter 2: keys=(t2.q1) -> Hash Output: t3.q1 - Bloom Filter 1 -> Seq Scan on public.int8_tbl t3 Output: t3.q1 -> Hash Output: t1.q1 - Bloom Filter 2 -> Seq Scan on public.int8_tbl t1 Output: t1.q1 -(23 rows) +(19 rows) select t1.q1, x from int8_tbl t1 left join @@ -2455,16 +2441,14 @@ order by 1, 2; Output: t2.q1, (t2.q2) -> Seq Scan on public.int8_tbl t2 Output: t2.q1, t2.q2 - Bloom Filter 1: keys=(t2.q1) -> Seq Scan on public.int8_tbl t3 Output: t3.q1, t2.q2 Filter: (t2.q2 = t3.q1) -> Hash Output: t1.q1 - Bloom Filter 1 -> Seq Scan on public.int8_tbl t1 Output: t1.q1 -(19 rows) +(17 rows) select t1.q1, x from int8_tbl t1 left join @@ -2528,21 +2512,19 @@ order by 1, 2, 3; Hash Cond: (t2.q1 = t3.q2) -> Seq Scan on public.int8_tbl t2 Output: t2.q1, t2.q2 - Bloom Filter 1: keys=(t2.q2) -> Hash Output: t3.q2, (COALESCE(t3.q1, t3.q1)) -> Seq Scan on public.int8_tbl t3 Output: t3.q2, COALESCE(t3.q1, t3.q1) -> Hash Output: t4.q1, t4.q2 - Bloom Filter 1 -> Seq Scan on public.int8_tbl t4 Output: t4.q1, t4.q2 -> Hash Output: t1.q2 -> Seq Scan on public.int8_tbl t1 Output: t1.q2 -(28 rows) +(26 rows) select ss2.* from int8_tbl t1 left join @@ -2610,13 +2592,11 @@ order by 1, 2, 3; Output: t3.q2, COALESCE(t3.q1, t3.q1) -> Seq Scan on public.int8_tbl t4 Output: t4.q1, t4.q2, (COALESCE(t3.q1, t3.q1)) - Bloom Filter 1: keys=(t4.q1) -> Hash Output: t1.q2 - Bloom Filter 1 -> Seq Scan on public.int8_tbl t1 Output: t1.q2 -(26 rows) +(24 rows) select ss2.* from int8_tbl t1 left join @@ -2917,13 +2897,11 @@ select * from tenk1 A where hundred in (select hundred from tenk2 B where B.odd Hash Join Hash Cond: ((a.odd = b.odd) AND (a.hundred = b.hundred)) -> Seq Scan on tenk1 a - Bloom Filter 1: keys=(odd, hundred) -> Hash - Bloom Filter 1 -> HashAggregate Group Key: b.odd, b.hundred -> Seq Scan on tenk2 b -(9 rows) +(7 rows) explain (costs off) select * from tenk1 A where exists @@ -2988,13 +2966,11 @@ ON B.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = b.odd); -> Hash Join Hash Cond: ((b.odd = c.odd) AND (b.hundred = c.hundred)) -> Seq Scan on tenk2 b - Bloom Filter 1: keys=(odd, hundred) -> Hash - Bloom Filter 1 -> HashAggregate Group Key: c.odd, c.hundred -> Seq Scan on tenk2 c -(12 rows) +(10 rows) -- we can pull up the sublink into the inner JoinExpr. explain (costs off) @@ -3009,15 +2985,13 @@ WHERE a.thousand < 750; Hash Cond: (a.hundred = c.hundred) -> Seq Scan on tenk1 a Filter: (thousand < 750) - Bloom Filter 1: keys=(hundred) -> Hash - Bloom Filter 1 -> HashAggregate Group Key: c.odd, c.hundred -> Seq Scan on tenk2 c -> Hash -> Seq Scan on tenk2 b -(14 rows) +(12 rows) -- we can pull up the aggregate sublink into RHS of a left join. explain (costs off) @@ -3154,11 +3128,9 @@ WHERE a.ten IN (VALUES (1), (2)); Hash Cond: (a.ten = c.ten) -> Seq Scan on onek a Filter: (ten = ANY ('{1,2}'::integer[])) - Bloom Filter 1: keys=(ten) -> Hash - Bloom Filter 1 -> Seq Scan on tenk1 c -(8 rows) +(6 rows) EXPLAIN (COSTS OFF) SELECT c.unique1,c.ten FROM tenk1 c JOIN onek a USING (ten) @@ -3169,11 +3141,9 @@ WHERE c.ten IN (VALUES (1), (2)); Hash Cond: (c.ten = a.ten) -> Seq Scan on tenk1 c Filter: (ten = ANY ('{1,2}'::integer[])) - Bloom Filter 1: keys=(ten) -> Hash - Bloom Filter 1 -> Seq Scan on onek a -(8 rows) +(6 rows) -- Constant expressions are simplified EXPLAIN (COSTS OFF) @@ -3494,15 +3464,14 @@ WHERE id NOT IN ( Hash Cond: (not_null_tab.id = t2.id) -> Seq Scan on not_null_tab -> Hash - -> Merge Join - Merge Cond: (t1.id = t2.id) - -> Sort - Sort Key: t1.id - -> Seq Scan on not_null_tab t1 - -> Sort - Sort Key: t2.id + -> Hash Join + Hash Cond: (t1.id = t2.id) + -> Seq Scan on not_null_tab t1 + Bloom Filter 1: keys=(id) + -> Hash + Bloom Filter 1 -> Seq Scan on not_null_tab t2 -(12 rows) +(11 rows) -- ANTI JOIN: outer side is defined NOT NULL, inner side is forced nonnullable -- by qual clause @@ -3543,11 +3512,8 @@ WHERE id NOT IN ( ); QUERY PLAN ------------------------------------------------- - Merge Anti Join - Merge Cond: (not_null_tab.id = t1.id) - -> Sort - Sort Key: not_null_tab.id - -> Seq Scan on not_null_tab + Merge Right Anti Join + Merge Cond: (t1.id = not_null_tab.id) -> Nested Loop Left Join -> Merge Join Merge Cond: (t1.id = t2.id) @@ -3559,6 +3525,9 @@ WHERE id NOT IN ( -> Seq Scan on null_tab t2 -> Materialize -> Seq Scan on null_tab t3 + -> Sort + Sort Key: not_null_tab.id + -> Seq Scan on not_null_tab (16 rows) -- ANTI JOIN: outer side is defined NOT NULL and is not nulled by outer join, diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 7d4af80faf6..13025cf93c5 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -623,13 +623,11 @@ MERGE INTO rw_view1 t Hash Cond: (base_tbl.a = generate_series.generate_series) -> Bitmap Heap Scan on base_tbl Recheck Cond: (a > 0) - Bloom Filter 1: keys=(a) -> Bitmap Index Scan on base_tbl_pkey Index Cond: (a > 0) -> Hash - Bloom Filter 1 -> Function Scan on generate_series -(11 rows) +(9 rows) -- it's still updatable if we add a DO ALSO rule CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text); @@ -3528,18 +3526,17 @@ EXPLAIN (COSTS OFF) UPDATE v2 SET a = 1; Update on t1 InitPlan exists_1 -> Result - -> Merge Join - Merge Cond: (t1.a = v1.a) - -> Sort - Sort Key: t1.a - -> Seq Scan on t1 - -> Sort - Sort Key: v1.a + -> Hash Join + Hash Cond: (t1.a = v1.a) + -> Seq Scan on t1 + Bloom Filter 1: keys=(a) + -> Hash + Bloom Filter 1 -> Subquery Scan on v1 -> Result One-Time Filter: (InitPlan exists_1).col1 -> Seq Scan on t1 t1_1 -(14 rows) +(13 rows) DROP VIEW v2; DROP VIEW v1; diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 5c86619f023..dfda848b13c 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -4324,15 +4324,14 @@ WHERE s.c = 1; Run Condition: (ntile(e2.salary) OVER w1 <= 1) -> Sort Sort Key: e1.depname - -> Merge Join - Merge Cond: (e1.empno = e2.empno) - -> Sort - Sort Key: e1.empno - -> Seq Scan on empsalary e1 - -> Sort - Sort Key: e2.empno + -> Hash Join + Hash Cond: (e1.empno = e2.empno) + -> Seq Scan on empsalary e1 + Bloom Filter 1: keys=(empno) + -> Hash + Bloom Filter 1 -> Seq Scan on empsalary e2 -(15 rows) +(14 rows) -- Ensure the run condition optimization is used in cases where the WindowFunc -- has a Var from another query level diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index db8c77721e7..25262b08839 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -686,11 +686,9 @@ select count(*) from tenk1 a -> Hash Semi Join Hash Cond: (a.unique1 = x.unique1) -> Index Only Scan using tenk1_unique1 on tenk1 a - Bloom Filter 1: keys=(unique1) -> Hash - Bloom Filter 1 -> CTE Scan on x -(10 rows) +(8 rows) explain (costs off) with x as materialized (insert into tenk1 default values returning unique1) @@ -753,22 +751,20 @@ select * from search_graph order by seq; -> Recursive Union -> Seq Scan on pg_temp.graph0 g Output: g.f, g.t, g.label, ARRAY[ROW(g.f, g.t)] - -> Merge Join + -> Hash Join Output: g_1.f, g_1.t, g_1.label, array_cat(sg.seq, ARRAY[ROW(g_1.f, g_1.t)]) - Merge Cond: (g_1.f = sg.t) - -> Sort + Hash Cond: (g_1.f = sg.t) + -> Seq Scan on pg_temp.graph0 g_1 Output: g_1.f, g_1.t, g_1.label - Sort Key: g_1.f - -> Seq Scan on pg_temp.graph0 g_1 - Output: g_1.f, g_1.t, g_1.label - -> Sort + Bloom Filter 1: keys=(g_1.f) + -> Hash Output: sg.seq, sg.t - Sort Key: sg.t + Bloom Filter 1 -> WorkTable Scan on search_graph sg Output: sg.seq, sg.t -> CTE Scan on search_graph Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq -(22 rows) +(20 rows) with recursive search_graph(f, t, label) as ( select * from graph0 g @@ -826,22 +822,20 @@ select * from search_graph order by seq; -> Recursive Union -> Seq Scan on pg_temp.graph0 g Output: g.f, g.t, g.label, ROW('0'::bigint, g.f, g.t) - -> Merge Join + -> Hash Join Output: g_1.f, g_1.t, g_1.label, ROW(int8inc((sg.seq)."*DEPTH*"), g_1.f, g_1.t) - Merge Cond: (g_1.f = sg.t) - -> Sort + Hash Cond: (g_1.f = sg.t) + -> Seq Scan on pg_temp.graph0 g_1 Output: g_1.f, g_1.t, g_1.label - Sort Key: g_1.f - -> Seq Scan on pg_temp.graph0 g_1 - Output: g_1.f, g_1.t, g_1.label - -> Sort + Bloom Filter 1: keys=(g_1.f) + -> Hash Output: sg.seq, sg.t - Sort Key: sg.t + Bloom Filter 1 -> WorkTable Scan on search_graph sg Output: sg.seq, sg.t -> CTE Scan on search_graph Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq -(22 rows) +(20 rows) with recursive search_graph(f, t, label) as ( select * from graph0 g @@ -1097,20 +1091,20 @@ select * from search_graph; 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} @@ -1135,20 +1129,20 @@ select * from search_graph; 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} @@ -1210,21 +1204,19 @@ select * from search_graph; -> Recursive Union -> Seq Scan on pg_temp.graph g Output: g.f, g.t, g.label, false, ARRAY[ROW(g.f, g.t)] - -> Merge Join + -> Hash Join Output: g_1.f, g_1.t, g_1.label, CASE WHEN (ROW(g_1.f, g_1.t) = ANY (sg.path)) THEN true ELSE false END, array_cat(sg.path, ARRAY[ROW(g_1.f, g_1.t)]) - Merge Cond: (g_1.f = sg.t) - -> Sort + Hash Cond: (g_1.f = sg.t) + -> Seq Scan on pg_temp.graph g_1 Output: g_1.f, g_1.t, g_1.label - Sort Key: g_1.f - -> Seq Scan on pg_temp.graph g_1 - Output: g_1.f, g_1.t, g_1.label - -> Sort + Bloom Filter 1: keys=(g_1.f) + -> Hash Output: sg.path, sg.t - Sort Key: sg.t + Bloom Filter 1 -> WorkTable Scan on search_graph sg Output: sg.path, sg.t Filter: (NOT sg.is_cycle) -(20 rows) +(18 rows) with recursive search_graph(f, t, label) as ( select * from graph g @@ -1244,20 +1236,20 @@ select * from search_graph; 5 | 1 | arc 5 -> 1 | f | {"(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} 2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} @@ -1281,20 +1273,20 @@ select * from search_graph; 5 | 1 | arc 5 -> 1 | N | {"(5,1)"} 1 | 2 | arc 1 -> 2 | N | {"(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | N | {"(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | N | {"(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | N | {"(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | N | {"(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | N | {"(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | N | {"(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | N | {"(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | N | {"(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | N | {"(1,4)","(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | N | {"(1,4)","(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | N | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | Y | {"(4,5)","(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | Y | {"(5,1)","(1,4)","(4,5)","(5,1)"} 2 | 3 | arc 2 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} @@ -1446,20 +1438,20 @@ select * from search_graph; 5 | 1 | arc 5 -> 1 | {"(5,1)"} | f | {"(5,1)"} 1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f | {"(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f | {"(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f | {"(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f | {"(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f | {"(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f | {"(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f | {"(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f | {"(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f | {"(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f | {"(1,4)","(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} 2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} @@ -1484,20 +1476,20 @@ select * from search_graph; 5 | 1 | arc 5 -> 1 | (0,5,1) | f | {"(5,1)"} 1 | 2 | arc 1 -> 2 | (1,1,2) | f | {"(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | (1,1,3) | f | {"(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | (1,2,3) | f | {"(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | (1,4,5) | f | {"(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | (1,5,1) | f | {"(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | (2,1,2) | f | {"(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | (2,1,3) | f | {"(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | (2,2,3) | f | {"(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | (2,4,5) | f | {"(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | (2,5,1) | f | {"(1,4)","(4,5)","(5,1)"} 1 | 2 | arc 1 -> 2 | (3,1,2) | f | {"(1,4)","(4,5)","(5,1)","(1,2)"} 1 | 3 | arc 1 -> 3 | (3,1,3) | f | {"(1,4)","(4,5)","(5,1)","(1,3)"} - 1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 2 | 3 | arc 2 -> 3 | (3,2,3) | f | {"(4,5)","(5,1)","(1,2)","(2,3)"} + 1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"} 4 | 5 | arc 4 -> 5 | (3,4,5) | t | {"(4,5)","(5,1)","(1,4)","(4,5)"} 5 | 1 | arc 5 -> 1 | (3,5,1) | t | {"(5,1)","(1,4)","(4,5)","(5,1)"} 2 | 3 | arc 2 -> 3 | (4,2,3) | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} @@ -1677,20 +1669,20 @@ select * from v_cycle1; 5 | 1 | arc 5 -> 1 1 | 2 | arc 1 -> 2 1 | 3 | arc 1 -> 3 - 1 | 4 | arc 1 -> 4 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 4 | 5 | arc 4 -> 5 5 | 1 | arc 5 -> 1 1 | 2 | arc 1 -> 2 1 | 3 | arc 1 -> 3 - 1 | 4 | arc 1 -> 4 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 4 | 5 | arc 4 -> 5 5 | 1 | arc 5 -> 1 1 | 2 | arc 1 -> 2 1 | 3 | arc 1 -> 3 - 1 | 4 | arc 1 -> 4 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 4 | 5 | arc 4 -> 5 5 | 1 | arc 5 -> 1 2 | 3 | arc 2 -> 3 @@ -1707,20 +1699,20 @@ select * from v_cycle2; 5 | 1 | arc 5 -> 1 1 | 2 | arc 1 -> 2 1 | 3 | arc 1 -> 3 - 1 | 4 | arc 1 -> 4 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 4 | 5 | arc 4 -> 5 5 | 1 | arc 5 -> 1 1 | 2 | arc 1 -> 2 1 | 3 | arc 1 -> 3 - 1 | 4 | arc 1 -> 4 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 4 | 5 | arc 4 -> 5 5 | 1 | arc 5 -> 1 1 | 2 | arc 1 -> 2 1 | 3 | arc 1 -> 3 - 1 | 4 | arc 1 -> 4 2 | 3 | arc 2 -> 3 + 1 | 4 | arc 1 -> 4 4 | 5 | arc 4 -> 5 5 | 1 | arc 5 -> 1 2 | 3 | arc 2 -> 3 @@ -3248,10 +3240,8 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); Hash Cond: (m.k = o.k) -> Seq Scan on public.m Output: m.ctid, m.k - Bloom Filter 1: keys=(m.k) -> Hash Output: o.k, o.v, o.* - Bloom Filter 1 -> Subquery Scan on o Output: o.k, o.v, o.* -> Result @@ -3262,7 +3252,7 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); -> CTE Scan on cte_basic Output: (cte_basic.b || ' merge update'::text) Filter: (cte_basic.a = m.k) -(23 rows) +(21 rows) -- InitPlan WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b) @@ -3299,15 +3289,13 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v); Hash Cond: (m.k = o.k) -> Seq Scan on public.m Output: m.ctid, m.k - Bloom Filter 1: keys=(m.k) -> Hash Output: o.k, o.v, o.* - Bloom Filter 1 -> Subquery Scan on o Output: o.k, o.v, o.* -> Result Output: 1, 'merge source InitPlan'::text -(23 rows) +(21 rows) -- MERGE source comes from CTE: WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b) @@ -3345,13 +3333,11 @@ WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text Hash Cond: (m.k = merge_source_cte.a) -> Seq Scan on public.m Output: m.ctid, m.k - Bloom Filter 1: keys=(m.k) -> Hash Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.* - Bloom Filter 1 -> CTE Scan on merge_source_cte Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.* -(22 rows) +(20 rows) DROP TABLE m; -- check that run to completion happens in proper ordering -- 2.50.1 (Apple Git-155)