From 3d7400c22c51389124846e63346c59c685852fa8 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Fri, 24 Jul 2026 22:07:07 +0900 Subject: [PATCH v1] wip: refactor join removal --- src/backend/optimizer/plan/analyzejoins.c | 661 ++-------------------- src/backend/optimizer/plan/planmain.c | 82 ++- src/backend/optimizer/prep/prepjointree.c | 78 +++ src/include/optimizer/planmain.h | 2 +- src/include/optimizer/prep.h | 2 + 5 files changed, 220 insertions(+), 605 deletions(-) diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 881950e5264..9404dfacd29 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -30,6 +30,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "parser/parse_agg.h" #include "rewrite/rewriteManip.h" @@ -55,19 +56,7 @@ bool enable_self_join_elimination; /* local functions */ static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo); -static void remove_leftjoinrel_from_query(PlannerInfo *root, int relid, - SpecialJoinInfo *sjinfo); -static void remove_rel_from_query(PlannerInfo *root, int relid, - int subst, SpecialJoinInfo *sjinfo, - Relids joinrelids); -static void remove_rel_from_restrictinfo(RestrictInfo *rinfo, - int relid, int ojrelid); -static void remove_rel_from_eclass(PlannerInfo *root, EquivalenceClass *ec, - int relid, int ojrelid); -static void remove_rel_from_restrictinfo_phvs(RestrictInfo *rinfo, - int relid, int ojrelid); -static Node *remove_rel_from_phvs(Node *node, int relid, int ojrelid); -static Node *remove_rel_from_phvs_mutator(Node *node, Relids removable); +static void remove_rel_from_query(PlannerInfo *root, int relid, int subst); static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved); static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel); static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, @@ -88,13 +77,14 @@ static bool replace_relid_callback(Node *node, /* * remove_useless_joins * Check for relations that don't actually need to be joined at all, - * and remove them from the query. + * and remove the first such join we find from the query jointree. * - * We are passed the current joinlist and return the updated list. Other - * data structures that have to be updated are accessible via "root". + * Returns true if a join was removed. Removal works on the jointree + * representation, and the caller is responsible for discarding and rebuilding + * all derived planner state before calling us again. */ -List * -remove_useless_joins(PlannerInfo *root, List *joinlist) +bool +remove_useless_joins(PlannerInfo *root) { ListCell *lc; @@ -102,12 +92,10 @@ remove_useless_joins(PlannerInfo *root, List *joinlist) * We are only interested in relations that are left-joined to, so we can * scan the join_info_list to find them easily. */ -restart: foreach(lc, root->join_info_list) { SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc); int innerrelid; - int nremoved; /* Skip if not removable */ if (!join_is_removable(root, sjinfo)) @@ -115,36 +103,20 @@ restart: /* * Currently, join_is_removable can only succeed when the sjinfo's - * righthand is a single baserel. Remove that rel from the query and - * joinlist. + * righthand is a single baserel. Remove that join out of the + * jointree; the caller will rebuild everything else. */ innerrelid = bms_singleton_member(sjinfo->min_righthand); - remove_leftjoinrel_from_query(root, innerrelid, sjinfo); + remove_leftjoin_from_jointree(root, innerrelid, sjinfo->ojrelid); - /* We verify that exactly one reference gets removed from joinlist */ - nremoved = 0; - joinlist = remove_rel_from_joinlist(joinlist, innerrelid, &nremoved); - if (nremoved != 1) - elog(ERROR, "failed to find relation %d in joinlist", innerrelid); - - /* - * We can delete this SpecialJoinInfo from the list too, since it's no - * longer of interest. (Since we'll restart the foreach loop - * immediately, we don't bother with foreach_delete_current.) - */ - root->join_info_list = list_delete_cell(root->join_info_list, lc); + pfree(root->simple_rel_array[innerrelid]); + root->simple_rel_array[innerrelid] = NULL; - /* - * Restart the scan. This is necessary to ensure we find all - * removable joins independently of ordering of the join_info_list - * (note that removal of attr_needed bits may make a join appear - * removable that did not before). - */ - goto restart; + return true; } - return joinlist; + return false; } /* @@ -319,151 +291,17 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) } /* - * Remove the target relid and references to the target join from the - * planner's data structures, having determined that there is no need - * to include them in the query. - * - * We are not terribly thorough here. We only bother to update parts of - * the planner's data structures that will actually be consulted later. - */ -static void -remove_leftjoinrel_from_query(PlannerInfo *root, int relid, - SpecialJoinInfo *sjinfo) -{ - RelOptInfo *rel = find_base_rel(root, relid); - int ojrelid = sjinfo->ojrelid; - Relids joinrelids; - Relids join_plus_commute; - List *joininfos; - ListCell *l; - - /* Compute the relid set for the join we are considering */ - joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand); - Assert(ojrelid != 0); - joinrelids = bms_add_member(joinrelids, ojrelid); - - remove_rel_from_query(root, relid, -1, sjinfo, joinrelids); - - /* - * Remove any joinquals referencing the rel from the joininfo lists. - * - * In some cases, a joinqual has to be put back after deleting its - * reference to the target rel. This can occur for pseudoconstant and - * outerjoin-delayed quals, which can get marked as requiring the rel in - * order to force them to be evaluated at or above the join. We can't - * just discard them, though. Only quals that logically belonged to the - * outer join being discarded should be removed from the query. - * - * We might encounter a qual that is a clone of a deletable qual with some - * outer-join relids added (see deconstruct_distribute_oj_quals). To - * ensure we get rid of such clones as well, add the relids of all OJs - * commutable with this one to the set we test against for - * pushed-down-ness. - */ - join_plus_commute = bms_union(joinrelids, - sjinfo->commute_above_r); - join_plus_commute = bms_add_members(join_plus_commute, - sjinfo->commute_below_l); - - /* - * We must make a copy of the rel's old joininfo list before starting the - * loop, because otherwise remove_join_clause_from_rels would destroy the - * list while we're scanning it. - */ - joininfos = list_copy(rel->joininfo); - foreach(l, joininfos) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - - remove_join_clause_from_rels(root, rinfo, rinfo->required_relids); - - if (RINFO_IS_PUSHED_DOWN(rinfo, join_plus_commute)) - { - /* - * There might be references to relid or ojrelid in the - * RestrictInfo's relid sets, as a consequence of PHVs having had - * ph_eval_at sets that include those. We already checked above - * that any such PHV is safe (and updated its ph_eval_at), so we - * can just drop those references. - */ - remove_rel_from_restrictinfo(rinfo, relid, ojrelid); - - /* - * Cross-check that the clause itself does not reference the - * target rel or join. - */ -#ifdef USE_ASSERT_CHECKING - { - Relids clause_varnos = pull_varnos(root, - (Node *) rinfo->clause); - - Assert(!bms_is_member(relid, clause_varnos)); - Assert(!bms_is_member(ojrelid, clause_varnos)); - } -#endif - /* Now throw it back into the joininfo lists */ - distribute_restrictinfo_to_rels(root, rinfo); - } - } - - /* - * There may be references to the rel in root->fkey_list, but if so, - * match_foreign_keys_to_quals() will get rid of them. - */ - - /* - * Now remove the rel from the baserel array to prevent it from being - * referenced again. (We can't do this earlier because - * remove_join_clause_from_rels will touch it.) - */ - root->simple_rel_array[relid] = NULL; - root->simple_rte_array[relid] = NULL; - - /* And nuke the RelOptInfo, just in case there's another access path */ - pfree(rel); - - /* - * Now repeat construction of attr_needed bits coming from all other - * sources. - */ - rebuild_placeholder_attr_needed(root); - rebuild_joinclause_attr_needed(root); - rebuild_eclass_attr_needed(root); - rebuild_lateral_attr_needed(root); -} - -/* - * Remove the target relid and references to the target join from the - * planner's data structures, having determined that there is no need - * to include them in the query. Optionally replace references to the - * removed relid with subst if this is a self-join removal. - * - * This function serves as the common infrastructure for left-join removal - * and self-join elimination. It is intentionally scoped to update only the - * shared planner data structures that are universally affected by relation - * removal. Each specific caller remains responsible for updating any - * remaining data structures required by its unique removal logic. - * - * The specific type of removal being performed is dictated by the combination - * of the sjinfo and subst parameters. A non-NULL sjinfo indicates left-join - * removal. When sjinfo is NULL, a positive subst value indicates self-join - * elimination (where references are replaced with subst). + * Remove the target relid and references to it from the planner's data + * structures, having determined that there is no need to include it in the + * query. References to the removed relid are replaced with subst. */ static void -remove_rel_from_query(PlannerInfo *root, int relid, - int subst, SpecialJoinInfo *sjinfo, - Relids joinrelids) +remove_rel_from_query(PlannerInfo *root, int relid, int subst) { - int ojrelid = sjinfo ? sjinfo->ojrelid : 0; Index rti; ListCell *l; - bool is_outer_join = (sjinfo != NULL); - bool is_self_join = (!is_outer_join && subst > 0); - Bitmapset *seen_serials = NULL; - Assert(is_outer_join || is_self_join); - Assert(!is_outer_join || ojrelid > 0); - Assert(!is_outer_join || joinrelids != NULL); + Assert(subst > 0); /* * Update all_baserels and related relid sets. @@ -471,20 +309,11 @@ remove_rel_from_query(PlannerInfo *root, int relid, root->all_baserels = adjust_relid_set(root->all_baserels, relid, subst); root->all_query_rels = adjust_relid_set(root->all_query_rels, relid, subst); - if (is_outer_join) - { - root->outer_join_rels = bms_del_member(root->outer_join_rels, ojrelid); - root->all_query_rels = bms_del_member(root->all_query_rels, ojrelid); - } - /* * Likewise remove references from SpecialJoinInfo data structures. * * This is relevant in case the relation we're deleting is part of the - * relid sets of special joins: those sets have to be adjusted. If we are - * removing an outer join, the RHS of the target outer join will be made - * empty here, but that's OK since the caller will delete that - * SpecialJoinInfo entirely. + * relid sets of special joins: those sets have to be adjusted. */ foreach(l, root->join_info_list) { @@ -494,7 +323,6 @@ remove_rel_from_query(PlannerInfo *root, int relid, * initsplan.c is fairly cavalier about allowing SpecialJoinInfos' * lefthand/righthand relid sets to be shared with other data * structures. Ensure that we don't modify the original relid sets. - * (The commute_xxx sets are always per-SpecialJoinInfo though.) */ sjinf->min_lefthand = bms_copy(sjinf->min_lefthand); sjinf->min_righthand = bms_copy(sjinf->min_righthand); @@ -507,125 +335,56 @@ remove_rel_from_query(PlannerInfo *root, int relid, sjinf->syn_lefthand = adjust_relid_set(sjinf->syn_lefthand, relid, subst); sjinf->syn_righthand = adjust_relid_set(sjinf->syn_righthand, relid, subst); - if (is_outer_join) - { - /* Remove ojrelid bit from the sets: */ - sjinf->min_lefthand = bms_del_member(sjinf->min_lefthand, ojrelid); - sjinf->min_righthand = bms_del_member(sjinf->min_righthand, ojrelid); - sjinf->syn_lefthand = bms_del_member(sjinf->syn_lefthand, ojrelid); - sjinf->syn_righthand = bms_del_member(sjinf->syn_righthand, ojrelid); - /* relid cannot appear in these fields, but ojrelid can: */ - sjinf->commute_above_l = bms_del_member(sjinf->commute_above_l, ojrelid); - sjinf->commute_above_r = bms_del_member(sjinf->commute_above_r, ojrelid); - sjinf->commute_below_l = bms_del_member(sjinf->commute_below_l, ojrelid); - sjinf->commute_below_r = bms_del_member(sjinf->commute_below_r, ojrelid); - } - else - { - /* - * For self-join removal, replace relid references in - * semi_rhs_exprs. - */ - ChangeVarNodesExtended((Node *) sjinf->semi_rhs_exprs, relid, subst, - 0, replace_relid_callback); - } + /* Replace relid references in semi_rhs_exprs */ + ChangeVarNodesExtended((Node *) sjinf->semi_rhs_exprs, relid, subst, + 0, replace_relid_callback); } /* - * Likewise remove references from PlaceHolderVar data structures, - * removing any no-longer-needed placeholders entirely. We only remove - * PHVs for left-join removal. With self-join elimination, PHVs already - * get moved to the remaining relation, where they might still be needed. - * It might also happen that we skip the removal of some PHVs that could - * be removed. However, the overhead of extra PHVs is small compared to - * the complexity of analysis needed to remove them. - * - * Removal is a bit trickier than it might seem: we can remove PHVs that - * are used at the target rel and/or in the join qual, but not those that - * are used at join partner rels or above the join. It's not that easy to - * distinguish PHVs used at partner rels from those used in the join qual, - * since they will both have ph_needed sets that are subsets of - * joinrelids. However, a PHV used at a partner rel could not have the - * target rel in ph_eval_at, so we check that while deciding whether to - * remove or just update the PHV. There is no corresponding test in - * join_is_removable because it doesn't need to distinguish those cases. + * Likewise remove references from PlaceHolderVar data structures. With + * self-join elimination, PHVs get moved to the remaining relation, where + * they might still be needed, so we don't remove any of them. */ foreach(l, root->placeholder_list) { PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l); + PlaceHolderVar *phv = phinfo->ph_var; - Assert(!is_outer_join || !bms_is_member(relid, phinfo->ph_lateral)); + phinfo->ph_eval_at = adjust_relid_set(phinfo->ph_eval_at, relid, subst); + Assert(!bms_is_empty(phinfo->ph_eval_at)); /* checked previously */ - if (is_outer_join && - bms_is_subset(phinfo->ph_needed, joinrelids) && - bms_is_member(relid, phinfo->ph_eval_at) && - !bms_is_member(ojrelid, phinfo->ph_eval_at)) - { - root->placeholder_list = foreach_delete_current(root->placeholder_list, - l); - root->placeholder_array[phinfo->phid] = NULL; - } + /* Reduce ph_needed to contain only "relation 0"; see below */ + if (bms_is_member(0, phinfo->ph_needed)) + phinfo->ph_needed = bms_make_singleton(0); else - { - PlaceHolderVar *phv = phinfo->ph_var; - - phinfo->ph_eval_at = adjust_relid_set(phinfo->ph_eval_at, relid, subst); - if (is_outer_join) - phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, ojrelid); - Assert(!bms_is_empty(phinfo->ph_eval_at)); /* checked previously */ + phinfo->ph_needed = NULL; - /* Reduce ph_needed to contain only "relation 0"; see below */ - if (bms_is_member(0, phinfo->ph_needed)) - phinfo->ph_needed = bms_make_singleton(0); - else - phinfo->ph_needed = NULL; + phv->phrels = adjust_relid_set(phv->phrels, relid, subst); + Assert(!bms_is_empty(phv->phrels)); - phv->phrels = adjust_relid_set(phv->phrels, relid, subst); - if (is_outer_join) - phv->phrels = bms_del_member(phv->phrels, ojrelid); - Assert(!bms_is_empty(phv->phrels)); - - /* - * For self-join removal, update Var nodes within the PHV's - * expression to reference the replacement relid, and adjust - * ph_lateral for the relid substitution. (For left-join removal, - * we're removing rather than replacing, and any surviving PHV - * shouldn't reference the removed rel in its expression. Also, - * relid can't appear in ph_lateral for outer joins.) - */ - if (is_self_join) - { - ChangeVarNodesExtended((Node *) phv->phexpr, relid, subst, 0, - replace_relid_callback); - phinfo->ph_lateral = adjust_relid_set(phinfo->ph_lateral, relid, subst); + /* + * Update Var nodes within the PHV's expression to reference the + * replacement relid, and adjust ph_lateral for the relid + * substitution. + */ + ChangeVarNodesExtended((Node *) phv->phexpr, relid, subst, 0, + replace_relid_callback); + phinfo->ph_lateral = adjust_relid_set(phinfo->ph_lateral, relid, subst); - /* - * ph_lateral might contain rels mentioned in ph_eval_at after - * the replacement, remove them. - */ - phinfo->ph_lateral = bms_difference(phinfo->ph_lateral, phinfo->ph_eval_at); - /* ph_lateral might or might not be empty */ - } + /* + * ph_lateral might contain rels mentioned in ph_eval_at after the + * replacement, remove them. + */ + phinfo->ph_lateral = bms_difference(phinfo->ph_lateral, phinfo->ph_eval_at); + /* ph_lateral might or might not be empty */ - Assert(phv->phnullingrels == NULL); /* no need to adjust */ - } + Assert(phv->phnullingrels == NULL); /* no need to adjust */ } /* - * Likewise remove references from EquivalenceClasses. - * - * For self-join removal, the caller has already updated the - * EquivalenceClasses, so we can skip this step. + * The EquivalenceClasses have already been updated by the caller + * (update_eclasses), so we don't need to touch them here. */ - if (is_outer_join) - { - foreach(l, root->eq_classes) - { - EquivalenceClass *ec = (EquivalenceClass *) lfirst(l); - - remove_rel_from_eclass(root, ec, relid, ojrelid); - } - } /* * Finally, we must prepare for the caller to recompute per-Var @@ -642,14 +401,8 @@ remove_rel_from_query(PlannerInfo *root, int relid, * bits from the existing relid sets. This is safe because we'd never * remove such references. * - * Additionally, if we are performing self-join elimination, we must - * replace references to the removed relid with subst within the - * lateral_vars lists. - * - * Also, for left-join removal, we strip the removed rel and join from any - * PlaceHolderVar embedded in the surviving rels' restriction clauses and - * join clauses; we needn't bother with the rel being removed, nor when - * the query has no PlaceHolderVars. + * We must also replace references to the removed relid with subst within + * the lateral_vars lists. */ for (rti = 1; rti < root->simple_rel_array_size; rti++) { @@ -672,307 +425,9 @@ remove_rel_from_query(PlannerInfo *root, int relid, otherrel->attr_needed[attroff] = NULL; } - if (is_self_join) - ChangeVarNodesExtended((Node *) otherrel->lateral_vars, relid, - subst, 0, replace_relid_callback); - - if (is_outer_join && rti != relid && root->glob->lastPHId != 0) - { - foreach_node(RestrictInfo, rinfo, otherrel->baserestrictinfo) - remove_rel_from_restrictinfo_phvs(rinfo, relid, ojrelid); - - /* - * Join clauses need the same treatment, but there's no value in - * processing any join clause more than once. So it's slightly - * annoying that we have to find them via the per-base-relation - * joininfo lists. Avoid duplicate processing by tracking the - * rinfo_serial numbers of join clauses we've already seen. (This - * doesn't work for is_clone clauses, so we must waste effort on - * them.) - */ - foreach_node(RestrictInfo, rinfo, otherrel->joininfo) - { - if (!rinfo->is_clone) /* else serial number is not unique */ - { - if (bms_is_member(rinfo->rinfo_serial, seen_serials)) - continue; /* saw it already */ - seen_serials = bms_add_member(seen_serials, - rinfo->rinfo_serial); - } - remove_rel_from_restrictinfo_phvs(rinfo, relid, ojrelid); - } - } - } -} - -/* - * Remove any references to relid or ojrelid from the RestrictInfo. - * - * We only bother to clean out bits in the RestrictInfo's various relid sets, - * not nullingrel bits in contained Vars and PHVs. (This might have to be - * improved sometime.) However, if the RestrictInfo contains an OR clause - * we have to also clean up the sub-clauses. - */ -static void -remove_rel_from_restrictinfo(RestrictInfo *rinfo, int relid, int ojrelid) -{ - /* - * 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.) - */ - 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); - /* Likewise for incompatible_relids */ - rinfo->incompatible_relids = bms_copy(rinfo->incompatible_relids); - rinfo->incompatible_relids = bms_del_member(rinfo->incompatible_relids, relid); - rinfo->incompatible_relids = bms_del_member(rinfo->incompatible_relids, ojrelid); - /* Likewise for outer_relids */ - rinfo->outer_relids = bms_copy(rinfo->outer_relids); - rinfo->outer_relids = bms_del_member(rinfo->outer_relids, relid); - rinfo->outer_relids = bms_del_member(rinfo->outer_relids, ojrelid); - /* Likewise for left_relids */ - rinfo->left_relids = bms_copy(rinfo->left_relids); - rinfo->left_relids = bms_del_member(rinfo->left_relids, relid); - rinfo->left_relids = bms_del_member(rinfo->left_relids, ojrelid); - /* Likewise for right_relids */ - rinfo->right_relids = bms_copy(rinfo->right_relids); - rinfo->right_relids = bms_del_member(rinfo->right_relids, relid); - rinfo->right_relids = bms_del_member(rinfo->right_relids, ojrelid); - - /* If it's an OR, recurse to clean up sub-clauses */ - if (restriction_is_or_clause(rinfo)) - { - ListCell *lc; - - Assert(is_orclause(rinfo->orclause)); - foreach(lc, ((BoolExpr *) rinfo->orclause)->args) - { - Node *orarg = (Node *) lfirst(lc); - - /* OR arguments should be ANDs or sub-RestrictInfos */ - if (is_andclause(orarg)) - { - List *andargs = ((BoolExpr *) orarg)->args; - ListCell *lc2; - - foreach(lc2, andargs) - { - RestrictInfo *rinfo2 = lfirst_node(RestrictInfo, lc2); - - remove_rel_from_restrictinfo(rinfo2, relid, ojrelid); - } - } - else - { - RestrictInfo *rinfo2 = castNode(RestrictInfo, orarg); - - remove_rel_from_restrictinfo(rinfo2, relid, ojrelid); - } - } - } -} - -/* - * Remove any references to relid or ojrelid from the EquivalenceClass. - * - * We fix the EC and EM relid sets to ensure that implied join equalities will - * be generated at the appropriate join level(s). We also strip the removed - * rel from PlaceHolderVars embedded in member expressions; a member's - * em_relids reflects ph_eval_at rather than the PHV's phrels, so the latter - * can still mention the removed rel even when em_relids does not. Like - * remove_rel_from_restrictinfo, we don't bother with nullingrel bits in - * contained plain Vars. - */ -static void -remove_rel_from_eclass(PlannerInfo *root, EquivalenceClass *ec, - int relid, int ojrelid) -{ - ListCell *lc; - - /* - * Strip the removed rel/join from PlaceHolderVars in member expressions. - * This is needed even when the EC's relids don't mention the removed rel. - * Plain Vars and Consts can't contain a PlaceHolderVar, so skip them. - */ - if (root->glob->lastPHId != 0) - { - foreach_node(EquivalenceMember, em, ec->ec_members) - { - if (!IsA(em->em_expr, Var) && !IsA(em->em_expr, Const)) - em->em_expr = (Expr *) - remove_rel_from_phvs((Node *) em->em_expr, relid, ojrelid); - } - } - - if (!bms_is_member(relid, ec->ec_relids) && - !bms_is_member(ojrelid, ec->ec_relids)) - return; - - /* Fix up the EC's overall relids */ - ec->ec_relids = bms_del_member(ec->ec_relids, relid); - ec->ec_relids = bms_del_member(ec->ec_relids, ojrelid); - - /* - * We don't expect any EC child members to exist at this point. Ensure - * that's the case, otherwise, we might be getting asked to do something - * this function hasn't been coded for. - */ - Assert(ec->ec_childmembers == NULL); - - /* - * Fix up the member expressions. Any non-const member that ends with - * empty em_relids must be a Var or PHV of the removed relation. We don't - * need it anymore, so we can drop it. - */ - foreach(lc, ec->ec_members) - { - EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); - - if (bms_is_member(relid, cur_em->em_relids) || - bms_is_member(ojrelid, cur_em->em_relids)) - { - Assert(!cur_em->em_is_const); - /* em_relids is likely to be shared with some RestrictInfo */ - cur_em->em_relids = bms_copy(cur_em->em_relids); - cur_em->em_relids = bms_del_member(cur_em->em_relids, relid); - cur_em->em_relids = bms_del_member(cur_em->em_relids, ojrelid); - if (bms_is_empty(cur_em->em_relids)) - ec->ec_members = foreach_delete_current(ec->ec_members, lc); - } - } - - /* Fix up the source clauses, in case we can re-use them later */ - foreach(lc, ec->ec_sources) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - - remove_rel_from_restrictinfo(rinfo, relid, ojrelid); - } - - /* - * Rather than expend code on fixing up any already-derived clauses, just - * drop them. (At this point, any such clauses would be base restriction - * clauses, which we'd not need anymore anyway.) - */ - ec_clear_derived_clauses(ec); -} - -/* - * Remove any references to relid or ojrelid from the PlaceHolderVars embedded - * in a RestrictInfo's clause. - * - * If it's an OR clause, we must also fix up the orclause, which is a parallel - * representation built from its own sub-RestrictInfos. We recurse into the - * sub-clauses for that, mirroring remove_rel_from_restrictinfo. - */ -static void -remove_rel_from_restrictinfo_phvs(RestrictInfo *rinfo, int relid, int ojrelid) -{ - rinfo->clause = (Expr *) - remove_rel_from_phvs((Node *) rinfo->clause, relid, ojrelid); - - /* If it's an OR, recurse to clean up sub-clauses */ - if (restriction_is_or_clause(rinfo)) - { - ListCell *lc; - - Assert(is_orclause(rinfo->orclause)); - foreach(lc, ((BoolExpr *) rinfo->orclause)->args) - { - Node *orarg = (Node *) lfirst(lc); - - /* OR arguments should be ANDs or sub-RestrictInfos */ - if (is_andclause(orarg)) - { - List *andargs = ((BoolExpr *) orarg)->args; - ListCell *lc2; - - foreach(lc2, andargs) - { - RestrictInfo *rinfo2 = lfirst_node(RestrictInfo, lc2); - - remove_rel_from_restrictinfo_phvs(rinfo2, relid, ojrelid); - } - } - else - { - RestrictInfo *rinfo2 = castNode(RestrictInfo, orarg); - - remove_rel_from_restrictinfo_phvs(rinfo2, relid, ojrelid); - } - } - } -} - -/* - * Remove any references to the specified RT index(es) from the phrels (and - * phnullingrels) of every PlaceHolderVar in the given expression. - * - * remove_rel_from_query() fixes up the relid sets of RestrictInfos and - * EquivalenceMembers, but not the PlaceHolderVars embedded in their - * expressions. That's normally fine, but such an expression may later be - * translated for an appendrel child and have its relids recomputed by - * pull_varnos(). A leftover removed relid in phrels would then make - * pull_varnos() reference a nonexistent rel, so we strip it here to match the - * canonical PlaceHolderVar. - */ -static Node * -remove_rel_from_phvs(Node *node, int relid, int ojrelid) -{ - Relids removable = bms_add_member(bms_make_singleton(relid), ojrelid); - - return remove_rel_from_phvs_mutator(node, removable); -} - -static Node * -remove_rel_from_phvs_mutator(Node *node, Relids removable) -{ - if (node == NULL) - return NULL; - if (IsA(node, PlaceHolderVar)) - { - PlaceHolderVar *phv = (PlaceHolderVar *) node; - Relids newphrels; - - /* Upper-level PlaceHolderVars should be long gone at this point */ - Assert(phv->phlevelsup == 0); - - /* Copy the PlaceHolderVar and mutate what's below ... */ - phv = (PlaceHolderVar *) - expression_tree_mutator(node, - remove_rel_from_phvs_mutator, - removable); - - /* - * ... then strip the removed rels from its relid sets. - * - * If stripping would empty phrels, the PHV is evaluated only at the - * removed relation(s); it then belongs to an EquivalenceMember that - * the caller drops immediately afterwards. Leave such a PHV - * untouched rather than build one with empty phrels, which the rest - * of the planner assumes never occurs. - */ - newphrels = bms_difference(phv->phrels, removable); - if (!bms_is_empty(newphrels)) - { - phv->phrels = newphrels; - phv->phnullingrels = bms_difference(phv->phnullingrels, - removable); - } - - return (Node *) phv; + ChangeVarNodesExtended((Node *) otherrel->lateral_vars, relid, + subst, 0, replace_relid_callback); } - return expression_tree_mutator(node, - remove_rel_from_phvs_mutator, - removable); } /* @@ -2172,7 +1627,7 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark, 0, replace_relid_callback); /* Replace links in the planner info */ - remove_rel_from_query(root, toRemove->relid, toKeep->relid, NULL, NULL); + remove_rel_from_query(root, toRemove->relid, toKeep->relid); /* Replace varno in the fully-processed targetlist */ ChangeVarNodesExtended((Node *) root->processed_tlist, toRemove->relid, diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 02495e22e24..ff15dfab96d 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -20,6 +20,8 @@ */ #include "postgres.h" +#include + #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/optimizer.h" @@ -28,6 +30,10 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/tlist.h" + + +static void reset_query_planner(PlannerInfo *root); /* @@ -175,6 +181,12 @@ query_planner(PlannerInfo *root, /* Remove any redundant GROUP BY columns */ remove_useless_groupby_columns(root); + /* + * If we successfully removed any joins, throw away all the derived data + * and loop back around. + */ +restart: + /* * Examine the targetlist and join tree, adding entries to baserel * targetlists for all referenced Vars, and generating PlaceHolderInfo @@ -228,7 +240,11 @@ query_planner(PlannerInfo *root, * jointree preprocessing, but the necessary information isn't available * until we've built baserel data structures and classified qual clauses. */ - joinlist = remove_useless_joins(root, joinlist); + if (remove_useless_joins(root)) + { + reset_query_planner(root); + goto restart; + } /* * Also, reduce any semijoins with unique inner rels to plain inner joins. @@ -303,3 +319,67 @@ query_planner(PlannerInfo *root, return final_rel; } + +static void +reset_query_planner(PlannerInfo *root) +{ + int i; + + /* Relids sets rebuilt by deconstruct_jointree() */ + root->all_baserels = NULL; + root->outer_join_rels = NULL; + root->all_query_rels = NULL; + + /* join-order and equivalence-class */ + root->join_domains = list_make1(makeNode(JoinDomain)); + root->eq_classes = NIL; + root->ec_merging_done = false; + root->canon_pathkeys = NIL; + root->left_join_clauses = NIL; + root->right_join_clauses = NIL; + root->full_join_clauses = NIL; + root->join_info_list = NIL; + + /* RestrictInfo serial numbers */ + root->last_rinfo_serial = 0; + + /* PlaceHolderInfos */ + root->placeholder_list = NIL; + root->placeholder_array = NULL; + root->placeholder_array_size = 0; + root->placeholdersFrozen = false; + + root->hasPseudoConstantQuals = false; + + /* pathkeys and processed clause lists recomputed by the qp_callback */ + root->query_pathkeys = NIL; + root->group_pathkeys = NIL; + root->num_groupby_pathkeys = 0; + root->window_pathkeys = NIL; + root->distinct_pathkeys = NIL; + root->sort_pathkeys = NIL; + root->setop_pathkeys = NIL; + root->processed_distinctClause = NIL; + + for (i = 1; i < root->simple_rel_array_size; i++) + { + RelOptInfo *rel = root->simple_rel_array[i]; + int attroff; + + if (rel == NULL) + continue; + + rel->reltarget = create_empty_pathtarget(); + for (attroff = rel->max_attr - rel->min_attr; attroff >= 0; attroff--) + rel->attr_needed[attroff] = NULL; + rel->lateral_vars = NIL; + rel->nulling_relids = NULL; + rel->baserestrictinfo = NIL; + rel->baserestrict_min_security = UINT_MAX; + rel->joininfo = NIL; + rel->has_eclass_joins = false; + rel->eclass_indexes = NULL; + rel->unique_for_rels = NIL; + rel->non_unique_for_rels = NIL; + } +} diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 72d39ba30fd..7fd77964034 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -171,6 +171,8 @@ static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, Relids *dropped_outer_joins); static int get_result_relid(PlannerInfo *root, Node *jtnode); static void remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc); +static Node *remove_leftjoin_recurse(PlannerInfo *root, Node *jtnode, + int relid, int ojrelid); static bool find_dependent_phvs(PlannerInfo *root, int varno, Relids baserels); static bool find_dependent_phvs_in_jointree(PlannerInfo *root, Node *node, int varno, @@ -4300,6 +4302,82 @@ remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc) */ } +/* + * remove_leftjoin_from_jointree + * Remove a provably-useless left join out of the query jointree. + */ +void +remove_leftjoin_from_jointree(PlannerInfo *root, int relid, int ojrelid) +{ + root->parse->jointree = (FromExpr *) + remove_leftjoin_recurse(root, (Node *) root->parse->jointree, + relid, ojrelid); + Assert(IsA(root->parse->jointree, FromExpr)); + + remove_nulling_relids((Node *) root->parse, + bms_make_singleton(ojrelid), NULL); + remove_nulling_relids((Node *) root->append_rel_list, + bms_make_singleton(ojrelid), NULL); +} + +/* + * Recursive guts of remove_leftjoin_from_jointree: find the JoinExpr whose + * rtindex is ojrelid and return its left input in place of the join. + */ +static Node * +remove_leftjoin_recurse(PlannerInfo *root, Node *jtnode, + int relid, int ojrelid) +{ + if (jtnode == NULL) + return NULL; + + if (IsA(jtnode, RangeTblRef)) + { + /* nothing to do */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + foreach(l, f->fromlist) + lfirst(l) = remove_leftjoin_recurse(root, (Node *) lfirst(l), + relid, ojrelid); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + j->larg = remove_leftjoin_recurse(root, j->larg, relid, ojrelid); + j->rarg = remove_leftjoin_recurse(root, j->rarg, relid, ojrelid); + + if (j->rtindex == ojrelid) + { + Assert(j->jointype == JOIN_LEFT); + + /* Fix up PlaceHolderVars as needed */ + if (root->glob->lastPHId != 0) + { + Relids subrelids; + + subrelids = get_relids_in_jointree(j->larg, true, false); + Assert(!bms_is_empty(subrelids)); + substitute_phv_relids((Node *) root->parse, relid, subrelids); + substitute_phv_relids((Node *) root->parse, ojrelid, subrelids); + fix_append_rel_relids(root, relid, subrelids); + fix_append_rel_relids(root, ojrelid, subrelids); + } + + /* Replace the join with its left input */ + return j->larg; + } + } + else + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode)); + + return jtnode; +} + /* * find_dependent_phvs - are there any PlaceHolderVars whose base relids are diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 71c043a25e8..bce4db8faed 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -108,7 +108,7 @@ extern void match_foreign_keys_to_quals(PlannerInfo *root); /* * prototypes for plan/analyzejoins.c */ -extern List *remove_useless_joins(PlannerInfo *root, List *joinlist); +extern bool remove_useless_joins(PlannerInfo *root); extern void reduce_unique_semijoins(PlannerInfo *root); extern bool query_supports_distinctness(Query *query); extern bool query_is_distinct_for(Query *query, List *distinct_cols); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 00bc567da76..1dc75ce332e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -30,6 +30,8 @@ extern void pull_up_subqueries(PlannerInfo *root); extern void flatten_simple_union_all(PlannerInfo *root); extern void reduce_outer_joins(PlannerInfo *root); extern void remove_useless_result_rtes(PlannerInfo *root); +extern void remove_leftjoin_from_jointree(PlannerInfo *root, int relid, + int ojrelid); extern Relids get_relids_in_jointree(Node *jtnode, bool include_outer_joins, bool include_inner_joins); extern Relids get_relids_for_join(Query *query, int joinrelid); -- 2.39.5 (Apple Git-154)