diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 49fc43bf167..bfa0c650687 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -988,13 +988,31 @@ generate_base_implied_equalities_broken(PlannerInfo *root, * * join_relids should always equal bms_union(outer_relids, inner_rel->relids). * We could simplify this function's API by computing it internally, but in - * all current uses, the caller has the value at hand anyway. + * most current uses, the caller has the value at hand anyway. */ List * generate_join_implied_equalities(PlannerInfo *root, Relids join_relids, Relids outer_relids, RelOptInfo *inner_rel) +{ + return generate_join_implied_equalities_for_ecs(root, + root->eq_classes, + join_relids, + outer_relids, + inner_rel); +} + +/* + * generate_join_implied_equalities_for_ecs + * As above, but consider only the listed ECs. + */ +List * +generate_join_implied_equalities_for_ecs(PlannerInfo *root, + List *eclasses, + Relids join_relids, + Relids outer_relids, + RelOptInfo *inner_rel) { List *result = NIL; Relids inner_relids = inner_rel->relids; @@ -1016,7 +1034,7 @@ generate_join_implied_equalities(PlannerInfo *root, nominal_join_relids = join_relids; } - foreach(lc, root->eq_classes) + foreach(lc, eclasses) { EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc); List *sublist = NIL; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 187437b6941..1e87a7302ab 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1100,6 +1100,7 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, Relids inner_and_req; List *pclauses; List *eclauses; + List *dropped_ecs; double rows; ListCell *lc; @@ -1153,6 +1154,7 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, required_outer, joinrel); /* We only want ones that aren't movable to lower levels */ + dropped_ecs = NIL; foreach(lc, eclauses) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); @@ -1169,13 +1171,79 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, joinrel->relids, join_and_req)); #endif - if (!join_clause_is_movable_into(rinfo, - outer_path->parent->relids, - outer_and_req) && - !join_clause_is_movable_into(rinfo, - inner_path->parent->relids, - inner_and_req)) - pclauses = lappend(pclauses, rinfo); + if (join_clause_is_movable_into(rinfo, + outer_path->parent->relids, + outer_and_req)) + continue; /* drop if movable into LHS */ + if (join_clause_is_movable_into(rinfo, + inner_path->parent->relids, + inner_and_req)) + { + /* drop if movable into RHS, but remember EC for use below */ + Assert(rinfo->left_ec == rinfo->right_ec); + dropped_ecs = lappend(dropped_ecs, rinfo->left_ec); + continue; + } + pclauses = lappend(pclauses, rinfo); + } + + /* + * EquivalenceClasses are harder to deal with than we could wish, because + * of the fact that a given EC can generate different clauses depending on + * context. Suppose we have an EC {X.X, Y.Y, Z.Z} where X and Y are the + * LHS and RHS of the current join and Z is in required_outer, and further + * suppose that the inner_path is parameterized by both X and Z. The code + * above will have produced either Z.Z = X.X or Z.Z = Y.Y from that EC, + * and in the latter case will have discarded it as being movable into the + * RHS. However, the EC machinery might have produced either Y.Y = X.X or + * Y.Y = Z.Z as the EC enforcement clause within the inner_path; it will + * not have produced both, and we can't readily tell from here which one + * it did pick. If we add no clause to this join, we'll end up with + * insufficient enforcement of the EC; either Z.Z or X.X will fail to be + * constrained to be equal to the other members of the EC. (When we come + * to join Z to this X/Y path, we will certainly drop whichever EC clause + * is generated at that join, so this omission won't get fixed later.) + * + * To handle this, for each EC we discarded such a clause from, try to + * generate a clause connecting the required_outer rels to the join's LHS + * ("Z.Z = X.X" in the terms of the above example). If successful, and if + * the clause can't be moved to the LHS, add it to the current join's + * restriction clauses. (If an EC cannot generate such a clause then it + * has nothing that needs to be enforced here, while if the clause can be + * moved into the LHS then it should have been enforced within that path.) + * + * Note that we don't need similar processing for ECs whose clause was + * considered to be movable into the LHS, because the LHS can't refer to + * the RHS so there is no comparable ambiguity about what it might + * actually be enforcing internally. + */ + if (dropped_ecs) + { + Relids real_outer_and_req; + + real_outer_and_req = bms_union(outer_path->parent->relids, + required_outer); + eclauses = + generate_join_implied_equalities_for_ecs(root, + dropped_ecs, + real_outer_and_req, + required_outer, + outer_path->parent); + foreach(lc, eclauses) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + /* As above, can't quite assert this here */ +#ifdef NOT_USED + Assert(join_clause_is_movable_into(rinfo, + outer_path->parent->relids, + real_outer_and_req)); +#endif + if (!join_clause_is_movable_into(rinfo, + outer_path->parent->relids, + outer_and_req)) + pclauses = lappend(pclauses, rinfo); + } } /* diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index d9a3c1f2326..e0ac4515c95 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -135,6 +135,11 @@ extern List *generate_join_implied_equalities(PlannerInfo *root, Relids join_relids, Relids outer_relids, RelOptInfo *inner_rel); +extern List *generate_join_implied_equalities_for_ecs(PlannerInfo *root, + List *eclasses, + Relids join_relids, + Relids outer_relids, + RelOptInfo *inner_rel); extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2); extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index c8f864bfab2..d9bbae097b7 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -2236,6 +2236,38 @@ where t4.thousand = t5.unique1 and ss.x1 = t4.tenthous and ss.x2 = t5.stringu1; 1000 (1 row) +-- +-- regression test: check a case where we formerly missed including an EC +-- enforcement clause because it was expected to be handled at scan level +-- +explain (costs off) +select a.f1, b.f1, t.thousand, t.tenthous from + tenk1 t, + (select sum(f1)+1 as f1 from int4_tbl i4a) a, + (select sum(f1) as f1 from int4_tbl i4b) b +where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Nested Loop + -> Aggregate + -> Seq Scan on int4_tbl i4b + -> Nested Loop + Join Filter: ((sum(i4b.f1)) = ((sum(i4a.f1) + 1))) + -> Aggregate + -> Seq Scan on int4_tbl i4a + -> Index Only Scan using tenk1_thous_tenthous on tenk1 t + Index Cond: ((thousand = (sum(i4b.f1))) AND (tenthous = ((((sum(i4a.f1) + 1)) + (sum(i4b.f1))) + 999))) +(9 rows) + +select a.f1, b.f1, t.thousand, t.tenthous from + tenk1 t, + (select sum(f1)+1 as f1 from int4_tbl i4a) a, + (select sum(f1) as f1 from int4_tbl i4b) b +where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous; + f1 | f1 | thousand | tenthous +----+----+----------+---------- +(0 rows) + -- -- Clean up -- diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 17293faa127..97bccec721a 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -391,6 +391,23 @@ from tenk1 t5 where t4.thousand = t5.unique1 and ss.x1 = t4.tenthous and ss.x2 = t5.stringu1; +-- +-- regression test: check a case where we formerly missed including an EC +-- enforcement clause because it was expected to be handled at scan level +-- +explain (costs off) +select a.f1, b.f1, t.thousand, t.tenthous from + tenk1 t, + (select sum(f1)+1 as f1 from int4_tbl i4a) a, + (select sum(f1) as f1 from int4_tbl i4b) b +where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous; + +select a.f1, b.f1, t.thousand, t.tenthous from + tenk1 t, + (select sum(f1)+1 as f1 from int4_tbl i4a) a, + (select sum(f1) as f1 from int4_tbl i4b) b +where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous; + -- -- Clean up