diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 51d806326eb..d871396e20c 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2443,15 +2443,17 @@ find_join_domain(PlannerInfo *root, Relids relids) * Detect whether two expressions are known equal due to equivalence * relationships. * - * Actually, this only shows that the expressions are equal according - * to some opfamily's notion of equality --- but we only use it for - * selectivity estimation, so a fuzzy idea of equality is OK. + * If opfamily is given, the expressions must be known equal per the semantics + * of that opfamily (note it has to be a btree opfamily, since those are the + * only opfamilies equivclass.c deals with). If opfamily is InvalidOid, we'll + * return true if they're equal according to any opfamily, which is fuzzy but + * OK for estimation purposes. * * Note: does not bother to check for "equal(item1, item2)"; caller must * check that case if it's possible to pass identical items. */ bool -exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2) +exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2, Oid opfamily) { ListCell *lc1; @@ -2466,6 +2468,17 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2) if (ec->ec_has_volatile) continue; + /* + * It's okay to consider ec_broken ECs here. Brokenness just means we + * couldn't derive all the implied clauses we'd have liked to; it does + * not invalidate our knowledge that the members are equal. + */ + + /* Ignore if this EC doesn't use specified opfamily */ + if (OidIsValid(opfamily) && + !list_member_oid(ec->ec_opfamilies, opfamily)) + continue; + foreach(lc2, ec->ec_members) { EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2); @@ -2494,8 +2507,7 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2) * (In principle there might be more than one matching eclass if multiple * collations are involved, but since collation doesn't matter for equality, * we ignore that fine point here.) This is much like exprs_known_equal, - * except that we insist on the comparison operator matching the eclass, so - * that the result is definite not approximate. + * except for the format of the input. * * On success, we also set fkinfo->eclass[colno] to the matching eclass, * and set fkinfo->fk_eclass_member[colno] to the eclass member for the @@ -2536,7 +2548,7 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, /* Never match to a volatile EC */ if (ec->ec_has_volatile) continue; - /* Note: it seems okay to match to "broken" eclasses here */ + /* It's okay to consider "broken" ECs here, see exprs_known_equal */ foreach(lc2, ec->ec_members) { diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 971d1c7aae5..d7266e4cdba 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -2080,10 +2080,9 @@ have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, List *restrictlist) { PartitionScheme part_scheme = rel1->part_scheme; + bool pk_known_equal[PARTITION_MAX_KEYS]; + int num_equal_pks; ListCell *lc; - int cnt_pks; - bool pk_has_clause[PARTITION_MAX_KEYS]; - bool strict_op; /* * This function must only be called when the joined relations have same @@ -2092,13 +2091,19 @@ have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel, Assert(rel1->part_scheme == rel2->part_scheme); Assert(part_scheme); - memset(pk_has_clause, 0, sizeof(pk_has_clause)); + /* We use a bool array to track which partkey columns are known equal */ + memset(pk_known_equal, 0, sizeof(pk_known_equal)); + /* ... as well as a count of how many are known equal */ + num_equal_pks = 0; + + /* First, look through the join's restriction clauses */ foreach(lc, restrictlist) { RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); OpExpr *opexpr; Expr *expr1; Expr *expr2; + bool strict_op; int ipk1; int ipk2; @@ -2176,11 +2181,15 @@ have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel, if (ipk1 != ipk2) continue; + /* Ignore clause if we already proved these keys equal. */ + if (pk_known_equal[ipk1]) + continue; + /* * The clause allows partitionwise join only if it uses the same * operator family as that specified by the partition key. */ - if (rel1->part_scheme->strategy == PARTITION_STRATEGY_HASH) + if (part_scheme->strategy == PARTITION_STRATEGY_HASH) { if (!OidIsValid(rinfo->hashjoinoperator) || !op_in_opfamily(rinfo->hashjoinoperator, @@ -2192,17 +2201,89 @@ have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel, continue; /* Mark the partition key as having an equi-join clause. */ - pk_has_clause[ipk1] = true; + pk_known_equal[ipk1] = true; + + /* We can stop examining clauses once we prove all keys equal. */ + if (++num_equal_pks == part_scheme->partnatts) + return true; } - /* Check whether every partition key has an equi-join condition. */ - for (cnt_pks = 0; cnt_pks < part_scheme->partnatts; cnt_pks++) + /* + * Also check to see if any keys are known equal by equivclass.c. In most + * cases there would have been a join restriction clause generated from + * any EC that had such knowledge, but there might be no such clause, or + * it might happen to constrain other members of the ECs than the ones we + * are looking for. + */ + for (int ipk = 0; ipk < part_scheme->partnatts; ipk++) { - if (!pk_has_clause[cnt_pks]) - return false; + Oid btree_opfamily; + + /* Ignore if we already proved these keys equal. */ + if (pk_known_equal[ipk]) + continue; + + /* + * We need a btree opfamily to ask equivclass.c about. If the + * partopfamily is a hash opfamily, look up its equality operator, and + * select some btree opfamily that that operator is part of. (Any + * such opfamily should be good enough, since equivclass.c will track + * multiple opfamilies as appropriate.) + */ + if (part_scheme->strategy == PARTITION_STRATEGY_HASH) + { + Oid eq_op; + List *eq_opfamilies; + + eq_op = get_opfamily_member(part_scheme->partopfamily[ipk], + part_scheme->partopcintype[ipk], + part_scheme->partopcintype[ipk], + HTEqualStrategyNumber); + if (!OidIsValid(eq_op)) + break; /* we're not going to succeed */ + eq_opfamilies = get_mergejoin_opfamilies(eq_op); + if (eq_opfamilies == NIL) + break; /* we're not going to succeed */ + btree_opfamily = linitial_oid(eq_opfamilies); + } + else + btree_opfamily = part_scheme->partopfamily[ipk]; + + /* + * We consider only non-nullable partition keys here; nullable ones + * would not be treated as part of the same equivalence classes as + * non-nullable ones. + */ + foreach(lc, rel1->partexprs[ipk]) + { + Node *expr1 = (Node *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, rel2->partexprs[ipk]) + { + Node *expr2 = (Node *) lfirst(lc2); + + if (exprs_known_equal(root, expr1, expr2, btree_opfamily)) + { + pk_known_equal[ipk] = true; + break; + } + } + if (pk_known_equal[ipk]) + break; + } + + if (pk_known_equal[ipk]) + { + /* We can stop examining keys once we prove all keys equal. */ + if (++num_equal_pks == part_scheme->partnatts) + return true; + } + else + break; /* no chance to succeed, give up */ } - return true; + return false; } /* diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 5f5d7959d8e..877a62a62ec 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -3313,10 +3313,11 @@ add_unique_group_var(PlannerInfo *root, List *varinfos, /* * Drop known-equal vars, but only if they belong to different - * relations (see comments for estimate_num_groups) + * relations (see comments for estimate_num_groups). We aren't too + * fussy about the semantics of "equal" here. */ if (vardata->rel != varinfo->rel && - exprs_known_equal(root, var, varinfo->var)) + exprs_known_equal(root, var, varinfo->var, InvalidOid)) { if (varinfo->ndistinct <= ndistinct) { diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 5c029b6b620..970499c4691 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -158,7 +158,8 @@ extern List *generate_join_implied_equalities_for_ecs(PlannerInfo *root, Relids join_relids, Relids outer_relids, RelOptInfo *inner_rel); -extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2); +extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2, + Oid opfamily); extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, ForeignKeyOptInfo *fkinfo, int colno); diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 53591a4f2d5..108f9ecb445 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -62,6 +62,45 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 450 | 0450 | 450 | 0450 (4 rows) +-- inner join with partially-redundant join clauses +EXPLAIN (COSTS OFF) +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; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Merge Join + Merge Cond: (t1_1.a = t2_1.a) + -> Index Scan using iprt1_p1_a on prt1_p1 t1_1 + -> Sort + Sort Key: t2_1.b + -> 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 + -> Hash + -> 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 + -> Hash + -> Seq Scan on prt2_p3 t2_3 + Filter: (a = b) +(22 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 +----+------+----+------ + 0 | 0000 | 0 | 0000 + 6 | 0006 | 6 | 0006 + 12 | 0012 | 12 | 0012 + 18 | 0018 | 18 | 0018 + 24 | 0024 | 24 | 0024 +(5 rows) + -- left outer join, 3-way EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM prt1 t1 @@ -1803,6 +1842,55 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1 450 | 0002 | 450 | 0002 (4 rows) +-- inner join with partially-redundant join clauses +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.a AND t1.a = t2.b AND t1.c = t2.c ORDER BY t1.a, t2.b; + QUERY PLAN +------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t1_1.a = t2_1.a) AND ((t1_1.c)::text = (t2_1.c)::text)) + -> Seq Scan on prt1_l_p1 t1_1 + -> Hash + -> Seq Scan on prt2_l_p1 t2_1 + Filter: (a = b) + -> Hash Join + Hash Cond: ((t1_2.a = t2_2.a) AND ((t1_2.c)::text = (t2_2.c)::text)) + -> Seq Scan on prt1_l_p2_p1 t1_2 + -> Hash + -> Seq Scan on prt2_l_p2_p1 t2_2 + Filter: (a = b) + -> Hash Join + Hash Cond: ((t1_3.a = t2_3.a) AND ((t1_3.c)::text = (t2_3.c)::text)) + -> Seq Scan on prt1_l_p2_p2 t1_3 + -> Hash + -> Seq Scan on prt2_l_p2_p2 t2_3 + Filter: (a = b) + -> Hash Join + Hash Cond: ((t1_5.a = t2_5.a) AND ((t1_5.c)::text = (t2_5.c)::text)) + -> Append + -> Seq Scan on prt1_l_p3_p1 t1_5 + -> Seq Scan on prt1_l_p3_p2 t1_6 + -> Hash + -> Append + -> Seq Scan on prt2_l_p3_p1 t2_5 + Filter: (a = b) + -> Seq Scan on prt2_l_p3_p2 t2_6 + Filter: (a = b) +(32 rows) + +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.a AND t1.a = t2.b AND t1.c = t2.c ORDER BY t1.a, t2.b; + a | c | b | c +----+------+----+------ + 0 | 0000 | 0 | 0000 + 6 | 0002 | 6 | 0002 + 12 | 0000 | 12 | 0000 + 18 | 0002 | 18 | 0002 + 24 | 0000 | 24 | 0000 +(5 rows) + -- left join EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b; diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index 128ce8376e6..e84b65f4442 100644 --- a/src/test/regress/sql/partition_join.sql +++ b/src/test/regress/sql/partition_join.sql @@ -34,6 +34,11 @@ EXPLAIN (COSTS OFF) 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; 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; +-- inner join with partially-redundant join clauses +EXPLAIN (COSTS OFF) +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; +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; + -- left outer join, 3-way EXPLAIN (COSTS OFF) SELECT COUNT(*) FROM prt1 t1 @@ -386,6 +391,11 @@ EXPLAIN (COSTS OFF) 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; 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; +-- inner join with partially-redundant join clauses +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.a AND t1.a = t2.b AND t1.c = t2.c ORDER BY t1.a, t2.b; +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.a AND t1.a = t2.b AND t1.c = t2.c ORDER BY t1.a, t2.b; + -- left join EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b;