From 1cced8ea42e7750f6b619ec93bdbf53830762926 Mon Sep 17 00:00:00 2001 From: Yuya Watari Date: Fri, 25 Aug 2023 10:43:36 +0900 Subject: [PATCH 2/6] Speed up searches for child EquivalenceMembers Traditionally, child EquivalenceMembers were in the EquivalenceClass->ec_members. When we wanted to find some child members matching a request, we had to perform a linear search. This search became heavy when tables had many partitions, leading to much planning time. After this commit, child EquivalenceMembers no longer exist in ec_members. Instead, RelOptInfos have them. This change demonstrates a significant performance improvement in planning time. --- contrib/postgres_fdw/postgres_fdw.c | 15 +- src/backend/optimizer/path/equivclass.c | 435 +++++++++++++++++++----- src/backend/optimizer/path/indxpath.c | 15 +- src/backend/optimizer/path/pathkeys.c | 9 +- src/backend/optimizer/util/relnode.c | 33 +- src/include/nodes/pathnodes.h | 79 +++++ src/include/optimizer/paths.h | 6 + src/tools/pgindent/typedefs.list | 2 + 8 files changed, 493 insertions(+), 101 deletions(-) diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index ac14c06c715..f959c5ff893 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -7847,14 +7847,13 @@ conversion_error_callback(void *arg) EquivalenceMember * find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) { - ListCell *lc; - PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private; + EquivalenceMemberIterator it; + EquivalenceMember *em; - foreach(lc, ec->ec_members) + setup_eclass_member_iterator_with_children(&it, root, ec, rel->relids); + while ((em = eclass_member_iterator_next(&it)) != NULL) { - EquivalenceMember *em = (EquivalenceMember *) lfirst(lc); - /* * Note we require !bms_is_empty, else we'd accept constant * expressions which are not suitable for the purpose. @@ -7865,6 +7864,7 @@ find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) is_foreign_expr(root, rel, em->em_expr)) return em; } + dispose_eclass_member_iterator(&it); return NULL; } @@ -7918,9 +7918,8 @@ find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec, if (em->em_is_const) continue; - /* Ignore child members */ - if (em->em_is_child) - continue; + /* Child members should not exist in ec_members */ + Assert(!em->em_is_child); /* Match if same expression (after stripping relabel) */ em_expr = em->em_expr; diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 7fe7cdff468..8cce165e95d 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -33,10 +33,14 @@ #include "utils/lsyscache.h" +static EquivalenceMember *make_eq_member(EquivalenceClass *ec, + Expr *expr, Relids relids, + JoinDomain *jdomain, + EquivalenceMember *parent, + Oid datatype); static EquivalenceMember *add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, JoinDomain *jdomain, - EquivalenceMember *parent, Oid datatype); static void generate_base_implied_equalities_const(PlannerInfo *root, EquivalenceClass *ec); @@ -68,6 +72,10 @@ static bool reconsider_outer_join_clause(PlannerInfo *root, static bool reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo); static JoinDomain *find_join_domain(PlannerInfo *root, Relids relids); +static void add_eclass_child_members_to_iterator(EquivalenceMemberIterator *it, + PlannerInfo *root, + EquivalenceClass *ec, + RelOptInfo *child_rel); static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root, Relids relids); static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1, @@ -268,7 +276,8 @@ process_equivalence(PlannerInfo *root, { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); - Assert(!cur_em->em_is_child); /* no children yet */ + /* Child members should not exist in ec_members */ + Assert(!cur_em->em_is_child); /* * Match constants only within the same JoinDomain (see @@ -373,7 +382,7 @@ process_equivalence(PlannerInfo *root, { /* Case 3: add item2 to ec1 */ em2 = add_eq_member(ec1, item2, item2_relids, - jdomain, NULL, item2_type); + jdomain, item2_type); ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_min_security = Min(ec1->ec_min_security, restrictinfo->security_level); @@ -390,7 +399,7 @@ process_equivalence(PlannerInfo *root, { /* Case 3: add item1 to ec2 */ em1 = add_eq_member(ec2, item1, item1_relids, - jdomain, NULL, item1_type); + jdomain, item1_type); ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo); ec2->ec_min_security = Min(ec2->ec_min_security, restrictinfo->security_level); @@ -422,9 +431,9 @@ process_equivalence(PlannerInfo *root, ec->ec_max_security = restrictinfo->security_level; ec->ec_merged = NULL; em1 = add_eq_member(ec, item1, item1_relids, - jdomain, NULL, item1_type); + jdomain, item1_type); em2 = add_eq_member(ec, item2, item2_relids, - jdomain, NULL, item2_type); + jdomain, item2_type); root->eq_classes = lappend(root->eq_classes, ec); @@ -510,11 +519,16 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation) } /* - * add_eq_member - build a new EquivalenceMember and add it to an EC + * make_eq_member + * + * Build a new EquivalenceMember without adding it to an EC. If 'parent' + * parameter is NULL, the result will be a parent member, otherwise a child + * member. Note that child EquivalenceMembers should not be added to its + * parent EquivalenceClass. */ static EquivalenceMember * -add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, - JoinDomain *jdomain, EquivalenceMember *parent, Oid datatype) +make_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, + JoinDomain *jdomain, EquivalenceMember *parent, Oid datatype) { EquivalenceMember *em = makeNode(EquivalenceMember); @@ -525,6 +539,7 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, em->em_datatype = datatype; em->em_jdomain = jdomain; em->em_parent = parent; + em->em_ec = ec; if (bms_is_empty(relids)) { @@ -545,11 +560,30 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, { ec->ec_relids = bms_add_members(ec->ec_relids, relids); } - ec->ec_members = lappend(ec->ec_members, em); return em; } +/* + * add_eq_member - build a new parent EquivalenceMember and add it to an EC + * + * Note: We don't have a function to add a child member like + * add_child_eq_member() because how to do it depends on the relations they + * are translated from. See add_child_rel_equivalences(), + * add_child_join_rel_equivalences() and add_setop_child_rel_equivalences() + * to see how to add child members. + */ +static EquivalenceMember * +add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, + JoinDomain *jdomain, Oid datatype) +{ + EquivalenceMember *em = make_eq_member(ec, expr, relids, jdomain, + NULL, datatype); + + ec->ec_members = lappend(ec->ec_members, em); + return em; +} + /* * get_eclass_for_sort_expr @@ -616,7 +650,8 @@ get_eclass_for_sort_expr(PlannerInfo *root, foreach(lc1, root->eq_classes) { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); - ListCell *lc2; + EquivalenceMemberIterator it; + EquivalenceMember *cur_em; /* * Never match to a volatile EC, except when we are looking at another @@ -631,10 +666,9 @@ get_eclass_for_sort_expr(PlannerInfo *root, if (!equal(opfamilies, cur_ec->ec_opfamilies)) continue; - foreach(lc2, cur_ec->ec_members) + setup_eclass_member_iterator_with_children(&it, root, cur_ec, rel); + while ((cur_em = eclass_member_iterator_next(&it)) != NULL) { - EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); - /* * Ignore child members unless they match the request. */ @@ -653,6 +687,7 @@ get_eclass_for_sort_expr(PlannerInfo *root, equal(expr, cur_em->em_expr)) return cur_ec; /* Match! */ } + dispose_eclass_member_iterator(&it); } /* No match; does caller want a NULL result? */ @@ -690,7 +725,7 @@ get_eclass_for_sort_expr(PlannerInfo *root, expr_relids = pull_varnos(root, (Node *) expr); newem = add_eq_member(newec, copyObject(expr), expr_relids, - jdomain, NULL, opcintype); + jdomain, opcintype); /* * add_eq_member doesn't check for volatile functions, set-returning @@ -764,15 +799,16 @@ find_ec_member_matching_expr(PlannerInfo *root, EquivalenceClass *ec, Expr *expr, Relids relids) { - ListCell *lc; + EquivalenceMemberIterator it; + EquivalenceMember *em; /* We ignore binary-compatible relabeling on both ends */ while (expr && IsA(expr, RelabelType)) expr = ((RelabelType *) expr)->arg; - foreach(lc, ec->ec_members) + setup_eclass_member_iterator_with_children(&it, root, ec, relids); + while ((em = eclass_member_iterator_next(&it)) != NULL) { - EquivalenceMember *em = (EquivalenceMember *) lfirst(lc); Expr *emexpr; /* @@ -799,6 +835,7 @@ find_ec_member_matching_expr(PlannerInfo *root, EquivalenceClass *ec, if (equal(emexpr, expr)) return em; } + dispose_eclass_member_iterator(&it); return NULL; } @@ -841,7 +878,8 @@ find_computable_ec_member(PlannerInfo *root, bool require_parallel_safe) { List *exprvars; - ListCell *lc; + EquivalenceMemberIterator it; + EquivalenceMember *em; /* * Pull out the Vars and quasi-Vars present in "exprs". In the typical @@ -855,9 +893,9 @@ find_computable_ec_member(PlannerInfo *root, PVC_INCLUDE_PLACEHOLDERS | PVC_INCLUDE_CONVERTROWTYPES); - foreach(lc, ec->ec_members) + setup_eclass_member_iterator_with_children(&it, root, ec, relids); + while ((em = eclass_member_iterator_next(&it)) != NULL) { - EquivalenceMember *em = (EquivalenceMember *) lfirst(lc); List *emvars; ListCell *lc2; @@ -901,6 +939,7 @@ find_computable_ec_member(PlannerInfo *root, return em; /* found usable expression */ } + dispose_eclass_member_iterator(&it); return NULL; } @@ -1162,7 +1201,8 @@ generate_base_implied_equalities_const(PlannerInfo *root, Oid eq_op; RestrictInfo *rinfo; - Assert(!cur_em->em_is_child); /* no children yet */ + Assert(!cur_em->em_is_child); /* Child members should not exist in + * ec_members */ if (cur_em == const_em) continue; eq_op = select_equality_operator(ec, @@ -1231,7 +1271,8 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); int relid; - Assert(!cur_em->em_is_child); /* no children yet */ + Assert(!cur_em->em_is_child); /* Child members should not exist in + * ec_members */ if (!bms_get_singleton_member(cur_em->em_relids, &relid)) continue; Assert(relid < root->simple_rel_array_size); @@ -1564,7 +1605,8 @@ generate_join_implied_equalities_normal(PlannerInfo *root, List *new_members = NIL; List *outer_members = NIL; List *inner_members = NIL; - ListCell *lc1; + EquivalenceMemberIterator it; + EquivalenceMember *cur_em; /* * First, scan the EC to identify member values that are computable at the @@ -1575,10 +1617,9 @@ generate_join_implied_equalities_normal(PlannerInfo *root, * as well as to at least one input member, plus enforce at least one * outer-rel member equal to at least one inner-rel member. */ - foreach(lc1, ec->ec_members) + setup_eclass_member_iterator_with_children(&it, root, ec, join_relids); + while ((cur_em = eclass_member_iterator_next(&it)) != NULL) { - EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1); - /* * We don't need to check explicitly for child EC members. This test * against join_relids will cause them to be ignored except when @@ -1594,6 +1635,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root, else new_members = lappend(new_members, cur_em); } + dispose_eclass_member_iterator(&it); /* * First, select the joinclause if needed. We can equate any one outer @@ -1611,6 +1653,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root, Oid best_eq_op = InvalidOid; int best_score = -1; RestrictInfo *rinfo; + ListCell *lc1; foreach(lc1, outer_members) { @@ -1685,6 +1728,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root, List *old_members = list_concat(outer_members, inner_members); EquivalenceMember *prev_em = NULL; RestrictInfo *rinfo; + ListCell *lc1; /* For now, arbitrarily take the first old_member as the one to use */ if (old_members) @@ -1692,7 +1736,7 @@ generate_join_implied_equalities_normal(PlannerInfo *root, foreach(lc1, new_members) { - EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1); + cur_em = (EquivalenceMember *) lfirst(lc1); if (prev_em != NULL) { @@ -2181,7 +2225,8 @@ reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo, { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); - Assert(!cur_em->em_is_child); /* no children yet */ + Assert(!cur_em->em_is_child); /* Child members should not exist + * in ec_members */ if (equal(outervar, cur_em->em_expr)) { match = true; @@ -2308,7 +2353,8 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo) foreach(lc2, cur_ec->ec_members) { coal_em = (EquivalenceMember *) lfirst(lc2); - Assert(!coal_em->em_is_child); /* no children yet */ + Assert(!coal_em->em_is_child); /* Child members should not exist + * in ec_members */ if (IsA(coal_em->em_expr, CoalesceExpr)) { CoalesceExpr *cexpr = (CoalesceExpr *) coal_em->em_expr; @@ -2526,8 +2572,8 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2, Oid opfamily) { EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2); - if (em->em_is_child) - continue; /* ignore children here */ + Assert(!em->em_is_child); /* Child members should not exist in + * ec_members */ if (equal(item1, em->em_expr)) item1member = true; else if (equal(item2, em->em_expr)) @@ -2598,8 +2644,8 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2); Var *var; - if (em->em_is_child) - continue; /* ignore children here */ + /* Child members should not exist in ec_members */ + Assert(!em->em_is_child); /* EM must be a Var, possibly with RelabelType */ var = (Var *) em->em_expr; @@ -2696,6 +2742,7 @@ add_child_rel_equivalences(PlannerInfo *root, Relids top_parent_relids = child_rel->top_parent_relids; Relids child_relids = child_rel->relids; int i; + ListCell *lc; /* * EC merging should be complete already, so we can use the parent rel's @@ -2708,7 +2755,6 @@ add_child_rel_equivalences(PlannerInfo *root, while ((i = bms_next_member(parent_rel->eclass_indexes, i)) >= 0) { EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i); - int num_members; /* * If this EC contains a volatile expression, then generating child @@ -2721,29 +2767,15 @@ add_child_rel_equivalences(PlannerInfo *root, /* Sanity check eclass_indexes only contain ECs for parent_rel */ Assert(bms_is_subset(top_parent_relids, cur_ec->ec_relids)); - /* - * We don't use foreach() here because there's no point in scanning - * newly-added child members, so we can stop after the last - * pre-existing EC member. - */ - num_members = list_length(cur_ec->ec_members); - for (int pos = 0; pos < num_members; pos++) + foreach(lc, cur_ec->ec_members) { - EquivalenceMember *cur_em = (EquivalenceMember *) list_nth(cur_ec->ec_members, pos); + EquivalenceMember *cur_em = lfirst_node(EquivalenceMember, lc); if (cur_em->em_is_const) continue; /* ignore consts here */ - /* - * We consider only original EC members here, not - * already-transformed child members. Otherwise, if some original - * member expression references more than one appendrel, we'd get - * an O(N^2) explosion of useless derived expressions for - * combinations of children. (But add_child_join_rel_equivalences - * may add targeted combinations for partitionwise-join purposes.) - */ - if (cur_em->em_is_child) - continue; /* ignore children here */ + /* Child members should not exist in ec_members */ + Assert(!cur_em->em_is_child); /* * Consider only members that reference and can be computed at @@ -2759,6 +2791,7 @@ add_child_rel_equivalences(PlannerInfo *root, /* OK, generate transformed child version */ Expr *child_expr; Relids new_relids; + EquivalenceMember *child_em; if (parent_rel->reloptkind == RELOPT_BASEREL) { @@ -2788,9 +2821,11 @@ add_child_rel_equivalences(PlannerInfo *root, top_parent_relids); new_relids = bms_add_members(new_relids, child_relids); - (void) add_eq_member(cur_ec, child_expr, new_relids, - cur_em->em_jdomain, - cur_em, cur_em->em_datatype); + child_em = make_eq_member(cur_ec, child_expr, new_relids, + cur_em->em_jdomain, + cur_em, cur_em->em_datatype); + child_rel->eclass_child_members = + lappend(child_rel->eclass_child_members, child_em); /* Record this EC index for the child rel */ child_rel->eclass_indexes = bms_add_member(child_rel->eclass_indexes, i); @@ -2840,7 +2875,7 @@ add_child_join_rel_equivalences(PlannerInfo *root, while ((i = bms_next_member(matching_ecs, i)) >= 0) { EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i); - int num_members; + ListCell *lc; /* * If this EC contains a volatile expression, then generating child @@ -2853,25 +2888,15 @@ add_child_join_rel_equivalences(PlannerInfo *root, /* Sanity check on get_eclass_indexes_for_relids result */ Assert(bms_overlap(top_parent_relids, cur_ec->ec_relids)); - /* - * We don't use foreach() here because there's no point in scanning - * newly-added child members, so we can stop after the last - * pre-existing EC member. - */ - num_members = list_length(cur_ec->ec_members); - for (int pos = 0; pos < num_members; pos++) + foreach(lc, cur_ec->ec_members) { - EquivalenceMember *cur_em = (EquivalenceMember *) list_nth(cur_ec->ec_members, pos); + EquivalenceMember *cur_em = lfirst_node(EquivalenceMember, lc); if (cur_em->em_is_const) continue; /* ignore consts here */ - /* - * We consider only original EC members here, not - * already-transformed child members. - */ - if (cur_em->em_is_child) - continue; /* ignore children here */ + /* Child members should not exist in ec_members */ + Assert(!cur_em->em_is_child); /* * We may ignore expressions that reference a single baserel, @@ -2886,6 +2911,8 @@ add_child_join_rel_equivalences(PlannerInfo *root, /* Yes, generate transformed child version */ Expr *child_expr; Relids new_relids; + EquivalenceMember *child_em; + int j; if (parent_joinrel->reloptkind == RELOPT_JOINREL) { @@ -2916,9 +2943,38 @@ add_child_join_rel_equivalences(PlannerInfo *root, top_parent_relids); new_relids = bms_add_members(new_relids, child_relids); - (void) add_eq_member(cur_ec, child_expr, new_relids, - cur_em->em_jdomain, - cur_em, cur_em->em_datatype); + child_em = make_eq_member(cur_ec, child_expr, new_relids, + cur_em->em_jdomain, + cur_em, cur_em->em_datatype); + child_joinrel->eclass_child_members = + lappend(child_joinrel->eclass_child_members, child_em); + + /* + * Update the corresponding inverted indexes. + */ + j = -1; + while ((j = bms_next_member(child_joinrel->relids, j)) >= 0) + { + EquivalenceClassIndexes *indexes = + &root->eclass_indexes_array[j]; + + /* + * We do not need to update the inverted index of the top + * parent relations. This is because EquivalenceMembers + * that have only such parent relations as em_relids are + * already present in the ec_members, and so cannot be + * candidates for additional iteration by + * EquivalenceMemberIterator. Since the iterator needs + * EquivalenceMembers whose em_relids has child relations, + * skipping the update of this inverted index allows for + * faster iteration. + */ + if (root->append_rel_array[j] == NULL) + continue; + indexes->joinrel_indexes = + bms_add_member(indexes->joinrel_indexes, + child_joinrel->join_rel_list_index); + } } } } @@ -2971,7 +3027,7 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel, tle->expr, child_rel->relids, parent_em->em_jdomain, - parent_em, + /* parent_em, */ /* Will be fixed in the next commit*/ exprType((Node *) tle->expr)); lc2 = lnext(setop_pathkeys, lc2); @@ -2987,6 +3043,219 @@ add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel, list_length(root->eq_classes) - 1); } +/* + * setup_eclass_member_iterator_with_children + * Setup an EquivalenceMemberIterator 'it' to iterate over all parent + * EquivalenceMembers and child members associated with the given 'ec' that + * are relevant to the specified 'relids'. + * + * This iterator returns: + * - All parent members stored directly in ec->ec_members. + * - The child members whose em_relids is a subset of the given 'relids'. + * + * Note: + * - The iterator may return false positives, i.e., child members whose + * em_relids is not a subset. So the caller must check that they satisfy + * the desired condition. + * - Once used, the caller should dispose of the iterator by calling + * dispose_eclass_member_iterator(). + * + * Parameters: + * root - The PlannerInfo context. + * ec - The EquivalenceClass from which to iterate members. + * relids - The Relids used to filter for relevant child members. + */ +void +setup_eclass_member_iterator_with_children(EquivalenceMemberIterator *it, + PlannerInfo *root, + EquivalenceClass *ec, + Relids relids) +{ + Bitmapset *matching_indexes; + int i; + + /* + * Initialize the iterator. + */ + it->index = -1; + it->list_is_copy = false; + it->ec_members = ec->ec_members; + + /* + * If there are no child relations, there is nothing to do. This + * effectively avoids regression for non-partitioned cases. + */ + if (root->append_rel_array == NULL) + return; + + /* + * EquivalenceClass->ec_members has only parent EquivalenceMembers. Child + * members are translated using child RelOptInfos and stored in them. This + * is done in add_child_rel_equivalences(), + * add_child_join_rel_equivalences(), and + * add_setop_child_rel_equivalences(). To retrieve child + * EquivalenceMembers of some parent, we need to know which RelOptInfos + * have such child members. We can know this information using indexes + * like EquivalenceClassIndexes->joinrel_indexes. + * + * We use an inverted index mechanism to quickly iterate over the members + * whose em_relids is a subset of the given 'relids'. The inverted indexes + * store RelOptInfo indices that have EquivalenceMembers mentioning them. + * Taking the union of these indexes allows to find which RelOptInfos have + * the EquivalenceMember we are looking for. With this method, the + * em_relids of the newly iterated ones overlap the given 'relids', but + * may not be subsets, so the caller must check that they satisfy the + * desired condition. + * + * The above comments are about joinrels, and for simple rels, this + * mechanism is simpler. It is sufficient to simply add the child + * EquivalenceMembers of RelOptInfo to the iterator. + * + * We need to perform these steps for each of the two types of relations. + */ + + /* + * Iterate over the given relids, adding child members for simple rels and + * taking union indexes for join rels. + */ + i = -1; + matching_indexes = NULL; + while ((i = bms_next_member(relids, i)) >= 0) + { + RelOptInfo *child_rel; + EquivalenceClassIndexes *indexes; + + /* + * If this relation is a parent, we don't have to do anything. + */ + if (root->append_rel_array[i] == NULL) + continue; + + /* + * Add child members that mention this relation to the iterator. + */ + child_rel = root->simple_rel_array[i]; + if (child_rel != NULL) + add_eclass_child_members_to_iterator(it, root, ec, child_rel); + + /* + * Union indexes for join rels. + */ + indexes = &root->eclass_indexes_array[i]; + matching_indexes = + bms_add_members(matching_indexes, indexes->joinrel_indexes); + } + + /* + * For join rels, add child members using 'matching_indexes'. + */ + i = -1; + while ((i = bms_next_member(matching_indexes, i)) >= 0) + { + RelOptInfo *child_joinrel = + list_nth_node(RelOptInfo, root->join_rel_list, i); + + Assert(child_joinrel != NULL); + + /* + * If this joinrel's Relids is not a subset of the given one, then the + * child EquivalenceMembers it holds should never be a subset either. + */ + if (bms_is_subset(child_joinrel->relids, relids)) + add_eclass_child_members_to_iterator(it, root, ec, child_joinrel); +#ifdef USE_ASSERT_CHECKING + else + { + /* + * Verify that the above comment is correct. + * + * NOTE: We may remove this assertion after the beta process. + */ + + ListCell *lc; + + foreach(lc, child_joinrel->eclass_child_members) + { + EquivalenceMember *child_em = lfirst_node(EquivalenceMember, lc); + + if (child_em->em_ec != ec) + continue; + Assert(!bms_is_subset(child_em->em_relids, relids)); + } + } +#endif + } + bms_free(matching_indexes); +} + +/* + * add_eclass_child_members_to_iterator + * Add a child EquivalenceMember referencing the given child_rel to + * the iterator from RelOptInfo. + * + * This function is expected to be called only from + * setup_eclass_member_iterator_with_children(). + */ +static void +add_eclass_child_members_to_iterator(EquivalenceMemberIterator *it, + PlannerInfo *root, + EquivalenceClass *ec, + RelOptInfo *child_rel) +{ + ListCell *lc; + + foreach(lc, child_rel->eclass_child_members) + { + EquivalenceMember *child_em = lfirst_node(EquivalenceMember, lc); + + /* Skip unwanted EquivalenceMembers */ + if (child_em->em_ec != ec) + continue; + + /* + * If this is the first time the iterator's list has been modified, we + * need to make a copy of it. + */ + if (!it->list_is_copy) + { + it->ec_members = list_copy(it->ec_members); + it->list_is_copy = true; + } + + /* Add this child EquivalenceMember to the list */ + it->ec_members = lappend(it->ec_members, child_em); + } +} + +/* + * eclass_member_iterator_next + * Get a next EquivalenceMember from an EquivalenceMemberIterator 'it' + * that was setup by setup_eclass_member_iterator_with_children(). NULL is + * returned if there are no members left. + */ +EquivalenceMember * +eclass_member_iterator_next(EquivalenceMemberIterator *it) +{ + if (++it->index < list_length(it->ec_members)) + return list_nth_node(EquivalenceMember, it->ec_members, it->index); + return NULL; +} + +/* + * dispose_eclass_member_iterator + * Free any memory allocated by the iterator. + */ +void +dispose_eclass_member_iterator(EquivalenceMemberIterator *it) +{ + /* + * XXX Should we use list_free()? I decided to use this style to take + * advantage of speculative execution. + */ + if (unlikely(it->list_is_copy)) + pfree(it->ec_members); +} + /* * generate_implied_equalities_for_column @@ -3041,6 +3310,7 @@ generate_implied_equalities_for_column(PlannerInfo *root, EquivalenceClass *cur_ec = (EquivalenceClass *) list_nth(root->eq_classes, i); EquivalenceMember *cur_em; ListCell *lc2; + EquivalenceMemberIterator it; /* Sanity check eclass_indexes only contain ECs for rel */ Assert(is_child_rel || bms_is_subset(rel->relids, cur_ec->ec_relids)); @@ -3062,15 +3332,14 @@ generate_implied_equalities_for_column(PlannerInfo *root, * corner cases, so for now we live with just reporting the first * match. See also get_eclass_for_sort_expr.) */ - cur_em = NULL; - foreach(lc2, cur_ec->ec_members) + setup_eclass_member_iterator_with_children(&it, root, cur_ec, rel->relids); + while ((cur_em = eclass_member_iterator_next(&it)) != NULL) { - cur_em = (EquivalenceMember *) lfirst(lc2); if (bms_equal(cur_em->em_relids, rel->relids) && callback(root, rel, cur_ec, cur_em, callback_arg)) break; - cur_em = NULL; } + dispose_eclass_member_iterator(&it); if (!cur_em) continue; @@ -3085,8 +3354,8 @@ generate_implied_equalities_for_column(PlannerInfo *root, Oid eq_op; RestrictInfo *rinfo; - if (other_em->em_is_child) - continue; /* ignore children here */ + /* 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 || @@ -3304,8 +3573,8 @@ eclass_useful_for_merging(PlannerInfo *root, { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); - if (cur_em->em_is_child) - continue; /* ignore children here */ + /* Child members should not exist in ec_members */ + Assert(!cur_em->em_is_child); if (!bms_overlap(cur_em->em_relids, relids)) return true; diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index a43ca16d683..c206f260633 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -190,7 +190,7 @@ static IndexClause *expand_indexqual_rowcompare(PlannerInfo *root, IndexOptInfo *index, Oid expr_op, bool var_on_left); -static void match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, +static void match_pathkeys_to_index(PlannerInfo *root, IndexOptInfo *index, List *pathkeys, List **orderby_clauses_p, List **clause_columns_p); static Expr *match_clause_to_ordering_op(IndexOptInfo *index, @@ -934,7 +934,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel, * query_pathkeys will allow an incremental sort to be considered on * the index's partially sorted results. */ - match_pathkeys_to_index(index, root->query_pathkeys, + match_pathkeys_to_index(root, index, root->query_pathkeys, &orderbyclauses, &orderbyclausecols); if (list_length(root->query_pathkeys) == list_length(orderbyclauses)) @@ -3739,7 +3739,7 @@ expand_indexqual_rowcompare(PlannerInfo *root, * item in the given 'pathkeys' list. */ static void -match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, +match_pathkeys_to_index(PlannerInfo *root, IndexOptInfo *index, List *pathkeys, List **orderby_clauses_p, List **clause_columns_p) { @@ -3756,7 +3756,8 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, { PathKey *pathkey = (PathKey *) lfirst(lc1); bool found = false; - ListCell *lc2; + EquivalenceMemberIterator it; + EquivalenceMember *member; /* Pathkey must request default sort order for the target opfamily */ @@ -3776,9 +3777,10 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, * be considered to match more than one pathkey list, which is OK * here. See also get_eclass_for_sort_expr.) */ - foreach(lc2, pathkey->pk_eclass->ec_members) + setup_eclass_member_iterator_with_children(&it, root, pathkey->pk_eclass, + index->rel->relids); + while ((member = eclass_member_iterator_next(&it)) != NULL) { - EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2); int indexcol; /* No possibility of match if it references other relations */ @@ -3813,6 +3815,7 @@ match_pathkeys_to_index(IndexOptInfo *index, List *pathkeys, if (found) /* don't want to look at remaining members */ break; } + dispose_eclass_member_iterator(&it); /* * Return the matches found so far when this pathkey couldn't be diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index 154eb505d75..a9419d37e2f 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -1151,8 +1151,8 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, Oid sub_expr_coll = sub_eclass->ec_collation; ListCell *k; - if (sub_member->em_is_child) - continue; /* ignore children here */ + /* Child members should not exist in ec_members */ + Assert(!sub_member->em_is_child); foreach(k, subquery_tlist) { @@ -1709,8 +1709,11 @@ select_outer_pathkeys_for_merge(PlannerInfo *root, { EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2); + /* Child members should not exist in ec_members */ + Assert(!em->em_is_child); + /* Potential future join partner? */ - if (!em->em_is_const && !em->em_is_child && + if (!em->em_is_const && !bms_overlap(em->em_relids, joinrel->relids)) score++; } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index ff507331a06..7791e282e9e 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -119,16 +119,23 @@ setup_simple_rel_arrays(PlannerInfo *root) root->simple_rte_array[rti++] = rte; } - /* append_rel_array is not needed if there are no AppendRelInfos */ + /* + * append_rel_array and eclass_indexes_array are not needed if there are + * no AppendRelInfos. + */ if (root->append_rel_list == NIL) { root->append_rel_array = NULL; + root->eclass_indexes_array = NULL; return; } root->append_rel_array = (AppendRelInfo **) palloc0(size * sizeof(AppendRelInfo *)); + root->eclass_indexes_array = (EquivalenceClassIndexes *) + palloc0(size * sizeof(EquivalenceClassIndexes)); + /* * append_rel_array is filled with any already-existing AppendRelInfos, * which currently could only come from UNION ALL flattening. We might @@ -175,11 +182,21 @@ expand_planner_arrays(PlannerInfo *root, int add_size) repalloc0_array(root->simple_rte_array, RangeTblEntry *, root->simple_rel_array_size, new_size); if (root->append_rel_array) + { + Assert(root->eclass_indexes_array); root->append_rel_array = repalloc0_array(root->append_rel_array, AppendRelInfo *, root->simple_rel_array_size, new_size); + root->eclass_indexes_array = + repalloc0_array(root->eclass_indexes_array, EquivalenceClassIndexes, root->simple_rel_array_size, new_size); + } else + { + Assert(!root->eclass_indexes_array); root->append_rel_array = palloc0_array(AppendRelInfo *, new_size); + root->eclass_indexes_array = + palloc0_array(EquivalenceClassIndexes, new_size); + } root->simple_rel_array_size = new_size; } @@ -234,6 +251,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->subplan_params = NIL; rel->rel_parallel_workers = -1; /* set up in get_relation_info */ rel->amflags = 0; + rel->join_rel_list_index = -1; + rel->eclass_child_members = NIL; rel->serverid = InvalidOid; if (rte->rtekind == RTE_RELATION) { @@ -626,6 +645,12 @@ set_foreign_rel_properties(RelOptInfo *joinrel, RelOptInfo *outer_rel, static void add_join_rel(PlannerInfo *root, RelOptInfo *joinrel) { + /* + * Store the index of this joinrel to use in + * add_child_join_rel_equivalences(). + */ + joinrel->join_rel_list_index = list_length(root->join_rel_list); + /* GEQO requires us to append the new joinrel to the end of the list! */ root->join_rel_list = lappend(root->join_rel_list, joinrel); @@ -741,6 +766,8 @@ build_join_rel(PlannerInfo *root, joinrel->subplan_params = NIL; joinrel->rel_parallel_workers = -1; joinrel->amflags = 0; + joinrel->join_rel_list_index = -1; + joinrel->eclass_child_members = NIL; joinrel->serverid = InvalidOid; joinrel->userid = InvalidOid; joinrel->useridiscurrent = false; @@ -928,6 +955,8 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, joinrel->subroot = NULL; joinrel->subplan_params = NIL; joinrel->amflags = 0; + joinrel->join_rel_list_index = -1; + joinrel->eclass_child_members = NIL; joinrel->serverid = InvalidOid; joinrel->userid = InvalidOid; joinrel->useridiscurrent = false; @@ -1490,6 +1519,8 @@ fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids) upperrel->cheapest_total_path = NULL; upperrel->cheapest_unique_path = NULL; upperrel->cheapest_parameterized_paths = NIL; + upperrel->join_rel_list_index = -1; + upperrel->eclass_child_members = NIL; root->upper_rels[kind] = lappend(root->upper_rels[kind], upperrel); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index c24a1fc8514..6b3a028cc58 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -216,6 +216,8 @@ typedef struct PlannerInfo PlannerInfo; #define HAVE_PLANNERINFO_TYPEDEF 1 #endif +struct EquivalenceClassIndexes; + struct PlannerInfo { pg_node_attr(no_copy_equal, no_read, no_query_jumble) @@ -272,6 +274,13 @@ struct PlannerInfo */ struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore); + /* + * eclass_indexes_array is the same length as simple_rel_array and holds + * the indexes of the corresponding rels for faster EquivalenceMember + * lookups. See the EquivalenceClass comment for more details. + */ + struct EquivalenceClassIndexes *eclass_indexes_array pg_node_attr(read_write_ignore); + /* * all_baserels is a Relids set of all base relids (but not joins or * "other" rels) in the query. This is computed in deconstruct_jointree. @@ -984,6 +993,17 @@ typedef struct RelOptInfo /* Bitmask of optional features supported by the table AM */ uint32 amflags; + /* + * information about a join rel + */ + /* index in root->join_rel_list of this rel */ + int join_rel_list_index; + + /* + * EquivalenceMembers referencing this rel + */ + List *eclass_child_members; + /* * Information about foreign tables and foreign joins */ @@ -1403,6 +1423,12 @@ typedef struct JoinDomain * entry: consider SELECT random() AS a, random() AS b ... ORDER BY b,a. * So we record the SortGroupRef of the originating sort clause. * + * EquivalenceClass->ec_members can only have parent members, and child members + * are stored in RelOptInfos, from which those child members are translated. To + * lookup child EquivalenceMembers, we use EquivalenceMemberIterator. See + * its comment for usage. The approach to lookup child members quickly is + * described as setup_eclass_member_iterator_with_children() comment. + * * NB: if ec_merged isn't NULL, this class has been merged into another, and * should be ignored in favor of using the pointed-to class. * @@ -1477,8 +1503,61 @@ typedef struct EquivalenceMember JoinDomain *em_jdomain; /* join domain containing the source clause */ /* if em_is_child is true, this links to corresponding EM for top parent */ struct EquivalenceMember *em_parent pg_node_attr(read_write_ignore); + EquivalenceClass *em_ec; /* EquivalenceClass which has this member */ } EquivalenceMember; +/* + * EquivalenceMemberIterator + * + * EquivalenceMemberIterator is designed to iterate over all parent + * EquivalenceMembers and child members associated with the given 'ec' that + * are relevant to the specified 'relids'. In particular, it iterates over: + * - All parent members stored directly in ec->ec_members. + * - The child members whose em_relids is a subset of the given 'relids'. + * + * Note: + * - The iterator may return false positives, i.e., child members whose + * em_relids is not a subset. So the caller must check that they satisfy + * the desired condition. + * + * The most common way to use this iterator is as follows: + * ----- + * PlannerInfo *root = given; + * EquivalenceClass *ec = given; + * Relids rel = given; + * EquivalenceMemberIterator it; + * EquivalenceMember *em; + * + * setup_eclass_member_iterator_with_children(&it, root, ec, rel); + * while ((em = eclass_member_iterator_next(&it)) != NULL) + * { + * use em ...; + * } + * dispose_eclass_member_iterator(&it); + * ----- + */ +typedef struct +{ + int index; /* current index within 'ec_members'. */ + bool list_is_copy; /* is 'ec_members' a newly allocated one? */ + List *ec_members; /* parent and child members */ +} EquivalenceMemberIterator; + +/* + * EquivalenceClassIndexes + * + * As mentioned in the EquivalenceClass comment, we introduce a + * bitmapset-based indexing mechanism for faster lookups of child + * EquivalenceMembers. This struct exists for each relation and holds the + * corresponding indexes. + */ +typedef struct EquivalenceClassIndexes +{ + Bitmapset *joinrel_indexes; /* Indexes in PlannerInfo's join_rel_list + * list for RelOptInfos that mention this + * relation */ +} EquivalenceClassIndexes; + /* * PathKeys * diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 84a000a3ef1..6b80c8665b9 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -183,6 +183,12 @@ extern void add_setop_child_rel_equivalences(PlannerInfo *root, RelOptInfo *child_rel, List *child_tlist, List *setop_pathkeys); +extern void setup_eclass_member_iterator_with_children(EquivalenceMemberIterator *it, + PlannerInfo *root, + EquivalenceClass *ec, + Relids relids); +extern EquivalenceMember *eclass_member_iterator_next(EquivalenceMemberIterator *it); +extern void dispose_eclass_member_iterator(EquivalenceMemberIterator *it); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9442a4841aa..4b613ad34aa 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -701,7 +701,9 @@ EphemeralNamedRelationData EphemeralNamedRelationMetadata EphemeralNamedRelationMetadataData EquivalenceClass +EquivalenceClassIndexes EquivalenceMember +EquivalenceMemberIterator ErrorContextCallback ErrorData ErrorSaveContext -- 2.34.1