From 4a6df93c71ffaafd37a67fc0e1534bf67a8c83c1 Mon Sep 17 00:00:00 2001 From: Yuya Watari Date: Fri, 25 Aug 2023 10:44:25 +0900 Subject: [PATCH v34 3/4] Introduce indexes for RestrictInfo This commit adds indexes to speed up searches for RestrictInfos. When there are many child partitions and we have to perform planning for join operations on the tables, we have to handle a large number of RestrictInfos, resulting in a long planning time. This commit adds ec_[source|derive]_indexes to speed up the search. We can use the indexes to filter out unwanted RestrictInfos, and improve the planning performance. Author: David Rowley and me, and includes rebase by Alena Rybakina [1]. [1] https://www.postgresql.org/message-id/72d292a1-06ff-432a-a803-af5053786444%40yandex.ru --- src/backend/nodes/outfuncs.c | 4 +- src/backend/optimizer/path/costsize.c | 3 +- src/backend/optimizer/path/equivclass.c | 514 ++++++++++++++++++++-- src/backend/optimizer/plan/analyzejoins.c | 132 ++++-- src/backend/optimizer/plan/planner.c | 2 + src/backend/optimizer/prep/prepjointree.c | 2 + src/backend/optimizer/util/appendinfo.c | 2 + src/backend/optimizer/util/restrictinfo.c | 7 + src/backend/rewrite/rewriteManip.c | 5 +- src/include/nodes/pathnodes.h | 54 ++- src/include/optimizer/paths.h | 20 +- 11 files changed, 665 insertions(+), 80 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index bb9bdd67192..206e65d157d 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -466,8 +466,8 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node) WRITE_NODE_FIELD(ec_opfamilies); WRITE_OID_FIELD(ec_collation); WRITE_NODE_FIELD(ec_members); - WRITE_NODE_FIELD(ec_sources); - WRITE_NODE_FIELD(ec_derives); + WRITE_BITMAPSET_FIELD(ec_source_indexes); + WRITE_BITMAPSET_FIELD(ec_derive_indexes); WRITE_BITMAPSET_FIELD(ec_relids); WRITE_BOOL_FIELD(ec_has_const); WRITE_BOOL_FIELD(ec_has_volatile); diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 256568d05a2..7d23a8385b6 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -5849,7 +5849,8 @@ get_foreign_key_join_selectivity(PlannerInfo *root, if (ec && ec->ec_has_const) { EquivalenceMember *em = fkinfo->fk_eclass_member[i]; - RestrictInfo *rinfo = find_derived_clause_for_ec_member(ec, + RestrictInfo *rinfo = find_derived_clause_for_ec_member(root, + ec, em); if (rinfo) diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 33de4439bec..1facf547bb9 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -42,6 +42,10 @@ static EquivalenceMember *add_parent_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, JoinDomain *jdomain, Oid datatype); +static void add_eq_source(PlannerInfo *root, EquivalenceClass *ec, + RestrictInfo *rinfo); +static void add_eq_derive(PlannerInfo *root, EquivalenceClass *ec, + RestrictInfo *rinfo); static void generate_base_implied_equalities_const(PlannerInfo *root, EquivalenceClass *ec); static void generate_base_implied_equalities_no_const(PlannerInfo *root, @@ -318,7 +322,6 @@ process_equivalence(PlannerInfo *root, /* If case 1, nothing to do, except add to sources */ if (ec1 == ec2) { - ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_min_security = Min(ec1->ec_min_security, restrictinfo->security_level); ec1->ec_max_security = Max(ec1->ec_max_security, @@ -329,6 +332,8 @@ process_equivalence(PlannerInfo *root, /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; + + add_eq_source(root, ec1, restrictinfo); return true; } @@ -349,8 +354,10 @@ process_equivalence(PlannerInfo *root, * be found. */ ec1->ec_members = list_concat(ec1->ec_members, ec2->ec_members); - ec1->ec_sources = list_concat(ec1->ec_sources, ec2->ec_sources); - ec1->ec_derives = list_concat(ec1->ec_derives, ec2->ec_derives); + ec1->ec_source_indexes = bms_join(ec1->ec_source_indexes, + ec2->ec_source_indexes); + ec1->ec_derive_indexes = bms_join(ec1->ec_derive_indexes, + ec2->ec_derive_indexes); ec1->ec_relids = bms_join(ec1->ec_relids, ec2->ec_relids); ec1->ec_has_const |= ec2->ec_has_const; /* can't need to set has_volatile */ @@ -362,10 +369,9 @@ process_equivalence(PlannerInfo *root, root->eq_classes = list_delete_nth_cell(root->eq_classes, ec2_idx); /* just to avoid debugging confusion w/ dangling pointers: */ ec2->ec_members = NIL; - ec2->ec_sources = NIL; - ec2->ec_derives = NIL; + ec2->ec_source_indexes = NULL; + ec2->ec_derive_indexes = NULL; ec2->ec_relids = NULL; - ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_min_security = Min(ec1->ec_min_security, restrictinfo->security_level); ec1->ec_max_security = Max(ec1->ec_max_security, @@ -376,13 +382,14 @@ process_equivalence(PlannerInfo *root, /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; + + add_eq_source(root, ec1, restrictinfo); } else if (ec1) { /* Case 3: add item2 to ec1 */ em2 = add_parent_eq_member(ec1, item2, item2_relids, jdomain, item2_type); - ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo); ec1->ec_min_security = Min(ec1->ec_min_security, restrictinfo->security_level); ec1->ec_max_security = Max(ec1->ec_max_security, @@ -393,13 +400,14 @@ process_equivalence(PlannerInfo *root, /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; + + add_eq_source(root, ec1, restrictinfo); } else if (ec2) { /* Case 3: add item1 to ec2 */ em1 = add_parent_eq_member(ec2, item1, item1_relids, jdomain, item1_type); - ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo); ec2->ec_min_security = Min(ec2->ec_min_security, restrictinfo->security_level); ec2->ec_max_security = Max(ec2->ec_max_security, @@ -410,6 +418,8 @@ process_equivalence(PlannerInfo *root, /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; + + add_eq_source(root, ec2, restrictinfo); } else { @@ -419,8 +429,8 @@ process_equivalence(PlannerInfo *root, ec->ec_opfamilies = opfamilies; ec->ec_collation = collation; ec->ec_members = NIL; - ec->ec_sources = list_make1(restrictinfo); - ec->ec_derives = NIL; + ec->ec_source_indexes = NULL; + ec->ec_derive_indexes = NULL; ec->ec_relids = NULL; ec->ec_has_const = false; ec->ec_has_volatile = false; @@ -442,6 +452,8 @@ process_equivalence(PlannerInfo *root, /* mark the RI as usable with this pair of EMs */ restrictinfo->left_em = em1; restrictinfo->right_em = em2; + + add_eq_source(root, ec, restrictinfo); } return true; @@ -583,6 +595,166 @@ add_parent_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, return em; } +/* + * add_eq_source - add 'rinfo' in eq_sources for this 'ec' + */ +static void +add_eq_source(PlannerInfo *root, EquivalenceClass *ec, RestrictInfo *rinfo) +{ + int source_idx = list_length(root->eq_sources); + int i; + + ec->ec_source_indexes = bms_add_member(ec->ec_source_indexes, source_idx); + root->eq_sources = lappend(root->eq_sources, rinfo); + Assert(rinfo->eq_sources_index == -1); + rinfo->eq_sources_index = source_idx; + + i = -1; + while ((i = bms_next_member(rinfo->clause_relids, i)) >= 0) + { + EquivalenceClassIndexes *index = &root->eclass_indexes_array[i]; + + index->source_indexes = bms_add_member(index->source_indexes, + source_idx); + } +} + +/* + * add_eq_derive - add 'rinfo' in eq_derives for this 'ec' + */ +static void +add_eq_derive(PlannerInfo *root, EquivalenceClass *ec, RestrictInfo *rinfo) +{ + int derive_idx = list_length(root->eq_derives); + int i; + + ec->ec_derive_indexes = bms_add_member(ec->ec_derive_indexes, derive_idx); + root->eq_derives = lappend(root->eq_derives, rinfo); + Assert(rinfo->eq_derives_index == -1); + rinfo->eq_derives_index = derive_idx; + + i = -1; + while ((i = bms_next_member(rinfo->clause_relids, i)) >= 0) + { + EquivalenceClassIndexes *index = &root->eclass_indexes_array[i]; + + index->derive_indexes = bms_add_member(index->derive_indexes, + derive_idx); + } +} + +/* + * update_clause_relids + * Update RestrictInfo->clause_relids with adjusting the relevant indexes. + * The clause_relids field must be updated through this function, not by + * setting the field directly. + */ +void +update_clause_relids(RestrictInfo *rinfo, Relids new_clause_relids) +{ + int i; + Relids removing_relids; + Relids adding_relids; +#ifdef USE_ASSERT_CHECKING + Relids common_relids; +#endif + + /* + * If there is nothing to do, we return immediately after setting the + * clause_relids field. + */ + if (rinfo->eq_sources_index == -1 && rinfo->eq_derives_index == -1) + { + rinfo->clause_relids = new_clause_relids; + return; + } + + /* + * Remove any references between this RestrictInfo and RangeTblEntries + * that are no longer relevant. + */ + removing_relids = bms_difference(rinfo->clause_relids, new_clause_relids); + i = -1; + while ((i = bms_next_member(removing_relids, i)) >= 0) + { + EquivalenceClassIndexes *index = &rinfo->root->eclass_indexes_array[i]; + + if (rinfo->eq_sources_index != -1) + { + Assert(bms_is_member(rinfo->eq_sources_index, + index->source_indexes)); + index->source_indexes = + bms_del_member(index->source_indexes, + rinfo->eq_sources_index); + } + if (rinfo->eq_derives_index != -1) + { + Assert(bms_is_member(rinfo->eq_derives_index, + index->derive_indexes)); + index->derive_indexes = + bms_del_member(index->derive_indexes, + rinfo->eq_derives_index); + } + } + bms_free(removing_relids); + + /* + * Add references between this RestrictInfo and RangeTblEntries that will + * be relevant. + */ + adding_relids = bms_difference(new_clause_relids, rinfo->clause_relids); + i = -1; + while ((i = bms_next_member(adding_relids, i)) >= 0) + { + EquivalenceClassIndexes *index = &rinfo->root->eclass_indexes_array[i]; + + if (rinfo->eq_sources_index != -1) + { + Assert(!bms_is_member(rinfo->eq_sources_index, + index->source_indexes)); + index->source_indexes = + bms_add_member(index->source_indexes, + rinfo->eq_sources_index); + } + if (rinfo->eq_derives_index != -1) + { + Assert(!bms_is_member(rinfo->eq_derives_index, + index->derive_indexes)); + index->derive_indexes = + bms_add_member(index->derive_indexes, + rinfo->eq_derives_index); + } + } + bms_free(adding_relids); + +#ifdef USE_ASSERT_CHECKING + + /* + * Verify that all indexes are set for common Relids. + */ + common_relids = bms_intersect(rinfo->clause_relids, new_clause_relids); + i = -1; + while ((i = bms_next_member(common_relids, i)) >= 0) + { + EquivalenceClassIndexes *index = &rinfo->root->eclass_indexes_array[i]; + + if (rinfo->eq_sources_index != -1) + Assert(bms_is_member(rinfo->eq_sources_index, + index->source_indexes)); + if (rinfo->eq_derives_index != -1) + Assert(bms_is_member(rinfo->eq_derives_index, + index->derive_indexes)); + } + bms_free(common_relids); +#endif + + /* + * Since we have done everything to adjust the indexes, we can set the + * clause_relids field. + */ + rinfo->clause_relids = new_clause_relids; +} + /* * get_eclass_for_sort_expr @@ -704,8 +876,8 @@ get_eclass_for_sort_expr(PlannerInfo *root, newec->ec_opfamilies = list_copy(opfamilies); newec->ec_collation = collation; newec->ec_members = NIL; - newec->ec_sources = NIL; - newec->ec_derives = NIL; + newec->ec_source_indexes = NULL; + newec->ec_derive_indexes = NULL; newec->ec_relids = NULL; newec->ec_has_const = false; newec->ec_has_volatile = contain_volatile_functions((Node *) expr); @@ -1064,7 +1236,7 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel, * scanning of the quals and before Path construction begins. * * We make no attempt to avoid generating duplicate RestrictInfos here: we - * don't search ec_sources or ec_derives for matches. It doesn't really + * don't search eq_sources or eq_derives for matches. It doesn't really * seem worth the trouble to do so. */ void @@ -1157,6 +1329,7 @@ generate_base_implied_equalities_const(PlannerInfo *root, { EquivalenceMember *const_em = NULL; ListCell *lc; + int i; /* * In the trivial case where we just had one "var = const" clause, push @@ -1166,9 +1339,9 @@ generate_base_implied_equalities_const(PlannerInfo *root, * equivalent to the old one. */ if (list_length(ec->ec_members) == 2 && - list_length(ec->ec_sources) == 1) + bms_get_singleton_member(ec->ec_source_indexes, &i)) { - RestrictInfo *restrictinfo = (RestrictInfo *) linitial(ec->ec_sources); + RestrictInfo *restrictinfo = list_nth_node(RestrictInfo, root->eq_sources, i); distribute_restrictinfo_to_rels(root, restrictinfo); return; @@ -1226,9 +1399,9 @@ generate_base_implied_equalities_const(PlannerInfo *root, /* * If the clause didn't degenerate to a constant, fill in the correct - * markings for a mergejoinable clause, and save it in ec_derives. (We + * markings for a mergejoinable clause, and save it in eq_derives. (We * will not re-use such clauses directly, but selectivity estimation - * may consult the list later. Note that this use of ec_derives does + * may consult the list later. Note that this use of eq_derives does * not overlap with its use for join clauses, since we never generate * join clauses from an ec_has_const eclass.) */ @@ -1238,7 +1411,8 @@ generate_base_implied_equalities_const(PlannerInfo *root, rinfo->left_ec = rinfo->right_ec = ec; rinfo->left_em = cur_em; rinfo->right_em = const_em; - ec->ec_derives = lappend(ec->ec_derives, rinfo); + + add_eq_derive(root, ec, rinfo); } } } @@ -1304,7 +1478,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, /* * If the clause didn't degenerate to a constant, fill in the * correct markings for a mergejoinable clause. We don't put it - * in ec_derives however; we don't currently need to re-find such + * in eq_derives however; we don't currently need to re-find such * clauses, and we don't want to clutter that list with non-join * clauses. */ @@ -1361,11 +1535,12 @@ static void generate_base_implied_equalities_broken(PlannerInfo *root, EquivalenceClass *ec) { - ListCell *lc; + int i = -1; - foreach(lc, ec->ec_sources) + while ((i = bms_next_member(ec->ec_source_indexes, i)) >= 0) { - RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc); + RestrictInfo *restrictinfo = list_nth_node(RestrictInfo, + root->eq_sources, i); if (ec->ec_has_const || bms_membership(restrictinfo->required_relids) != BMS_MULTIPLE) @@ -1406,11 +1581,11 @@ generate_base_implied_equalities_broken(PlannerInfo *root, * Because the same join clauses are likely to be needed multiple times as * we consider different join paths, we avoid generating multiple copies: * whenever we select a particular pair of EquivalenceMembers to join, - * we check to see if the pair matches any original clause (in ec_sources) - * or previously-built clause (in ec_derives). This saves memory and allows - * re-use of information cached in RestrictInfos. We also avoid generating - * commutative duplicates, i.e. if the algorithm selects "a.x = b.y" but - * we already have "b.y = a.x", we return the existing clause. + * we check to see if the pair matches any original clause in root's + * eq_sources or previously-built clause (in root's eq_derives). This saves + * memory and allows re-use of information cached in RestrictInfos. We also + * avoid generating commutative duplicates, i.e. if the algorithm selects + * "a.x = b.y" but we already have "b.y = a.x", we return the existing clause. * * If we are considering an outer join, sjinfo is the associated OJ info, * otherwise it can be NULL. @@ -1778,12 +1953,16 @@ generate_join_implied_equalities_broken(PlannerInfo *root, Relids nominal_inner_relids, RelOptInfo *inner_rel) { + Bitmapset *matching_es; List *result = NIL; - ListCell *lc; + int i; - foreach(lc, ec->ec_sources) + matching_es = get_ec_source_indexes(root, ec, nominal_join_relids); + i = -1; + while ((i = bms_next_member(matching_es, i)) >= 0) { - RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc); + RestrictInfo *restrictinfo = list_nth_node(RestrictInfo, + root->eq_sources, i); Relids clause_relids = restrictinfo->required_relids; if (bms_is_subset(clause_relids, nominal_join_relids) && @@ -1795,12 +1974,12 @@ generate_join_implied_equalities_broken(PlannerInfo *root, /* * If we have to translate, just brute-force apply adjust_appendrel_attrs * to all the RestrictInfos at once. This will result in returning - * RestrictInfos that are not listed in ec_derives, but there shouldn't be + * RestrictInfos that are not listed in eq_derives, but there shouldn't be * any duplication, and it's a sufficiently narrow corner case that we * shouldn't sweat too much over it anyway. * * Since inner_rel might be an indirect descendant of the baserel - * mentioned in the ec_sources clauses, we have to be prepared to apply + * mentioned in the eq_sources clauses, we have to be prepared to apply * multiple levels of Var translation. */ if (IS_OTHER_REL(inner_rel) && result != NIL) @@ -1862,10 +2041,11 @@ create_join_clause(PlannerInfo *root, EquivalenceMember *rightem, EquivalenceClass *parent_ec) { + Bitmapset *matches; RestrictInfo *rinfo; RestrictInfo *parent_rinfo = NULL; - ListCell *lc; MemoryContext oldcontext; + int i; /* * Search to see if we already built a RestrictInfo for this pair of @@ -1876,9 +2056,12 @@ create_join_clause(PlannerInfo *root, * it's not identical, it'd better have the same effects, or the operator * families we're using are broken. */ - foreach(lc, ec->ec_sources) + matches = bms_int_members(get_ec_source_indexes_strict(root, ec, leftem->em_relids), + get_ec_source_indexes_strict(root, ec, rightem->em_relids)); + i = -1; + while ((i = bms_next_member(matches, i)) >= 0) { - rinfo = (RestrictInfo *) lfirst(lc); + rinfo = list_nth_node(RestrictInfo, root->eq_sources, i); if (rinfo->left_em == leftem && rinfo->right_em == rightem && rinfo->parent_ec == parent_ec) @@ -1889,9 +2072,13 @@ create_join_clause(PlannerInfo *root, return rinfo; } - foreach(lc, ec->ec_derives) + matches = bms_int_members(get_ec_derive_indexes_strict(root, ec, leftem->em_relids), + get_ec_derive_indexes_strict(root, ec, rightem->em_relids)); + + i = -1; + while ((i = bms_next_member(matches, i)) >= 0) { - rinfo = (RestrictInfo *) lfirst(lc); + rinfo = list_nth_node(RestrictInfo, root->eq_derives, i); if (rinfo->left_em == leftem && rinfo->right_em == rightem && rinfo->parent_ec == parent_ec) @@ -1964,7 +2151,7 @@ create_join_clause(PlannerInfo *root, rinfo->left_em = leftem; rinfo->right_em = rightem; /* and save it for possible re-use */ - ec->ec_derives = lappend(ec->ec_derives, rinfo); + add_eq_derive(root, ec, rinfo); MemoryContextSwitchTo(oldcontext); @@ -2690,16 +2877,19 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * Returns NULL if no such clause can be found. */ RestrictInfo * -find_derived_clause_for_ec_member(EquivalenceClass *ec, +find_derived_clause_for_ec_member(PlannerInfo *root, EquivalenceClass *ec, EquivalenceMember *em) { - ListCell *lc; + int i; Assert(ec->ec_has_const); Assert(!em->em_is_const); - foreach(lc, ec->ec_derives) + + i = -1; + while ((i = bms_next_member(ec->ec_derive_indexes, i)) >= 0) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + RestrictInfo *rinfo = list_nth_node(RestrictInfo, root->eq_derives, + i); /* * generate_base_implied_equalities_const will have put non-const @@ -3498,7 +3688,7 @@ have_relevant_eclass_joinclause(PlannerInfo *root, * surely be true if both of them overlap ec_relids.) * * Note we don't test ec_broken; if we did, we'd need a separate code - * path to look through ec_sources. Checking the membership anyway is + * path to look through eq_sources. Checking the membership anyway is * OK as a possibly-overoptimistic heuristic. * * We don't test ec_has_const either, even though a const eclass won't @@ -3586,7 +3776,7 @@ eclass_useful_for_merging(PlannerInfo *root, /* * Note we don't test ec_broken; if we did, we'd need a separate code path - * to look through ec_sources. Checking the members anyway is OK as a + * to look through eq_sources. Checking the members anyway is OK as a * possibly-overoptimistic heuristic. */ @@ -3743,3 +3933,239 @@ get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2) /* Calculate and return the common EC indexes, recycling the left input. */ return bms_int_members(rel1ecs, rel2ecs); } + +/* + * get_ec_source_indexes + * Returns a Bitmapset with indexes into root->eq_sources for all + * RestrictInfos in 'ec' that have + * bms_overlap(relids, rinfo->clause_relids) as true. + * + * Returns a newly allocated Bitmapset which the caller is free to modify. + */ +Bitmapset * +get_ec_source_indexes(PlannerInfo *root, EquivalenceClass *ec, Relids relids) +{ + Bitmapset *rel_esis = NULL; + int i = -1; + + while ((i = bms_next_member(relids, i)) >= 0) + { + EquivalenceClassIndexes *index = &root->eclass_indexes_array[i]; + + rel_esis = bms_add_members(rel_esis, index->source_indexes); + } + +#ifdef USE_ASSERT_CHECKING + /* verify the results look sane */ + i = -1; + while ((i = bms_next_member(rel_esis, i)) >= 0) + { + RestrictInfo *rinfo = list_nth_node(RestrictInfo, root->eq_sources, + i); + + Assert(bms_overlap(relids, rinfo->clause_relids)); + } +#endif + + /* bitwise-AND to leave only the ones for this EquivalenceClass */ + return bms_int_members(rel_esis, ec->ec_source_indexes); +} + +/* + * get_ec_source_indexes_strict + * Returns a Bitmapset with indexes into root->eq_sources for all + * RestrictInfos in 'ec' that have + * bms_is_subset(relids, rinfo->clause_relids) as true. + * + * Returns a newly allocated Bitmapset which the caller is free to modify. + */ +Bitmapset * +get_ec_source_indexes_strict(PlannerInfo *root, EquivalenceClass *ec, + Relids relids) +{ + Bitmapset *esis = NULL; + int i = bms_next_member(relids, -1); + + if (i >= 0) + { + EquivalenceClassIndexes *index = &root->eclass_indexes_array[i]; + + /* + * bms_intersect to the first relation to try to keep the resulting + * Bitmapset as small as possible. This saves having to make a + * complete bms_copy() of one of them. One may contain significantly + * more words than the other. + */ + esis = bms_intersect(ec->ec_source_indexes, + index->source_indexes); + + while ((i = bms_next_member(relids, i)) >= 0) + { + index = &root->eclass_indexes_array[i]; + esis = bms_int_members(esis, index->source_indexes); + } + } + +#ifdef USE_ASSERT_CHECKING + /* verify the results look sane */ + i = -1; + while ((i = bms_next_member(esis, i)) >= 0) + { + RestrictInfo *rinfo = list_nth_node(RestrictInfo, root->eq_sources, + i); + + Assert(bms_is_subset(relids, rinfo->clause_relids)); + } +#endif + + return esis; +} + +/* + * get_ec_derive_indexes + * Returns a Bitmapset with indexes into root->eq_derives for all + * RestrictInfos in 'ec' that have + * bms_overlap(relids, rinfo->clause_relids) as true. + * + * Returns a newly allocated Bitmapset which the caller is free to modify. + * + * XXX is this function even needed? + */ +Bitmapset * +get_ec_derive_indexes(PlannerInfo *root, EquivalenceClass *ec, Relids relids) +{ + Bitmapset *rel_edis = NULL; + int i = -1; + + while ((i = bms_next_member(relids, i)) >= 0) + { + EquivalenceClassIndexes *index = &root->eclass_indexes_array[i]; + + rel_edis = bms_add_members(rel_edis, index->derive_indexes); + } + +#ifdef USE_ASSERT_CHECKING + /* verify the results look sane */ + i = -1; + while ((i = bms_next_member(rel_edis, i)) >= 0) + { + RestrictInfo *rinfo = list_nth_node(RestrictInfo, root->eq_derives, + i); + + Assert(bms_overlap(relids, rinfo->clause_relids)); + } +#endif + + /* bitwise-AND to leave only the ones for this EquivalenceClass */ + return bms_int_members(rel_edis, ec->ec_derive_indexes); +} + +/* + * get_ec_derive_indexes_strict + * Returns a Bitmapset with indexes into root->eq_derives for all + * RestrictInfos in 'ec' that have + * bms_is_subset(relids, rinfo->clause_relids) as true. + * + * Returns a newly allocated Bitmapset which the caller is free to modify. + */ +Bitmapset * +get_ec_derive_indexes_strict(PlannerInfo *root, EquivalenceClass *ec, + Relids relids) +{ + Bitmapset *edis = NULL; + int i = bms_next_member(relids, -1); + + if (i >= 0) + { + EquivalenceClassIndexes *index = &root->eclass_indexes_array[i]; + + /* + * bms_intersect to the first relation to try to keep the resulting + * Bitmapset as small as possible. This saves having to make a + * complete bms_copy() of one of them. One may contain significantly + * more words than the other. + */ + edis = bms_intersect(ec->ec_derive_indexes, + index->derive_indexes); + + while ((i = bms_next_member(relids, i)) >= 0) + { + index = &root->eclass_indexes_array[i]; + edis = bms_int_members(edis, index->derive_indexes); + } + } + +#ifdef USE_ASSERT_CHECKING + /* verify the results look sane */ + i = -1; + while ((i = bms_next_member(edis, i)) >= 0) + { + RestrictInfo *rinfo = list_nth_node(RestrictInfo, root->eq_derives, + i); + + Assert(bms_is_subset(relids, rinfo->clause_relids)); + } +#endif + + return edis; +} + +#ifdef USE_ASSERT_CHECKING +/* + * verify_eclass_indexes + * Verify that there are no missing references between RestrictInfos and + * EquivalenceMember's indexes, namely source_indexes and derive_indexes. + * If you modify these indexes, you should check them with this function. + */ +void +verify_eclass_indexes(PlannerInfo *root, EquivalenceClass *ec) +{ + ListCell *lc; + + /* + * All RestrictInfos in root->eq_sources must have references to + * source_indexes. + */ + foreach(lc, root->eq_sources) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + int index; + int k; + + /* Deleted members are marked NULL */ + if (rinfo == NULL) + continue; + + index = list_cell_number(root->eq_sources, lc); + k = -1; + while ((k = bms_next_member(rinfo->clause_relids, k)) >= 0) + { + /* must have a reference */ + Assert(bms_is_member(index, root->eclass_indexes_array[k].source_indexes)); + } + } + + /* + * All RestrictInfos in root->eq_derives must have references to + * derive_indexes. + */ + foreach(lc, root->eq_derives) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + int index; + int k; + + /* Deleted members are marked NULL */ + if (rinfo == NULL) + continue; + + index = list_cell_number(root->eq_derives, lc); + k = -1; + while ((k = bms_next_member(rinfo->clause_relids, k)) >= 0) + { + /* must have a reference */ + Assert(bms_is_member(index, root->eclass_indexes_array[k].derive_indexes)); + } + } +} +#endif diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 8a8d4a2af33..cc3fec7c718 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -58,7 +58,7 @@ static void remove_leftjoinrel_from_query(PlannerInfo *root, int relid, SpecialJoinInfo *sjinfo); static void remove_rel_from_restrictinfo(RestrictInfo *rinfo, int relid, int ojrelid); -static void remove_rel_from_eclass(EquivalenceClass *ec, +static void remove_rel_from_eclass(PlannerInfo *root, EquivalenceClass *ec, SpecialJoinInfo *sjinfo, int relid, int subst); static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved); @@ -473,7 +473,7 @@ remove_rel_from_query(PlannerInfo *root, RelOptInfo *rel, if (bms_is_member(relid, ec->ec_relids) || (sjinfo == NULL || bms_is_member(sjinfo->ojrelid, ec->ec_relids))) - remove_rel_from_eclass(ec, sjinfo, relid, subst); + remove_rel_from_eclass(root, ec, sjinfo, relid, subst); } /* @@ -640,17 +640,25 @@ remove_leftjoinrel_from_query(PlannerInfo *root, int relid, static void remove_rel_from_restrictinfo(RestrictInfo *rinfo, int relid, int ojrelid) { + Relids new_clause_relids; + /* * initsplan.c is fairly cavalier about allowing RestrictInfos to share * relid sets with other RestrictInfos, and SpecialJoinInfos too. Make * sure this RestrictInfo has its own relid sets before we modify them. - * (In present usage, clause_relids is probably not shared, but - * required_relids could be; let's not assume anything.) + * The 'new_clause_relids' passed to update_clause_relids() must be a + * different instance from the current rinfo->clause_relids, so we make a + * copy of it. + */ + new_clause_relids = bms_copy(rinfo->clause_relids); + new_clause_relids = bms_del_member(new_clause_relids, relid); + new_clause_relids = bms_del_member(new_clause_relids, ojrelid); + update_clause_relids(rinfo, new_clause_relids); + + /* + * In present usage, required_relids could be shared, so we make a copy of + * it. */ - rinfo->clause_relids = bms_copy(rinfo->clause_relids); - rinfo->clause_relids = bms_del_member(rinfo->clause_relids, relid); - rinfo->clause_relids = bms_del_member(rinfo->clause_relids, ojrelid); - /* Likewise for required_relids */ rinfo->required_relids = bms_copy(rinfo->required_relids); rinfo->required_relids = bms_del_member(rinfo->required_relids, relid); rinfo->required_relids = bms_del_member(rinfo->required_relids, ojrelid); @@ -699,10 +707,11 @@ remove_rel_from_restrictinfo(RestrictInfo *rinfo, int relid, int ojrelid) * level(s). */ static void -remove_rel_from_eclass(EquivalenceClass *ec, SpecialJoinInfo *sjinfo, - int relid, int subst) +remove_rel_from_eclass(PlannerInfo *root, EquivalenceClass *ec, + SpecialJoinInfo *sjinfo, int relid, int subst) { ListCell *lc; + int i; /* Fix up the EC's overall relids */ ec->ec_relids = adjust_relid_set(ec->ec_relids, relid, subst); @@ -734,9 +743,10 @@ remove_rel_from_eclass(EquivalenceClass *ec, SpecialJoinInfo *sjinfo, } /* Fix up the source clauses, in case we can re-use them later */ - foreach(lc, ec->ec_sources) + i = -1; + while ((i = bms_next_member(ec->ec_source_indexes, i)) >= 0) { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + RestrictInfo *rinfo = list_nth_node(RestrictInfo, root->eq_sources, i); if (sjinfo == NULL) ChangeVarNodes((Node *) rinfo, relid, subst, 0); @@ -749,7 +759,7 @@ remove_rel_from_eclass(EquivalenceClass *ec, SpecialJoinInfo *sjinfo, * drop them. (At this point, any such clauses would be base restriction * clauses, which we'd not need anymore anyway.) */ - ec->ec_derives = NIL; + ec->ec_derive_indexes = NULL; } /* @@ -1504,10 +1514,12 @@ is_innerrel_unique_for(PlannerInfo *root, * delete them. */ static void -update_eclasses(EquivalenceClass *ec, int from, int to) +update_eclasses(PlannerInfo *root, EquivalenceClass *ec, int from, int to) { List *new_members = NIL; - List *new_sources = NIL; + Bitmapset *new_source_indexes = NULL; + int i; + int j; foreach_node(EquivalenceMember, em, ec->ec_members) { @@ -1544,17 +1556,50 @@ update_eclasses(EquivalenceClass *ec, int from, int to) list_free(ec->ec_members); ec->ec_members = new_members; - list_free(ec->ec_derives); - ec->ec_derives = NULL; + i = -1; + while ((i = bms_next_member(ec->ec_derive_indexes, i)) >= 0) + { + RestrictInfo *rinfo = list_nth_node(RestrictInfo, root->eq_derives, i); + + /* + * Remove all references between this RestrictInfo and its relating + * index. + */ + j = -1; + while ((j = bms_next_member(rinfo->clause_relids, j)) >= 0) + { + EquivalenceClassIndexes *indexes = &root->eclass_indexes_array[j]; + + Assert(bms_is_member(i, indexes->derive_indexes)); + indexes->derive_indexes = + bms_del_member(indexes->derive_indexes, i); + } + + /* + * Can't delete the element because we would need to rebuild all the + * eq_derives indexes. But set NULL to detect potential problems. + */ + list_nth_cell(root->eq_derives, i)->ptr_value = NULL; + + /* + * Since this RestrictInfo no longer exists in root->eq_derives, we + * must reset the stored index. + */ + rinfo->eq_derives_index = -1; + } + bms_free(ec->ec_derive_indexes); + ec->ec_derive_indexes = NULL; /* Update EC source expressions */ - foreach_node(RestrictInfo, rinfo, ec->ec_sources) + i = -1; + while ((i = bms_next_member(ec->ec_source_indexes, i)) >= 0) { + RestrictInfo *rinfo = list_nth_node(RestrictInfo, root->eq_sources, i); bool is_redundant = false; if (!bms_is_member(from, rinfo->required_relids)) { - new_sources = lappend(new_sources, rinfo); + new_source_indexes = bms_add_member(new_source_indexes, i); continue; } @@ -1565,8 +1610,11 @@ update_eclasses(EquivalenceClass *ec, int from, int to) * redundancy with existing ones. We don't have to check for * redundancy with derived clauses, because we've just deleted them. */ - foreach_node(RestrictInfo, other, new_sources) + j = -1; + while ((j = bms_next_member(new_source_indexes, j)) >= 0) { + RestrictInfo *other = list_nth_node(RestrictInfo, root->eq_sources, j); + if (!equal(rinfo->clause_relids, other->clause_relids)) continue; @@ -1577,13 +1625,47 @@ update_eclasses(EquivalenceClass *ec, int from, int to) } } - if (!is_redundant) - new_sources = lappend(new_sources, rinfo); + if (is_redundant) + { + /* + * Remove all references between this RestrictInfo and its + * relating index. + */ + j = -1; + while ((j = bms_next_member(rinfo->clause_relids, j)) >= 0) + { + EquivalenceClassIndexes *indexes = &root->eclass_indexes_array[j]; + + Assert(bms_is_member(i, indexes->source_indexes)); + indexes->source_indexes = + bms_del_member(indexes->source_indexes, i); + } + + /* + * Can't delete the element because we would need to rebuild all + * the eq_derives indexes. But set NULL to detect potential + * problems. + */ + list_nth_cell(root->eq_sources, i)->ptr_value = NULL; + + /* + * Since this RestrictInfo no longer exists in root->eq_sources, + * we must reset the stored index. + */ + rinfo->eq_sources_index = -1; + } + else + new_source_indexes = bms_add_member(new_source_indexes, i); } - list_free(ec->ec_sources); - ec->ec_sources = new_sources; + bms_free(ec->ec_source_indexes); + ec->ec_source_indexes = new_source_indexes; ec->ec_relids = adjust_relid_set(ec->ec_relids, from, to); + +#ifdef USE_ASSERT_CHECKING + /* Make sure that we didn't break EquivalenceClass indexes */ + verify_eclass_indexes(root, ec); +#endif } /* @@ -1749,7 +1831,7 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark, { EquivalenceClass *ec = (EquivalenceClass *) list_nth(root->eq_classes, i); - update_eclasses(ec, toRemove->relid, toKeep->relid); + update_eclasses(root, ec, toRemove->relid, toKeep->relid); toKeep->eclass_indexes = bms_add_member(toKeep->eclass_indexes, i); } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index a4d523dcb0f..d2a229776a4 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -665,6 +665,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, PlannerInfo *parent_root, root->multiexpr_params = NIL; root->join_domains = NIL; root->eq_classes = NIL; + root->eq_sources = NIL; + root->eq_derives = NIL; root->ec_merging_done = false; root->last_rinfo_serial = 0; root->all_result_relids = diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index d131a5bbc59..15293ddbcbd 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1296,6 +1296,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->multiexpr_params = NIL; subroot->join_domains = NIL; subroot->eq_classes = NIL; + subroot->eq_sources = NIL; + subroot->eq_derives = NIL; subroot->ec_merging_done = false; subroot->last_rinfo_serial = 0; subroot->all_result_relids = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 5b3dc0d8653..1e543f705a0 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -512,6 +512,8 @@ adjust_appendrel_attrs_mutator(Node *node, newinfo->right_bucketsize = -1; newinfo->left_mcvfreq = -1; newinfo->right_mcvfreq = -1; + newinfo->eq_sources_index = -1; + newinfo->eq_derives_index = -1; return (Node *) newinfo; } diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index a80083d2323..e011da4f6bc 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -238,6 +238,11 @@ make_plain_restrictinfo(PlannerInfo *root, restrictinfo->left_hasheqoperator = InvalidOid; restrictinfo->right_hasheqoperator = InvalidOid; + restrictinfo->eq_sources_index = -1; + restrictinfo->eq_derives_index = -1; + + restrictinfo->root = root; + return restrictinfo; } @@ -394,6 +399,8 @@ commute_restrictinfo(RestrictInfo *rinfo, Oid comm_op) result->right_mcvfreq = rinfo->left_mcvfreq; result->left_hasheqoperator = InvalidOid; result->right_hasheqoperator = InvalidOid; + result->eq_sources_index = -1; + result->eq_derives_index = -1; return result; } diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 1c61085a0a7..92faa486225 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -18,6 +18,7 @@ #include "nodes/nodeFuncs.h" #include "nodes/pathnodes.h" #include "nodes/plannodes.h" +#include "optimizer/paths.h" #include "parser/parse_coerce.h" #include "parser/parse_relation.h" #include "parser/parsetree.h" @@ -649,8 +650,8 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) expression_tree_walker((Node *) rinfo->clause, ChangeVarNodes_walker, (void *) context); expression_tree_walker((Node *) rinfo->orclause, ChangeVarNodes_walker, (void *) context); - rinfo->clause_relids = - adjust_relid_set(rinfo->clause_relids, context->rt_index, context->new_index); + update_clause_relids(rinfo, + adjust_relid_set(rinfo->clause_relids, context->rt_index, context->new_index)); rinfo->num_base_rels = bms_num_members(rinfo->clause_relids); rinfo->left_relids = adjust_relid_set(rinfo->left_relids, context->rt_index, context->new_index); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 9651f52d405..093c40288fd 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -343,6 +343,12 @@ struct PlannerInfo /* list of active EquivalenceClasses */ List *eq_classes; + /* list of source RestrictInfos used to build EquivalenceClasses */ + List *eq_sources; + + /* list of RestrictInfos derived from EquivalenceClasses */ + List *eq_derives; + /* set true once ECs are canonical */ bool ec_merging_done; @@ -1426,6 +1432,24 @@ typedef struct JoinDomain * its comment for usage. The approach to lookup child members quickly is * described as setup_eclass_all_member_iterator_for_relids() comment. * + * At various locations in the query planner, we must search for source and + * derived RestrictInfos regarding a given EquivalenceClass. For the common + * case, an EquivalenceClass does not have a large number of RestrictInfos, + * however, in cases such as planning queries to partitioned tables, the + * number of members can become large. To maintain planning performance, we + * make use of a bitmap index to allow us to quickly find RestrictInfos in a + * given EquivalenceClass belonging to a given relation or set of relations. + * This is done by storing a list of RestrictInfos belonging to all + * EquivalenceClasses in PlannerInfo and storing a Bitmapset for each + * RelOptInfo which has a bit set for each RestrictInfo in that list which + * relates to the given relation. We also store a Bitmapset to mark all of + * the indexes in the PlannerInfo's list of RestrictInfos in the + * EquivalenceClass. We can quickly find the interesting indexes into the + * PlannerInfo's list by performing a bit-wise AND on the RelOptInfo's + * Bitmapset and the EquivalenceClasses. RestrictInfos must be looked up in + * PlannerInfo by this technique using the ec_source_indexes and + * ec_derive_indexes Bitmapsets. + * * 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. * @@ -1444,8 +1468,10 @@ typedef struct EquivalenceClass List *ec_opfamilies; /* btree operator family OIDs */ Oid ec_collation; /* collation, if datatypes are collatable */ List *ec_members; /* list of EquivalenceMembers */ - List *ec_sources; /* list of generating RestrictInfos */ - List *ec_derives; /* list of derived RestrictInfos */ + Bitmapset *ec_source_indexes; /* indexes into PlannerInfo's eq_sources + * list of generating RestrictInfos */ + Bitmapset *ec_derive_indexes; /* indexes into PlannerInfo's eq_derives + * list of derived RestrictInfos */ Relids ec_relids; /* all relids appearing in ec_members, except * for child members (see below) */ bool ec_has_const; /* any pseudoconstants in ec_members? */ @@ -1545,14 +1571,20 @@ typedef struct * * 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. + * EquivalenceMembers and RestrictInfos. 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 */ + Bitmapset *source_indexes; /* Indexes in PlannerInfo's eq_sources list + * for RestrictInfos that mention this + * relation */ + Bitmapset *derive_indexes; /* Indexes in PlannerInfo's eq_derives list + * for RestrictInfos that mention this + * relation */ } EquivalenceClassIndexes; /* @@ -2702,7 +2734,12 @@ typedef struct RestrictInfo /* number of base rels in clause_relids */ int num_base_rels pg_node_attr(equal_ignore); - /* The relids (varnos+varnullingrels) actually referenced in the clause: */ + /* + * The relids (varnos+varnullingrels) actually referenced in the clause. + * + * NOTE: This field must be updated through update_clause_relids(), not by + * setting the field directly. + */ Relids clause_relids pg_node_attr(equal_ignore); /* The set of relids required to evaluate the clause: */ @@ -2820,6 +2857,13 @@ typedef struct RestrictInfo /* hash equality operators used for memoize nodes, else InvalidOid */ Oid left_hasheqoperator pg_node_attr(equal_ignore); Oid right_hasheqoperator pg_node_attr(equal_ignore); + + /* the index within root->eq_sources and root->eq_derives */ + int eq_sources_index pg_node_attr(equal_ignore); + int eq_derives_index pg_node_attr(equal_ignore); + + /* PlannerInfo where this RestrictInfo was created */ + PlannerInfo *root pg_node_attr(copy_as_scalar, equal_ignore, read_write_ignore); } RestrictInfo; /* diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 954ab6e1955..4b8dc7b1d6f 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -132,6 +132,7 @@ extern Expr *canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation); extern void reconsider_outer_join_clauses(PlannerInfo *root); extern void rebuild_eclass_attr_needed(PlannerInfo *root); +extern void update_clause_relids(RestrictInfo *rinfo, Relids new_clause_relids); extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root, Expr *expr, List *opfamilies, @@ -168,7 +169,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2, extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, ForeignKeyOptInfo *fkinfo, int colno); -extern RestrictInfo *find_derived_clause_for_ec_member(EquivalenceClass *ec, +extern RestrictInfo *find_derived_clause_for_ec_member(PlannerInfo *root, + EquivalenceClass *ec, EquivalenceMember *em); extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, @@ -204,6 +206,22 @@ extern bool eclass_useful_for_merging(PlannerInfo *root, extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist); extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo, List *indexclauses); +extern Bitmapset *get_ec_source_indexes(PlannerInfo *root, + EquivalenceClass *ec, + Relids relids); +extern Bitmapset *get_ec_source_indexes_strict(PlannerInfo *root, + EquivalenceClass *ec, + Relids relids); +extern Bitmapset *get_ec_derive_indexes(PlannerInfo *root, + EquivalenceClass *ec, + Relids relids); +extern Bitmapset *get_ec_derive_indexes_strict(PlannerInfo *root, + EquivalenceClass *ec, + Relids relids); +#ifdef USE_ASSERT_CHECKING +extern void verify_eclass_indexes(PlannerInfo *root, + EquivalenceClass *ec); +#endif /* * pathkeys.c -- 2.45.2.windows.1