diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 0dcc2ed4266..393a7a69742 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -89,6 +89,7 @@ static void ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec); static void ec_add_derived_clauses(EquivalenceClass *ec, List *clauses); static void ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause); static void ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo); +static void ec_clear_derived_clauses(EquivalenceClass *ec); static RestrictInfo *ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec, EquivalenceMember *leftem, EquivalenceMember *rightem, @@ -1451,8 +1452,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, * For the moment we force all the Vars to be available at all join nodes * for this eclass. Perhaps this could be improved by doing some * pre-analysis of which members we prefer to join, but it's no worse than - * what happened in the pre-8.3 code. (Note: rebuild_eclass_attr_needed - * needs to match this code.) + * what happened in the pre-8.3 code. */ foreach(lc, ec->ec_members) { @@ -2559,51 +2559,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo) return false; /* failed to make any deduction */ } -/* - * rebuild_eclass_attr_needed - * Put back attr_needed bits for Vars/PHVs needed for join eclasses. - * - * This is used to rebuild attr_needed/ph_needed sets after removal of a - * useless outer join. It should match what - * generate_base_implied_equalities_no_const did, except that we call - * add_vars_to_attr_needed not add_vars_to_targetlist. - */ -void -rebuild_eclass_attr_needed(PlannerInfo *root) -{ - ListCell *lc; - - foreach(lc, root->eq_classes) - { - EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc); - - /* - * 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); - - /* Need do anything only for a multi-member, no-const EC. */ - if (list_length(ec->ec_members) > 1 && !ec->ec_has_const) - { - ListCell *lc2; - - foreach(lc2, ec->ec_members) - { - EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); - List *vars = pull_var_clause((Node *) cur_em->em_expr, - PVC_RECURSE_AGGREGATES | - PVC_RECURSE_WINDOWFUNCS | - PVC_INCLUDE_PLACEHOLDERS); - - add_vars_to_attr_needed(root, vars, ec->ec_relids); - list_free(vars); - } - } - } -} - /* * find_join_domain * Find the highest JoinDomain enclosed within the given relid set. @@ -3825,7 +3780,7 @@ ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo) * when thousands of partitions are involved, so we free it as well -- even * though we do not typically free lists. */ -void +static void ec_clear_derived_clauses(EquivalenceClass *ec) { list_free(ec->ec_derives_list); diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index cbd36f85417..2252185564c 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -7,9 +7,14 @@ * certain optimizations cannot be performed at that stage for lack of * detailed information about the query. The routines here are invoked * after initsplan.c has done its work, and can do additional join removal - * and simplification steps based on the information extracted. The penalty - * is that we have to work harder to clean up after ourselves when we modify - * the query, since the derived data structures have to be updated too. + * and simplification steps based on the information extracted. + * + * Although the decisions about what can be removed are made using the + * planner's derived data structures, the removals themselves are implemented + * by editing the query's jointree, which is a far simpler and more stable + * representation. We make no attempt to update the derived data structures + * to match; instead, query_planner() throws them all away and recomputes them + * whenever we report having removed something. * * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -23,13 +28,13 @@ #include "postgres.h" #include "catalog/pg_class.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/joininfo.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #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,24 +60,23 @@ 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 List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved); +static Node *remove_join_from_jointree(Node *jtnode, int ojrelid, + int *nremoved); +static void remove_rels_from_query_tree(PlannerInfo *root, + Relids removed_relids); +static bool reduce_semijoin_in_jointree(Node *jtnode, Relids syn_righthand); static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel); static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list, List **extra_clauses); static DistinctColInfo *distinct_col_search(int colno, List *distinct_cols); +static bool innerrel_is_unique_ext(PlannerInfo *root, + Relids joinrelids, + Relids outerrelids, + RelOptInfo *innerrel, + JoinType jointype, + List *restrictlist, + bool force_cache, + List **extra_clauses); static bool is_innerrel_unique_for(PlannerInfo *root, Relids joinrelids, Relids outerrelids, @@ -80,71 +84,100 @@ static bool is_innerrel_unique_for(PlannerInfo *root, JoinType jointype, List *restrictlist, List **extra_clauses); +static Node *remove_rel_from_jointree(Node *jtnode, int relid, + Node **orphan_quals, int *nremoved); +static Node *merge_quals(Node *quals1, Node *quals2); +static void fixup_selfjoin_jointree(PlannerInfo *root, Node *jtnode, int relid, + Node **hoist_quals, bool *found_relid); +static List *fixup_selfjoin_quals(PlannerInfo *root, List *quals, int relid); +static Node *replace_selfjoin_qual(Node *qual); static int self_join_candidates_cmp(const void *a, const void *b); -static bool replace_relid_callback(Node *node, - ChangeVarNodes_context *context); /* - * remove_useless_joins + * remove_useless_outer_joins * Check for relations that don't actually need to be joined at all, - * and remove them from the query. + * and remove them from the query's 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 we removed anything. In that case the caller must discard + * everything it has derived from the jointree and compute it over again, + * since we don't try to update any of that here. */ -List * -remove_useless_joins(PlannerInfo *root, List *joinlist) +bool +remove_useless_outer_joins(PlannerInfo *root) { + Relids removed_relids = NULL; ListCell *lc; /* * 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; + RangeTblEntry *rte; /* Skip if not removable */ if (!join_is_removable(root, sjinfo)) continue; /* - * Currently, join_is_removable can only succeed when the sjinfo's - * righthand is a single baserel. Remove that rel from the query and - * joinlist. + * join_is_removable insists that the join's syntactic righthand side + * be a single baserel, so we can implement the removal by dropping + * the JoinExpr and everything below its righthand side. */ - innerrelid = bms_singleton_member(sjinfo->min_righthand); + innerrelid = bms_singleton_member(sjinfo->syn_righthand); - remove_leftjoinrel_from_query(root, innerrelid, sjinfo); - - /* We verify that exactly one reference gets removed from joinlist */ + /* We verify that exactly one JoinExpr gets removed */ nremoved = 0; - joinlist = remove_rel_from_joinlist(joinlist, innerrelid, &nremoved); + root->parse->jointree = (FromExpr *) + remove_join_from_jointree((Node *) root->parse->jointree, + sjinfo->ojrelid, &nremoved); if (nremoved != 1) - elog(ERROR, "failed to find relation %d in joinlist", innerrelid); + elog(ERROR, "failed to find join %d in jointree", sjinfo->ojrelid); + + /* Track all the relids we've removed, for use below */ + removed_relids = bms_add_member(removed_relids, innerrelid); + removed_relids = bms_add_member(removed_relids, sjinfo->ojrelid); /* - * 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.) + * As in pull_up_simple_subquery, discard no-longer-needed subqueries. + * This is not just an optimization, but is necessary to prevent + * subsequent processing from descending into stale subtrees and + * seeing inconsistent data. Likewise discard any securityQuals of + * the removed rel. (Although simple_rte_array[] will be rebuilt + * shortly, we can still use it to find the RTE in the parse tree.) */ - root->join_info_list = list_delete_cell(root->join_info_list, lc); + rte = root->simple_rte_array[innerrelid]; + if (rte->rtekind == RTE_SUBQUERY) + rte->subquery = NULL; + rte->securityQuals = NIL; /* - * 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). + * It's okay to keep scanning join_info_list for more removable joins, + * even though the data that join_is_removable consults is now + * slightly out of date. Removing a join can only delete attr_needed + * bits and join clauses, and any attr_needed bit or join clause that + * mentions the removed rel above its own join level would have + * prevented that rel from being removable. So what remains to be + * examined is unchanged by what we just did. + * + * The converse doesn't hold: dropping a join can make some other join + * removable that didn't look so before. That's why our caller loops + * until we report finding nothing more to remove. */ - goto restart; } - return joinlist; + if (bms_is_empty(removed_relids)) + return false; + + /* Clean up the traces that the removed rels have left elsewhere */ + remove_rels_from_query_tree(root, removed_relids); + + return true; } /* @@ -176,8 +209,15 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo) if (sjinfo->jointype != JOIN_LEFT) return false; - if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid)) + /* + * We test the syntactic righthand side, not min_righthand, because the + * removal is done by deleting the whole righthand subtree of the join. + * (min_righthand can be a singleton when syn_righthand is not, but in + * such a case the attr_needed tests below would reject the join anyway.) + */ + if (!bms_get_singleton_member(sjinfo->syn_righthand, &innerrelid)) return false; + Assert(bms_equal(sjinfo->min_righthand, sjinfo->syn_righthand)); /* * Never try to eliminate a left join to the query result rel. Although @@ -319,712 +359,90 @@ 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. + * remove_join_from_jointree + * Delete the JoinExpr with the given RT index, along with everything + * below its righthand side, from the query's jointree. * - * 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. + * The JoinExpr is replaced by its lefthand input. Its ON conditions can just + * be dropped: since this is a left join, they could only have determined + * which righthand rows join to a given lefthand row, and there are no + * righthand rows anymore. * - * 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). - */ -static void -remove_rel_from_query(PlannerInfo *root, int relid, - int subst, SpecialJoinInfo *sjinfo, - Relids joinrelids) -{ - 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); - - /* - * Update all_baserels and related relid sets. - */ - 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. - */ - foreach(l, root->join_info_list) - { - SpecialJoinInfo *sjinf = (SpecialJoinInfo *) lfirst(l); - - /* - * 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); - sjinf->syn_lefthand = bms_copy(sjinf->syn_lefthand); - sjinf->syn_righthand = bms_copy(sjinf->syn_righthand); - - /* Now adjust relid bit in the sets: */ - sjinf->min_lefthand = adjust_relid_set(sjinf->min_lefthand, relid, subst); - sjinf->min_righthand = adjust_relid_set(sjinf->min_righthand, relid, subst); - 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); - } - } - - /* - * 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. - */ - foreach(l, root->placeholder_list) - { - PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l); - - Assert(!is_outer_join || !bms_is_member(relid, phinfo->ph_lateral)); - - 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; - } - 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 */ - - /* 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); - 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); - - /* - * 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 */ - } - } - - /* - * Likewise remove references from EquivalenceClasses. - * - * For self-join removal, the caller has already updated the - * EquivalenceClasses, so we can skip this step. - */ - 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 - * attr_needed and per-PlaceHolderVar ph_needed relid sets. These have to - * be known accurately, else we may fail to remove other now-removable - * joins. Because the caller removes the join clause(s) associated with - * the removed join, Vars that were formerly needed may no longer be. - * - * The actual reconstruction of these relid sets is performed by the - * specific caller. Here, we simply clear out the existing attr_needed - * sets (we already did this above for ph_needed) to ensure they are - * rebuilt from scratch. We can cheat to one small extent: we can avoid - * re-examining the targetlist and HAVING qual by preserving "relation 0" - * 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. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *otherrel = root->simple_rel_array[rti]; - int attroff; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (otherrel == NULL) - continue; - - Assert(otherrel->relid == rti); /* sanity check on array */ - - for (attroff = otherrel->max_attr - otherrel->min_attr; - attroff >= 0; - attroff--) - { - if (bms_is_member(0, otherrel->attr_needed[attroff])) - otherrel->attr_needed[attroff] = bms_make_singleton(0); - else - 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. + * *nremoved is incremented by the number of JoinExprs removed (there should + * be exactly one, but the caller checks that). */ static Node * -remove_rel_from_phvs(Node *node, int relid, int ojrelid) +remove_join_from_jointree(Node *jtnode, int ojrelid, int *nremoved) { - 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) + if (jtnode == NULL) return NULL; - if (IsA(node, PlaceHolderVar)) + if (IsA(jtnode, RangeTblRef)) { - 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; + /* nothing to do here */ } - return expression_tree_mutator(node, - remove_rel_from_phvs_mutator, - removable); + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + foreach(l, f->fromlist) + lfirst(l) = remove_join_from_jointree((Node *) lfirst(l), + ojrelid, nremoved); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + if (j->rtindex == ojrelid) + { + (*nremoved)++; + return j->larg; + } + j->larg = remove_join_from_jointree(j->larg, ojrelid, nremoved); + j->rarg = remove_join_from_jointree(j->rarg, ojrelid, nremoved); + } + else + elog(ERROR, "unrecognized jointree node type: %d", + (int) nodeTag(jtnode)); + + return jtnode; } /* - * Remove any occurrences of the target relid from a joinlist structure. + * remove_rels_from_query_tree + * Delete all remaining references to the given relids from the query. * - * It's easiest to build a whole new list structure, so we handle it that - * way. Efficiency is not a big deal here. - * - * *nremoved is incremented by the number of occurrences removed (there - * should be exactly one, but the caller checks that). + * Having removed some relations and outer joins from the jointree, we must + * get rid of any references to them that are left behind elsewhere. There + * should be no ordinary Vars of a removed relation left, but OJ relids can + * still appear in the nullingrels sets of surviving Vars and PlaceHolderVars, + * and both regular and OJ relids can appear in the phrels sets of + * PlaceHolderVars. ChangeVarNodes knows how to strip a relid out of all of + * those. */ -static List * -remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved) +static void +remove_rels_from_query_tree(PlannerInfo *root, Relids removed_relids) { - List *result = NIL; - ListCell *jl; + int relid = -1; - foreach(jl, joinlist) + while ((relid = bms_next_member(removed_relids, relid)) >= 0) { - Node *jlnode = (Node *) lfirst(jl); + ChangeVarNodes((Node *) root->parse, relid, INVALID_VAR, 0); - if (IsA(jlnode, RangeTblRef)) - { - int varno = ((RangeTblRef *) jlnode)->rtindex; + /* + * processed_tlist shares some but not all of its nodes with + * parse->targetList, so it has to be processed separately. (That's + * harmless: ChangeVarNodes works in-place, and removing a relid that + * isn't there is idempotent.) + */ + ChangeVarNodes((Node *) root->processed_tlist, relid, INVALID_VAR, 0); - if (varno == relid) - (*nremoved)++; - else - result = lappend(result, jlnode); - } - else if (IsA(jlnode, List)) - { - /* Recurse to handle subproblem */ - List *sublist; - - sublist = remove_rel_from_joinlist((List *) jlnode, - relid, nremoved); - /* Avoid including empty sub-lists in the result */ - if (sublist) - result = lappend(result, sublist); - } - else - { - elog(ERROR, "unrecognized joinlist node type: %d", - (int) nodeTag(jlnode)); - } + /* There could be references in the append_rel_list, too */ + if (root->append_rel_list != NIL) + ChangeVarNodes((Node *) root->append_rel_list, relid, INVALID_VAR, 0); } - - return result; } - /* * reduce_unique_semijoins * Check for semijoins that can be simplified to plain inner joins @@ -1033,14 +451,13 @@ remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved) * Ideally this would happen during reduce_outer_joins, but we don't have * enough information at that point. * - * To perform the strength reduction when applicable, we need only delete - * the semijoin's SpecialJoinInfo from root->join_info_list. (We don't - * bother fixing the join type attributed to it in the query jointree, - * since that won't be consulted again.) + * Like the join removal cases, we do this on the query's jointree, so + * returning true means the caller must recompute the derived data. */ -void +bool reduce_unique_semijoins(PlannerInfo *root) { + bool changed = false; ListCell *lc; /* @@ -1061,8 +478,13 @@ reduce_unique_semijoins(PlannerInfo *root) if (sjinfo->jointype != JOIN_SEMI) continue; - if (!bms_get_singleton_member(sjinfo->min_righthand, &innerrelid)) + /* + * We test the syntactic righthand side, since that's what identifies + * the JoinExpr we'll modify. + */ + if (!bms_get_singleton_member(sjinfo->syn_righthand, &innerrelid)) continue; + Assert(bms_equal(sjinfo->min_righthand, sjinfo->syn_righthand)); innerrel = find_base_rel(root, innerrelid); @@ -1097,9 +519,65 @@ reduce_unique_semijoins(PlannerInfo *root) JOIN_SEMI, restrictlist, true)) continue; - /* OK, remove the SpecialJoinInfo from the list. */ - root->join_info_list = foreach_delete_current(root->join_info_list, lc); + /* OK, reduce the join to a plain inner join in the jointree. */ + if (!reduce_semijoin_in_jointree((Node *) root->parse->jointree, + sjinfo->syn_righthand)) + elog(ERROR, "failed to find semijoin in jointree"); + changed = true; } + + return changed; +} + +/* + * reduce_semijoin_in_jointree + * Find the JoinExpr for the semijoin with the given syntactic righthand + * side, and turn it into an inner join. + * + * Semijoins have no RT index of their own, so we have to identify the one + * we want by the set of relids on its righthand side. + */ +static bool +reduce_semijoin_in_jointree(Node *jtnode, Relids syn_righthand) +{ + if (jtnode == NULL) + return false; + if (IsA(jtnode, RangeTblRef)) + { + /* nothing to do here */ + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + foreach(l, f->fromlist) + { + if (reduce_semijoin_in_jointree((Node *) lfirst(l), syn_righthand)) + return true; + } + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + if (j->jointype == JOIN_SEMI && + bms_equal(get_relids_in_jointree(j->rarg, true, false), + syn_righthand)) + { + j->jointype = JOIN_INNER; + return true; + } + if (reduce_semijoin_in_jointree(j->larg, syn_righthand)) + return true; + if (reduce_semijoin_in_jointree(j->rarg, syn_righthand)) + return true; + } + else + elog(ERROR, "unrecognized jointree node type: %d", + (int) nodeTag(jtnode)); + + return false; } @@ -1548,7 +1026,7 @@ innerrel_is_unique(PlannerInfo *root, * A non-NULL extra_clauses indicates that we're checking for self-join and * correspondingly dealing with filtered clauses. */ -bool +static bool innerrel_is_unique_ext(PlannerInfo *root, Relids joinrelids, Relids outerrelids, @@ -1728,422 +1206,72 @@ is_innerrel_unique_for(PlannerInfo *root, } /* - * Update EC members to point to the remaining relation instead of the removed - * one, removing duplicates. + * Remove the toRemove relation after we have proven that it participates only + * in an unneeded unique self-join with toKeep. * - * Restriction clauses for base relations are already distributed to - * the respective baserestrictinfo lists (see - * generate_implied_equalities_for_column). The above code has already processed - * this list and updated these clauses to reference the remaining - * relation, so that we can skip them here based on their relids. + * The removal is done by deleting the relation's RangeTblRef from the + * jointree and then pointing everything that referenced it at the relation we + * are keeping. All the conditions that were attached to the removed relation + * thereby become conditions on the remaining one, which is what we want: + * we've proven that the two relations select the same rows. Note that + * this change requires us to hoist those conditions up to someplace + * syntactically enclosing toKeep. * - * Likewise, we have already processed the join clauses that join the - * removed relation to the remaining one. - * - * Finally, there might be join clauses tying the removed relation to - * some third relation. We can't just delete the source clauses and - * regenerate them from the EC because the corresponding equality - * operators might be missing (see the handling of ec_broken). - * Therefore, we will update the references in the source clauses. - * - * Derived clauses can be generated again, so it is simpler just to - * delete them. + * kmark and rmark are the PlanRowMarks (if any) for the kept and removed + * relations. We could re-locate those, but the caller already found them. */ static void -update_eclasses(EquivalenceClass *ec, int from, int to) -{ - List *new_members = NIL; - List *new_sources = NIL; - - /* - * 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); - - foreach_node(EquivalenceMember, em, ec->ec_members) - { - bool is_redundant = false; - - if (!bms_is_member(from, em->em_relids)) - { - new_members = lappend(new_members, em); - continue; - } - - em->em_relids = adjust_relid_set(em->em_relids, from, to); - em->em_jdomain->jd_relids = adjust_relid_set(em->em_jdomain->jd_relids, from, to); - - /* We only process inner joins */ - ChangeVarNodesExtended((Node *) em->em_expr, from, to, 0, - replace_relid_callback); - - foreach_node(EquivalenceMember, other, new_members) - { - if (!equal(em->em_relids, other->em_relids)) - continue; - - if (equal(em->em_expr, other->em_expr)) - { - is_redundant = true; - break; - } - } - - if (!is_redundant) - new_members = lappend(new_members, em); - } - - list_free(ec->ec_members); - ec->ec_members = new_members; - - ec_clear_derived_clauses(ec); - - /* Update EC source expressions */ - foreach_node(RestrictInfo, rinfo, ec->ec_sources) - { - bool is_redundant = false; - - if (!bms_is_member(from, rinfo->required_relids)) - { - new_sources = lappend(new_sources, rinfo); - continue; - } - - ChangeVarNodesExtended((Node *) rinfo, from, to, 0, - replace_relid_callback); - - /* - * After switching the clause to the remaining relation, check it for - * 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) - { - if (!equal(rinfo->clause_relids, other->clause_relids)) - continue; - - if (equal(rinfo->clause, other->clause)) - { - is_redundant = true; - break; - } - } - - if (!is_redundant) - new_sources = lappend(new_sources, rinfo); - } - - list_free(ec->ec_sources); - ec->ec_sources = new_sources; - ec->ec_relids = adjust_relid_set(ec->ec_relids, from, to); -} - -/* - * "Logically" compares two RestrictInfo's ignoring the 'rinfo_serial' field, - * which makes almost every RestrictInfo unique. This type of comparison is - * useful when removing duplicates while moving RestrictInfo's from removed - * relation to remaining relation during self-join elimination. - * - * XXX: In the future, we might remove the 'rinfo_serial' field completely and - * get rid of this function. - */ -static bool -restrict_infos_logically_equal(RestrictInfo *a, RestrictInfo *b) -{ - int saved_rinfo_serial = a->rinfo_serial; - bool result; - - a->rinfo_serial = b->rinfo_serial; - result = equal(a, b); - a->rinfo_serial = saved_rinfo_serial; - - return result; -} - -/* - * This function adds all non-redundant clauses to the keeping relation - * during self-join elimination. That is a contradictory operation. On the - * one hand, we reduce the length of the `restrict` lists, which can - * impact planning or executing time. Additionally, we improve the - * accuracy of cardinality estimation. On the other hand, it is one more - * place that can make planning time much longer in specific cases. It - * would have been better to avoid calling the equal() function here, but - * it's the only way to detect duplicated inequality expressions. - * - * (*keep_rinfo_list) is given by pointer because it might be altered by - * distribute_restrictinfo_to_rels(). - */ -static void -add_non_redundant_clauses(PlannerInfo *root, - List *rinfo_candidates, - List **keep_rinfo_list, - Index removed_relid) -{ - foreach_node(RestrictInfo, rinfo, rinfo_candidates) - { - bool is_redundant = false; - - Assert(!bms_is_member(removed_relid, rinfo->required_relids)); - - foreach_node(RestrictInfo, src, (*keep_rinfo_list)) - { - if (!bms_equal(src->clause_relids, rinfo->clause_relids)) - /* Can't compare trivially different clauses */ - continue; - - if (src == rinfo || - (rinfo->parent_ec != NULL && - src->parent_ec == rinfo->parent_ec) || - restrict_infos_logically_equal(rinfo, src)) - { - is_redundant = true; - break; - } - } - if (!is_redundant) - distribute_restrictinfo_to_rels(root, rinfo); - } -} - -/* - * A custom callback for ChangeVarNodesExtended() providing Self-join - * elimination (SJE) related functionality - * - * SJE needs to skip the RangeTblRef node type. During SJE's last - * step, remove_rel_from_joinlist() removes remaining RangeTblRefs - * with target relid. If ChangeVarNodes() replaces the target relid - * before, remove_rel_from_joinlist() would fail to identify the nodes - * to delete. - * - * SJE also needs to change the relids within RestrictInfo's. - */ -static bool -replace_relid_callback(Node *node, ChangeVarNodes_context *context) -{ - if (IsA(node, RangeTblRef)) - { - return true; - } - else if (IsA(node, RestrictInfo)) - { - RestrictInfo *rinfo = (RestrictInfo *) node; - int relid = -1; - bool is_req_equal = - (rinfo->required_relids == rinfo->clause_relids); - bool clause_relids_is_multiple = - (bms_membership(rinfo->clause_relids) == BMS_MULTIPLE); - - /* - * Recurse down into clauses if the target relation is present in - * clause_relids or required_relids. We must check required_relids - * because the relation not present in clause_relids might still be - * present somewhere in orclause. - */ - if (bms_is_member(context->rt_index, rinfo->clause_relids) || - bms_is_member(context->rt_index, rinfo->required_relids)) - { - Relids new_clause_relids; - - ChangeVarNodesWalkExpression((Node *) rinfo->clause, context); - ChangeVarNodesWalkExpression((Node *) rinfo->orclause, context); - - new_clause_relids = adjust_relid_set(rinfo->clause_relids, - context->rt_index, - context->new_index); - - /* - * Incrementally adjust num_base_rels based on the change of - * clause_relids, which could contain both base relids and - * outer-join relids. This operation is legal until we remove - * only baserels. - */ - rinfo->num_base_rels -= bms_num_members(rinfo->clause_relids) - - bms_num_members(new_clause_relids); - - rinfo->clause_relids = new_clause_relids; - rinfo->left_relids = - adjust_relid_set(rinfo->left_relids, context->rt_index, context->new_index); - rinfo->right_relids = - adjust_relid_set(rinfo->right_relids, context->rt_index, context->new_index); - } - - if (is_req_equal) - rinfo->required_relids = rinfo->clause_relids; - else - rinfo->required_relids = - adjust_relid_set(rinfo->required_relids, context->rt_index, context->new_index); - - rinfo->outer_relids = - adjust_relid_set(rinfo->outer_relids, context->rt_index, context->new_index); - rinfo->incompatible_relids = - adjust_relid_set(rinfo->incompatible_relids, context->rt_index, context->new_index); - - if (rinfo->mergeopfamilies && - bms_get_singleton_member(rinfo->clause_relids, &relid) && - clause_relids_is_multiple && - relid == context->new_index && IsA(rinfo->clause, OpExpr)) - { - Expr *leftOp; - Expr *rightOp; - - leftOp = (Expr *) get_leftop(rinfo->clause); - rightOp = (Expr *) get_rightop(rinfo->clause); - - /* - * For self-join elimination, changing varnos could transform - * "t1.a = t2.a" into "t1.a = t1.a". That is always true as long - * as "t1.a" is not null. We use equal() to check for such a - * case, and then we replace the qual with a check for not null - * (NullTest). - */ - if (leftOp != NULL && equal(leftOp, rightOp)) - { - NullTest *ntest = makeNode(NullTest); - - ntest->arg = leftOp; - ntest->nulltesttype = IS_NOT_NULL; - ntest->argisrow = false; - ntest->location = -1; - rinfo->clause = (Expr *) ntest; - rinfo->mergeopfamilies = NIL; - rinfo->left_em = NULL; - rinfo->right_em = NULL; - } - Assert(rinfo->orclause == NULL); - } - return true; - } - - return false; -} - -/* - * Remove a relation after we have proven that it participates only in an - * unneeded unique self-join. - * - * Replace any links in planner info structures. - * - * Transfer join and restriction clauses from the removed relation to the - * remaining one. We change the Vars of the clause to point to the - * remaining relation instead of the removed one. The clauses that require - * a subset of joinrelids become restriction clauses of the remaining - * relation, and others remain join clauses. We append them to - * baserestrictinfo and joininfo, respectively, trying not to introduce - * duplicates. - * - * We also have to process the 'joinclauses' list here, because it - * contains EC-derived join clauses which must become filter clauses. It - * is not enough to just correct the ECs because the EC-derived - * restrictions are generated before join removal (see - * generate_base_implied_equalities). - * - * NOTE: Remember to keep the code in sync with PlannerInfo to be sure all - * cached relids and relid bitmapsets can be correctly cleaned during the - * self-join elimination procedure. - */ -static void -remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark, +remove_self_join_rel(PlannerInfo *root, RelOptInfo *toKeep, RelOptInfo *toRemove, - List *restrictlist) + PlanRowMark *kmark, PlanRowMark *rmark) { - List *joininfos; - ListCell *lc; - int i; - List *jinfo_candidates = NIL; - List *binfo_candidates = NIL; + Node *orphan_quals = NULL; + int nremoved = 0; + Node *hoist_quals = NULL; + bool found_relid = false; Assert(toKeep->relid > 0); Assert(toRemove->relid > 0); - /* - * Replace the index of the removing table with the keeping one. The - * technique of removing/distributing restrictinfo is used here to attach - * just appeared (for keeping relation) join clauses and avoid adding - * duplicates of those that already exist in the joininfo list. - */ - joininfos = list_copy(toRemove->joininfo); - foreach_node(RestrictInfo, rinfo, joininfos) - { - remove_join_clause_from_rels(root, rinfo, rinfo->required_relids); - ChangeVarNodesExtended((Node *) rinfo, toRemove->relid, toKeep->relid, - 0, replace_relid_callback); - - if (bms_membership(rinfo->required_relids) == BMS_MULTIPLE) - jinfo_candidates = lappend(jinfo_candidates, rinfo); - else - binfo_candidates = lappend(binfo_candidates, rinfo); - } + /* We verify that exactly one reference gets removed from the jointree */ + root->parse->jointree = (FromExpr *) + remove_rel_from_jointree((Node *) root->parse->jointree, + toRemove->relid, + &orphan_quals, &nremoved); + if (nremoved != 1) + elog(ERROR, "failed to find relation %d in jointree", toRemove->relid); + /* The topmost FromExpr can't have gone away, so nothing can be orphaned */ + Assert(root->parse->jointree != NULL); + Assert(orphan_quals == NULL); /* - * Concatenate restrictlist to the list of base restrictions of the - * removing table just to simplify the replacement procedure: all of them - * weren't connected to any keeping relations and need to be added to some - * rels. + * Replace all references to the removed relation. Note that this must + * happen after the jointree surgery, else we'd not be able to tell the + * two relations' RangeTblRefs apart. */ - toRemove->baserestrictinfo = list_concat(toRemove->baserestrictinfo, - restrictlist); - foreach_node(RestrictInfo, rinfo, toRemove->baserestrictinfo) - { - ChangeVarNodesExtended((Node *) rinfo, toRemove->relid, toKeep->relid, - 0, replace_relid_callback); - - if (bms_membership(rinfo->required_relids) == BMS_MULTIPLE) - jinfo_candidates = lappend(jinfo_candidates, rinfo); - else - binfo_candidates = lappend(binfo_candidates, rinfo); - } + ChangeVarNodes((Node *) root->parse, toRemove->relid, toKeep->relid, 0); /* - * Now, add all non-redundant clauses to the keeping relation. + * processed_tlist shares some but not all of its nodes with + * parse->targetList, so it has to be processed separately. (That's + * harmless: ChangeVarNodes works in-place, and the second visit to a + * shared node finds nothing to change.) */ - add_non_redundant_clauses(root, binfo_candidates, - &toKeep->baserestrictinfo, toRemove->relid); - add_non_redundant_clauses(root, jinfo_candidates, - &toKeep->joininfo, toRemove->relid); + ChangeVarNodes((Node *) root->processed_tlist, toRemove->relid, + toKeep->relid, 0); - list_free(binfo_candidates); - list_free(jinfo_candidates); + /* There could be references in the append_rel_list, too */ + if (root->append_rel_list != NIL) + ChangeVarNodes((Node *) root->append_rel_list, toRemove->relid, + toKeep->relid, 0); - /* - * Arrange equivalence classes, mentioned removing a table, with the - * keeping one: varno of removing table should be replaced in members and - * sources lists. Also, remove duplicated elements if this replacement - * procedure created them. - */ - i = -1; - while ((i = bms_next_member(toRemove->eclass_indexes, i)) >= 0) - { - EquivalenceClass *ec = (EquivalenceClass *) list_nth(root->eq_classes, i); - - update_eclasses(ec, toRemove->relid, toKeep->relid); - toKeep->eclass_indexes = bms_add_member(toKeep->eclass_indexes, i); - } - - /* - * Transfer the targetlist and attr_needed flags. - */ - - foreach(lc, toRemove->reltarget->exprs) - { - Node *node = lfirst(lc); - - ChangeVarNodesExtended(node, toRemove->relid, toKeep->relid, 0, - replace_relid_callback); - if (!list_member(toKeep->reltarget->exprs, node)) - toKeep->reltarget->exprs = lappend(toKeep->reltarget->exprs, node); - } - - for (i = toKeep->min_attr; i <= toKeep->max_attr; i++) - { - int attno = i - toKeep->min_attr; - - toRemove->attr_needed[attno] = adjust_relid_set(toRemove->attr_needed[attno], - toRemove->relid, toKeep->relid); - toKeep->attr_needed[attno] = bms_add_members(toKeep->attr_needed[attno], - toRemove->attr_needed[attno]); - } + /* Clean up the quals that the substitution has messed with */ + fixup_selfjoin_jointree(root, (Node *) root->parse->jointree, + toKeep->relid, + &hoist_quals, &found_relid); + /* We shouldn't have any leftover quals, and we must have found toKeep */ + Assert(hoist_quals == NULL); + Assert(found_relid); /* * If the removed relation has a row mark, transfer it to the remaining @@ -2163,63 +1291,345 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark, } else { - /* Shouldn't have inheritance children here. */ + /* Shouldn't have inheritance children yet. */ Assert(rmark->rti == rmark->prti); rmark->rti = rmark->prti = toKeep->relid; } } +} + +/* + * remove_rel_from_jointree + * Delete the RangeTblRef for the given relation from the query's + * jointree. + * + * This is used for self-join elimination, where the removed relation's + * qual conditions must all be preserved (they will be transposed onto the + * remaining relation afterwards). Hence, if dropping the RangeTblRef leaves + * a JoinExpr or FromExpr with nothing under it, we can't simply drop that + * node; we hand its quals back to the caller in *orphan_quals, to be merged + * into the nearest enclosing node that still has some content. That's a + * valid transformation only for inner joins, but a jointree node can't become + * empty at an outer join here: remove_self_joins_one_group() insists that the + * two relations be on the same side of every outer join, so the relation we + * are keeping would have to be in the emptied subtree too. + * + * *nremoved is incremented by the number of RangeTblRefs removed (there + * should be exactly one, but the caller checks that). + */ +static Node * +remove_rel_from_jointree(Node *jtnode, int relid, + Node **orphan_quals, int *nremoved) +{ + if (jtnode == NULL) + return NULL; + if (IsA(jtnode, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) jtnode; + + if (rtr->rtindex == relid) + { + (*nremoved)++; + return NULL; + } + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + List *newfromlist = NIL; + Node *sub_orphans = NULL; + ListCell *l; + + foreach(l, f->fromlist) + { + Node *newchild; + + newchild = remove_rel_from_jointree((Node *) lfirst(l), relid, + &sub_orphans, nremoved); + if (newchild != NULL) + newfromlist = lappend(newfromlist, newchild); + } + f->fromlist = newfromlist; + f->quals = merge_quals(sub_orphans, f->quals); + if (newfromlist == NIL) + { + /* Nothing left here, so pass our quals up to the parent */ + *orphan_quals = merge_quals(f->quals, *orphan_quals); + return NULL; + } + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + Node *sub_orphans = NULL; + + j->larg = remove_rel_from_jointree(j->larg, relid, + &sub_orphans, nremoved); + j->rarg = remove_rel_from_jointree(j->rarg, relid, + &sub_orphans, nremoved); + if (j->larg == NULL || j->rarg == NULL) + { + Node *surviving = (j->larg != NULL) ? j->larg : j->rarg; + Node *quals = merge_quals(sub_orphans, j->quals); + + /* As explained above, this can only happen for an inner join */ + Assert(j->jointype == JOIN_INNER); + /* We can't have removed both children */ + Assert(surviving != NULL); + + /* + * Replace the join by a FromExpr, so that the surviving side's + * rows are still filtered by the join's conditions. + */ + return (Node *) makeFromExpr(list_make1(surviving), quals); + } + /* A subtree that survives never hands any quals back to us */ + Assert(sub_orphans == NULL); + } + else + elog(ERROR, "unrecognized jointree node type: %d", + (int) nodeTag(jtnode)); + + return jtnode; +} + +/* + * merge_quals + * Combine two jointree qual conditions. + * + * quals1 should be the quals from the lower of the two jointree levels, + * so that those quals get applied first. + * + * Jointree quals have been through preprocess_expression() by now, so each + * one is either NULL or an implicitly-ANDed List. + */ +static Node * +merge_quals(Node *quals1, Node *quals2) +{ + if (quals1 == NULL) + return quals2; + if (quals2 == NULL) + return quals1; + return (Node *) list_concat(castNode(List, quals1), + castNode(List, quals2)); +} + +/* + * fixup_selfjoin_jointree + * Clean up the query's jointree quals after self-join elimination has + * merged one relation into another. (relid is the kept relation.) + * + * See fixup_selfjoin_quals() for what needs fixing locally to each qual list. + * In addition, we need to check quals to see if they refer to relid, and if + * so make sure they get hoisted to someplace syntactically above relid. + * Do that using a "hoist_quals" in/out parameter similar to "orphan_quals" + * in remove_rel_from_jointree. (We can't readily merge these concerns into + * a single pass, since remove_rel_from_jointree must run before we relabel + * the removed rel's Vars.) In addition, *found_relid is set true if + * the subtree rooted at jtnode is found to contain relid's RangeTblRef, + * so that we can tell when to stop hoisting quals. + * If a qual gets hoisted up, we apply fixup_selfjoin_quals() to it only + * after it reaches its final level. This rule improves the odds of + * detecting duplicate quals. + */ +static void +fixup_selfjoin_jointree(PlannerInfo *root, Node *jtnode, int relid, + Node **hoist_quals, bool *found_relid) +{ + if (jtnode == NULL) + return; + if (IsA(jtnode, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) jtnode; + + if (rtr->rtindex == relid) + { + Assert(!*found_relid); + *found_relid = true; + } + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + Node *sub_hoist_quals = NULL; + bool sub_found_relid = false; + ListCell *l; + + foreach(l, f->fromlist) + fixup_selfjoin_jointree(root, (Node *) lfirst(l), relid, + &sub_hoist_quals, &sub_found_relid); + if (sub_found_relid) + { + /* This FromExpr covers relid, so OK to stop hoisting quals here */ + f->quals = merge_quals(sub_hoist_quals, f->quals); + Assert(!*found_relid); + *found_relid = true; + } + else + { + /* We might need to hoist some of our own quals too */ + List *hoistable = NIL; + List *keepable = NIL; + + foreach_ptr(Node, qual, castNode(List, f->quals)) + { + if (bms_is_member(relid, pull_varnos(root, qual))) + hoistable = lappend(hoistable, qual); + else + keepable = lappend(keepable, qual); + } + f->quals = (Node *) keepable; + sub_hoist_quals = merge_quals(sub_hoist_quals, (Node *) hoistable); + *hoist_quals = merge_quals(sub_hoist_quals, *hoist_quals); + } + f->quals = (Node *) fixup_selfjoin_quals(root, + castNode(List, f->quals), + relid); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + Node *sub_hoist_quals = NULL; + bool sub_found_relid = false; + + fixup_selfjoin_jointree(root, j->larg, relid, + &sub_hoist_quals, &sub_found_relid); + fixup_selfjoin_jointree(root, j->rarg, relid, + &sub_hoist_quals, &sub_found_relid); + if (sub_found_relid) + { + /* This JoinExpr covers relid, so OK to stop hoisting quals here */ + j->quals = merge_quals(sub_hoist_quals, j->quals); + Assert(!*found_relid); + *found_relid = true; + } + else + { + /* We might need to hoist some of our own quals too */ + List *hoistable = NIL; + List *keepable = NIL; + + foreach_ptr(Node, qual, castNode(List, j->quals)) + { + if (bms_is_member(relid, pull_varnos(root, qual))) + hoistable = lappend(hoistable, qual); + else + keepable = lappend(keepable, qual); + } + j->quals = (Node *) keepable; + sub_hoist_quals = merge_quals(sub_hoist_quals, (Node *) hoistable); + /* We should never need to hoist quals above an outer join */ + Assert(sub_hoist_quals == NULL || j->jointype == JOIN_INNER); + *hoist_quals = merge_quals(sub_hoist_quals, *hoist_quals); + } + j->quals = (Node *) fixup_selfjoin_quals(root, + castNode(List, j->quals), + relid); + } + else + elog(ERROR, "unrecognized jointree node type: %d", + (int) nodeTag(jtnode)); +} + +/* + * fixup_selfjoin_quals + * Clean up one qual list after self-join elimination. + * + * Two things need fixing here. First, a join clause such as "t1.a = t2.a" + * has turned into "t1.a = t1.a". For a strict mergejoinable operator that + * means "t1.a IS NOT NULL", and we should make the substitution, for two + * reasons: + * 1. It will typically result in better selectivity estimates. + * 2. EquivalenceClass processing is likely to make the substitution + * if we don't. While not directly harmful, we'd then fail to + * recognize it as a duplicate of a user-written "t1.a IS NOT NULL" + * clause, again leading to bad selectivity estimates. + * Second, conditions that were written against the two relations separately + * may now be identical, and we don't want to apply the same condition twice + * (much less double-count its selectivity). + * + * We only touch the top-level conjuncts of the list. There, turning a NULL + * result into FALSE makes no difference, whereas below a NOT it would, + * invalidating the IS NOT NULL substitution. EquivalenceClass processing + * will not be applied to sub-clauses, and cleaning up duplicates in them + * seems like more trouble than it's worth. Also, we only consider clauses + * that mention the relation we merged into, so that we don't change the + * treatment of anything we didn't touch. + * + * Since this is not a correctness issue but just an optimization opportunity, + * we likewise don't worry about recognizing duplicates that appear in + * different qual lists. + */ +static List * +fixup_selfjoin_quals(PlannerInfo *root, List *quals, int relid) +{ + List *result = NIL; + ListCell *l; + + foreach(l, quals) + { + Node *qual = (Node *) lfirst(l); + + if (bms_is_member(relid, pull_varnos(root, qual))) + { + qual = replace_selfjoin_qual(qual); + /* Drop it if the substitution has made it a duplicate */ + if (list_member(result, qual)) + continue; + } + result = lappend(result, qual); + } + + return result; +} + +/* + * replace_selfjoin_qual + * Replace one "X = X" qual by "X IS NOT NULL", if it is one. + */ +static Node * +replace_selfjoin_qual(Node *qual) +{ + OpExpr *opexpr; + Node *leftop; + Node *rightop; + NullTest *ntest; + + /* See if it looks like "X op X" */ + if (!is_opclause(qual)) + return qual; + opexpr = (OpExpr *) qual; + if (list_length(opexpr->args) != 2) + return qual; + leftop = get_leftop((Expr *) opexpr); + rightop = get_rightop((Expr *) opexpr); + if (!equal(leftop, rightop)) + return qual; /* - * Replace varno in all the query structures, except nodes RangeTblRef - * otherwise later remove_rel_from_joinlist will yield errors. + * The operator must be strict and behave like btree equality, else we + * can't conclude that it yields true for any non-null input. And the + * input had better not be volatile, else the two evaluations might not + * agree. If either condition doesn't hold, the clause is not a candidate + * to be an equivalence, so we needn't worry about it getting replaced by + * equivclass.c. */ - ChangeVarNodesExtended((Node *) root->parse, toRemove->relid, toKeep->relid, - 0, replace_relid_callback); + set_opfuncid(opexpr); + if (!func_strict(opexpr->opfuncid)) + return qual; + if (!op_mergejoinable(opexpr->opno, exprType(leftop))) + return qual; + if (contain_volatile_functions(leftop)) + return qual; - /* Replace links in the planner info */ - remove_rel_from_query(root, toRemove->relid, toKeep->relid, NULL, NULL); - - /* Replace varno in the fully-processed targetlist */ - ChangeVarNodesExtended((Node *) root->processed_tlist, toRemove->relid, - toKeep->relid, 0, replace_relid_callback); - - /* - * No need to touch all_result_relids or leaf_result_relids: at this point - * those sets contain only parse->resultRelation; inheritance children - * have not been added yet; that happens later in add_other_rels_to_query. - * And remove_self_joins_recurse rejects parse->resultRelation as an SJE - * candidate to preserve the EPQ mechanism. So toRemove->relid cannot be - * a member. - */ - Assert(!bms_is_member(toRemove->relid, root->all_result_relids)); - Assert(!bms_is_member(toRemove->relid, root->leaf_result_relids)); - - /* - * There may be references to the rel in root->fkey_list, but if so, - * match_foreign_keys_to_quals() will get rid of them. - */ - - /* - * Finally, 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[toRemove->relid] = NULL; - root->simple_rte_array[toRemove->relid] = NULL; - - /* And nuke the RelOptInfo, just in case there's another access path. */ - pfree(toRemove); - - - /* - * 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); + /* OK, replace it */ + ntest = makeNode(NullTest); + ntest->arg = (Expr *) leftop; + ntest->nulltesttype = IS_NOT_NULL; + ntest->argisrow = false; /* correct even if composite arg */ + ntest->location = -1; + return (Node *) ntest; } /* @@ -2244,7 +1654,12 @@ split_selfjoin_quals(PlannerInfo *root, List *joinquals, List **selfjoinquals, Node *leftexpr; Node *rightexpr; - /* In general, clause looks like F(arg1) = G(arg2) */ + /* + * Since the given joinquals all came from + * generate_join_implied_equalities, they ought to look like equality + * operators on single-relation expressions. But let's check that. + * Anything that doesn't look like that can be dumped into ojoinquals. + */ if (!rinfo->mergeopfamilies || bms_num_members(rinfo->clause_relids) != 2 || bms_membership(rinfo->left_relids) != BMS_SINGLETON || @@ -2275,10 +1690,9 @@ split_selfjoin_quals(PlannerInfo *root, List *joinquals, List **selfjoinquals, * when we have cast of the same var to different (but compatible) * types. */ - ChangeVarNodesExtended(rightexpr, - bms_singleton_member(rinfo->right_relids), - bms_singleton_member(rinfo->left_relids), 0, - replace_relid_callback); + ChangeVarNodes(rightexpr, + bms_singleton_member(rinfo->right_relids), + bms_singleton_member(rinfo->left_relids), 0); if (equal(leftexpr, rightexpr)) sjoinquals = lappend(sjoinquals, rinfo); @@ -2314,8 +1728,7 @@ match_unique_clauses(PlannerInfo *root, RelOptInfo *outer, List *uclauses, bms_is_empty(rinfo->right_relids)); clause = (Expr *) copyObject(rinfo->clause); - ChangeVarNodesExtended((Node *) clause, relid, outer->relid, 0, - replace_relid_callback); + ChangeVarNodes((Node *) clause, relid, outer->relid, 0); iclause = bms_is_empty(rinfo->left_relids) ? get_rightop(clause) : get_leftop(clause); @@ -2360,12 +1773,23 @@ match_unique_clauses(PlannerInfo *root, RelOptInfo *outer, List *uclauses, * Find and remove unique self-joins in a group of base relations that have * the same Oid. * - * Returns a set of relids that were removed. + * Return true if we removed any joins. + * + * After a removal, we continue searching for more removals, even though the + * tests will be using derived data that is now partially stale. That is safe + * because we are trying to prove that a candidate pair of relations must + * match the same row, and the stale data can only omit quals, never invent + * them. The removed relation's quals are moved onto the kept relation in + * the jointree but not into its baserestrictinfo, and no other derived data + * changes. A proof made from a subset of the applicable quals remains valid + * when the rest are added, since extra quals can only remove rows from the + * join. So a pass may miss a removal that a later pass will find, but it + * cannot make one that isn't justified. */ -static Relids +static bool remove_self_joins_one_group(PlannerInfo *root, Relids relids) { - Relids result = NULL; + bool removed = false; int k; /* Index of kept relation */ int r = -1; /* Index of removed relation */ @@ -2373,8 +1797,8 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids) { RelOptInfo *rrel = root->simple_rel_array[r]; + /* k iterates over the relids after r */ k = r; - while ((k = bms_next_member(relids, k)) > 0) { Relids joinrelids = NULL; @@ -2394,8 +1818,8 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids) /* * It is impossible to eliminate the join of two relations if they - * belong to different rules of order. Otherwise, the planner - * can't find any variants of the correct query plan. + * are not on the same side of every outer join. Otherwise, the + * planner can't find any variants of the correct query plan. */ foreach(lc, root->join_info_list) { @@ -2509,32 +1933,35 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids) if (!match_unique_clauses(root, rrel, uclauses, krel->relid)) continue; + /* OK, remove rrel from the query */ + remove_self_join_rel(root, krel, rrel, kmark, rmark); + removed = true; + /* - * Remove rrel RelOptInfo from the planner structures and the - * corresponding row mark. + * Since relation r is now gone, we mustn't keep looking for + * matches to it. But we can keep scanning later relids members + * for additional join pairs. */ - remove_self_join_rel(root, kmark, rmark, krel, rrel, restrictlist); - - result = bms_add_member(result, r); - - /* We have removed the outer relation, try the next one. */ break; } } - return result; + return removed; } /* - * Gather indexes of base relations from the joinlist and try to eliminate self - * joins. + * Gather indexes of base relations from the joinlist and try to eliminate + * self-joins. + * + * Return true if we removed any joins. */ -static Relids -remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove) +static bool +remove_self_joins_recurse(PlannerInfo *root, List *joinlist) { + bool removed = false; ListCell *jl; Relids relids = NULL; - SelfJoinCandidate *candidates = NULL; + SelfJoinCandidate *candidates; int i; int j; int numRels; @@ -2553,7 +1980,7 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove) * We only consider ordinary relations as candidates to be * removed, and these relations should not have TABLESAMPLE * clauses specified. Removing a relation with TABLESAMPLE clause - * could potentially change the syntax of the query. Because of + * could potentially change the semantics of the query. Because of * UPDATE/DELETE EPQ mechanism, currently Query->resultRelation or * Query->mergeTargetRelation associated rel cannot be eliminated. */ @@ -2569,9 +1996,8 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove) } else if (IsA(jlnode, List)) { - /* Recursively go inside the sub-joinlist */ - toRemove = remove_self_joins_recurse(root, (List *) jlnode, - toRemove); + /* Recursively perform SJE within the sub-joinlist */ + removed |= remove_self_joins_recurse(root, (List *) jlnode); } else elog(ERROR, "unrecognized joinlist node type: %d", @@ -2580,9 +2006,9 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove) numRels = bms_num_members(relids); - /* Need at least two relations for the join */ + /* No work if not at least two relations at this level */ if (numRels < 2) - return toRemove; + return removed; /* ... but don't fail to report sub-removals */ /* * In order to find relations with the same oid we first build an array of @@ -2603,15 +2029,13 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove) /* * Iteratively form a group of relation indexes with the same oid and - * launch the routine that detects self-joins in this group and removes - * excessive range table entries. + * launch the routine that detects self-joins in this group. * - * At the end of the iteration, exclude the group from the overall relids - * list. So each next iteration of the cycle will involve less and less - * value of relids. + * We remove considered relations from relids as we scan, so that that set + * should be empty at the end. */ i = 0; - for (j = 1; j < numRels + 1; j++) + for (j = 1; j <= numRels; j++) { if (j == numRels || candidates[j].reloid != candidates[i].reloid) { @@ -2619,7 +2043,6 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove) { /* Create a group of relation indexes with the same oid */ Relids group = NULL; - Relids removed; while (i < j) { @@ -2628,35 +2051,25 @@ remove_self_joins_recurse(PlannerInfo *root, List *joinlist, Relids toRemove) } relids = bms_del_members(relids, group); - /* - * Try to remove self-joins from a group of identical entries. - * Make the next attempt iteratively - if something is deleted - * from a group, changes in clauses and equivalence classes - * can give us a chance to find more candidates. - */ - do - { - Assert(!bms_overlap(group, toRemove)); - removed = remove_self_joins_one_group(root, group); - toRemove = bms_add_members(toRemove, removed); - group = bms_del_members(group, removed); - } while (!bms_is_empty(removed) && - bms_membership(group) == BMS_MULTIPLE); - bms_free(removed); + /* Try to remove self-joins from the group */ + removed |= remove_self_joins_one_group(root, group); bms_free(group); } else { - /* Single relation, just remove it from the set */ - relids = bms_del_member(relids, candidates[i].relid); - i = j; + /* Nothing to do with this group, just drop it from the set */ + while (i < j) + { + relids = bms_del_member(relids, candidates[i].relid); + i++; + } } } } Assert(bms_is_empty(relids)); - return toRemove; + return removed; } /* @@ -2698,45 +2111,26 @@ self_join_candidates_cmp(const void *a, const void *b) * go over each set with the same Oid, and consider each pair of relations * in this set. * - * To remove the join, we mark one of the participating relations as dead - * and rewrite all references to it to point to the remaining relation. - * This includes modifying RestrictInfos, EquivalenceClasses, and - * EquivalenceMembers. We also have to modify the row marks. The join clauses - * of the removed relation become either restriction or join clauses, based on - * whether they reference any relations not participating in the removed join. + * To remove the join, we delete one of the participating relations from the + * query's jointree and rewrite all references to it to point to the remaining + * relation. We also have to modify their row marks. * - * 'joinlist' is the top-level joinlist of the query. If it has any - * references to the removed relations, we update them to point to the - * remaining ones. + * 'joinlist' is the top-level joinlist of the query; we use it to identify + * groups of relations that could be joined to each other. + * + * We return true if we removed any self-joins. If so, the caller must + * recompute everything that was derived from the jointree, and should then + * try join simplifications again since we might have exposed opportunities + * for additional simplifications. */ -List * +bool remove_useless_self_joins(PlannerInfo *root, List *joinlist) { - Relids toRemove = NULL; - int relid = -1; - + /* Skip if SJE is disabled, or if the joinlist has less than 2 members. */ if (!enable_self_join_elimination || joinlist == NIL || (list_length(joinlist) == 1 && !IsA(linitial(joinlist), List))) - return joinlist; + return false; - /* - * Merge pairs of relations participated in self-join. Remove unnecessary - * range table entries. - */ - toRemove = remove_self_joins_recurse(root, joinlist, toRemove); - - if (unlikely(toRemove != NULL)) - { - /* At the end, remove orphaned relation links */ - while ((relid = bms_next_member(toRemove, relid)) >= 0) - { - int nremoved = 0; - - joinlist = remove_rel_from_joinlist(joinlist, relid, &nremoved); - if (nremoved != 1) - elog(ERROR, "failed to find relation %d in joinlist", relid); - } - } - - return joinlist; + /* Try to merge pairs of self-joined relations. */ + return remove_self_joins_recurse(root, joinlist); } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index f08a918146c..a2fb0b55a79 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -295,8 +295,6 @@ build_base_rel_tlists(PlannerInfo *root, List *final_tlist) * have a single owning relation; we keep their attr_needed info in * root->placeholder_list instead. Find or create the associated * PlaceHolderInfo entry, and update its ph_needed. - * - * See also add_vars_to_attr_needed. */ void add_vars_to_targetlist(PlannerInfo *root, List *vars, @@ -350,63 +348,6 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, } } -/* - * add_vars_to_attr_needed - * This does a subset of what add_vars_to_targetlist does: it just - * updates attr_needed for Vars and ph_needed for PlaceHolderVars. - * We assume the Vars are already in their relations' targetlists. - * - * This is used to rebuild attr_needed/ph_needed sets after removal - * of a useless outer join. The removed join clause might have been - * the only upper-level use of some other relation's Var, in which - * case we can reduce that Var's attr_needed and thereby possibly - * open the door to further join removals. But we can't tell that - * without tedious reconstruction of the attr_needed data. - * - * Note that if a Var's attr_needed is successfully reduced to empty, - * it will still be in the relation's targetlist even though we do - * not really need the scan plan node to emit it. The extra plan - * inefficiency seems tiny enough to not be worth spending planner - * cycles to get rid of it. - */ -void -add_vars_to_attr_needed(PlannerInfo *root, List *vars, - Relids where_needed) -{ - ListCell *temp; - - Assert(!bms_is_empty(where_needed)); - - foreach(temp, vars) - { - Node *node = (Node *) lfirst(temp); - - if (IsA(node, Var)) - { - Var *var = (Var *) node; - RelOptInfo *rel = find_base_rel(root, var->varno); - int attno = var->varattno; - - if (bms_is_subset(where_needed, rel->relids)) - continue; - Assert(attno >= rel->min_attr && attno <= rel->max_attr); - attno -= rel->min_attr; - rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno], - where_needed); - } - else if (IsA(node, PlaceHolderVar)) - { - PlaceHolderVar *phv = (PlaceHolderVar *) node; - PlaceHolderInfo *phinfo = find_placeholder_info(root, phv); - - phinfo->ph_needed = bms_add_members(phinfo->ph_needed, - where_needed); - } - else - elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); - } -} - /***************************************************************************** * * GROUP BY @@ -1226,54 +1167,10 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex) */ add_vars_to_targetlist(root, newvars, where_needed); - /* - * Remember the lateral references for rebuild_lateral_attr_needed and - * create_lateral_join_info. - */ + /* Remember the lateral references for create_lateral_join_info */ brel->lateral_vars = newvars; } -/* - * rebuild_lateral_attr_needed - * Put back attr_needed bits for Vars/PHVs needed for lateral references. - * - * This is used to rebuild attr_needed/ph_needed sets after removal of a - * useless outer join. It should match what find_lateral_references did, - * except that we call add_vars_to_attr_needed not add_vars_to_targetlist. - */ -void -rebuild_lateral_attr_needed(PlannerInfo *root) -{ - Index rti; - - /* We need do nothing if the query contains no LATERAL RTEs */ - if (!root->hasLateralRTEs) - return; - - /* Examine the same baserels that find_lateral_references did */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - Relids where_needed; - - if (brel == NULL) - continue; - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - /* - * We don't need to repeat all of extract_lateral_references, since it - * kindly saved the extracted Vars/PHVs in lateral_vars. - */ - if (brel->lateral_vars == NIL) - continue; - - where_needed = bms_make_singleton(rti); - - add_vars_to_attr_needed(root, brel->lateral_vars, where_needed); - } -} - /* * create_lateral_join_info * Fill in the per-base-relation direct_lateral_relids, lateral_relids @@ -3227,9 +3124,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * var propagation is ensured by making ojscope include input rels from * both sides of the join. * - * See also rebuild_joinclause_attr_needed, which has to partially repeat - * this work after removal of an outer join. - * * Note: if the clause gets absorbed into an EquivalenceClass then this * may be unnecessary, but for now we have to do it to cover the case * where the EC becomes ec_broken and we end up reinserting the original @@ -3803,11 +3697,6 @@ process_implied_equality(PlannerInfo *root, * some of the Vars could have missed having that done because they only * appeared in single-relation clauses originally. So do it here for * safety. - * - * See also rebuild_joinclause_attr_needed, which has to partially repeat - * this work after removal of an outer join. (Since we will put this - * clause into the joininfo lists, that function needn't do any extra work - * to find it.) */ if (bms_membership(relids) == BMS_MULTIPLE) { @@ -3949,72 +3838,6 @@ get_join_domain_min_rels(PlannerInfo *root, Relids domain_relids) } -/* - * rebuild_joinclause_attr_needed - * Put back attr_needed bits for Vars/PHVs needed for join clauses. - * - * This is used to rebuild attr_needed/ph_needed sets after removal of a - * useless outer join. It should match what distribute_qual_to_rels did, - * except that we call add_vars_to_attr_needed not add_vars_to_targetlist. - */ -void -rebuild_joinclause_attr_needed(PlannerInfo *root) -{ - /* - * We must examine all join clauses, 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.) - */ - Bitmapset *seen_serials = NULL; - Index rti; - - /* Scan all baserels for join clauses */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - ListCell *lc; - - if (brel == NULL) - continue; - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - foreach(lc, brel->joininfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Relids relids = rinfo->required_relids; - - 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); - } - - if (bms_membership(relids) == BMS_MULTIPLE) - { - List *vars = pull_var_clause((Node *) rinfo->clause, - PVC_RECURSE_AGGREGATES | - PVC_RECURSE_WINDOWFUNCS | - PVC_INCLUDE_PLACEHOLDERS); - Relids where_needed; - - if (rinfo->is_clone) - where_needed = bms_intersect(relids, root->all_baserels); - else - where_needed = relids; - add_vars_to_attr_needed(root, vars, where_needed); - list_free(vars); - } - } - } -} - - /* * match_foreign_keys_to_quals * Match foreign-key constraints to equivalence classes and join quals @@ -4044,9 +3867,9 @@ match_foreign_keys_to_quals(PlannerInfo *root) /* * Either relid might identify a rel that is in the query's rtable but - * isn't referenced by the jointree, or has been removed by join - * removal, so that it won't have a RelOptInfo. Hence don't use - * find_base_rel() here. We can ignore such FKs. + * isn't referenced by the jointree (typically because it's been + * removed by join removal), so that it won't have a RelOptInfo. Hence + * don't use find_base_rel() here. We can ignore such FKs. */ if (fkinfo->con_relid >= root->simple_rel_array_size || fkinfo->ref_relid >= root->simple_rel_array_size) diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 02495e22e24..68d3409476c 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -54,33 +54,71 @@ RelOptInfo * query_planner(PlannerInfo *root, query_pathkeys_callback qp_callback, void *qp_extra) { - Query *parse = root->parse; + Query *parse; List *joinlist; RelOptInfo *final_rel; /* - * Init planner lists to empty. + * The join simplification steps below work by modifying parse->jointree, + * and they make no attempt to update the information we derive from it. + * So whenever one of them succeeds, we must throw away all that derived + * information and recompute it from scratch, which we do by looping back + * to "restart". We cannot loop indefinitely, because each successful + * simplification either deletes a base relation from the jointree or + * turns a semijoin into an inner join, and neither of those can be undone + * by a later pass. * - * NOTE: append_rel_list was set up by subquery_planner, so do not touch - * here. + * These initial Asserts check that the state at entry is not too complex + * for the code below to restore. There mustn't be any EquivalenceClasses + * yet, and we should have only the top-level JoinDomain. */ + Assert(root->eq_classes == NIL); + Assert(list_length(root->join_domains) == 1); + +restart: + parse = root->parse; + + /* + * Initialize information derived from the jointree to empty. + * + * It's critical that this reset every field that the steps below will + * fill in, since we may be going around this loop more than once. + * + * NOTE: append_rel_list was created earlier, so do not clear it here; + * rowMarks ditto. Join simplification must update those if necessary. + */ + root->all_baserels = NULL; + root->outer_join_rels = NULL; + root->all_query_rels = NULL; root->join_rel_list = NIL; root->join_rel_hash = NULL; root->join_rel_level = NULL; root->join_cur_level = 0; + 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; + root->last_rinfo_serial = 0; root->placeholder_list = NIL; root->placeholder_array = NULL; root->placeholder_array_size = 0; + root->placeholdersFrozen = false; root->agg_clause_list = NIL; root->group_expr_list = NIL; root->tlist_vars = NIL; root->fkey_list = NIL; root->initial_rels = NIL; + root->hasPseudoConstantQuals = false; + + /* + * We don't want to delete the top-level join domain, but get rid of other + * ones so as to reset the list to initial state. deconstruct_jointree + * will take care of (re)computing the top level's jd_relids. + */ + root->join_domains = list_truncate(root->join_domains, 1); /* * Set up arrays for accessing base relations and AppendRelInfos. @@ -144,6 +182,21 @@ query_planner(PlannerInfo *root, /* Select cheapest path (pretty easy in this case...) */ set_cheapest(final_rel); + /* + * Fill in all_result_relids and leaf_result_relids, just in + * case something looks at them (at this writing, the core + * code won't). This must match the similar stanza below. + */ + if (parse->resultRelation) + { + int rti = parse->resultRelation; + RangeTblEntry *res_rte = root->simple_rte_array[rti]; + + root->all_result_relids = bms_make_singleton(rti); + if (!res_rte->inh) + root->leaf_result_relids = bms_make_singleton(rti); + } + /* * We don't need to run generate_base_implied_equalities, but * we do need to pretend that EC merging is complete. @@ -227,19 +280,30 @@ query_planner(PlannerInfo *root, * Remove any useless outer joins. Ideally this would be done during * jointree preprocessing, but the necessary information isn't available * until we've built baserel data structures and classified qual clauses. + * If we remove a join, loop back to the top and redo what we did so far. */ - joinlist = remove_useless_joins(root, joinlist); + if (remove_useless_outer_joins(root)) + goto restart; /* * Also, reduce any semijoins with unique inner rels to plain inner joins. - * Likewise, this can't be done until now for lack of needed info. + * Likewise, this can't be done until now for lack of needed info, and we + * must loop around if we find any simplifications. */ - reduce_unique_semijoins(root); + if (reduce_unique_semijoins(root)) + goto restart; /* - * Remove self joins on a unique column. + * Remove self joins on a unique column. Again, this couldn't be done any + * earlier, and we must loop around if we find anything to remove. + */ + if (remove_useless_self_joins(root, joinlist)) + goto restart; + + /* + * No more join simplifications apply, so we're done looping. Code below + * this point does not need to be able to restart. */ - joinlist = remove_useless_self_joins(root, joinlist); /* * Now distribute "placeholders" to base rels as needed. This has to be @@ -274,6 +338,22 @@ query_planner(PlannerInfo *root, */ setup_eager_aggregation(root); + /* + * If there's a result relation, initialize all_result_relids to include + * it; and if we've verified that it is non-inheriting, mark it as a leaf + * target. add_other_rels_to_query() will expand these sets if the result + * relation has children. + */ + if (parse->resultRelation) + { + int rti = parse->resultRelation; + RangeTblEntry *rte = root->simple_rte_array[rti]; + + root->all_result_relids = bms_make_singleton(rti); + if (!rte->inh) + root->leaf_result_relids = bms_make_singleton(rti); + } + /* * Now expand appendrels by adding "otherrels" for their children. We * delay this to the end so that we have as much information as possible diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index a605387dcaa..c3c158a253d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -803,9 +803,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name, root->eq_classes = NIL; root->ec_merging_done = false; root->last_rinfo_serial = 0; - root->all_result_relids = - parse->resultRelation ? bms_make_singleton(parse->resultRelation) : NULL; - root->leaf_result_relids = NULL; /* we'll find out leaf-ness later */ + root->all_result_relids = NULL; + root->leaf_result_relids = NULL; root->append_rel_list = NIL; root->row_identity_vars = NIL; root->rowMarks = NIL; @@ -978,19 +977,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name, list_length(rte->securityQuals)); } - /* - * If we have now verified that the query target relation is - * non-inheriting, mark it as a leaf target. - */ - if (parse->resultRelation) - { - RangeTblEntry *rte = rt_fetch(parse->resultRelation, parse->rtable); - - if (!rte->inh) - root->leaf_result_relids = - bms_make_singleton(parse->resultRelation); - } - /* * This would be a convenient time to check access permissions for all * relations mentioned in the query, since it would be better to fail now, diff --git a/src/backend/optimizer/util/joininfo.c b/src/backend/optimizer/util/joininfo.c index ef2f054c39e..f75e35f22ae 100644 --- a/src/backend/optimizer/util/joininfo.c +++ b/src/backend/optimizer/util/joininfo.c @@ -145,39 +145,3 @@ add_join_clause_to_rels(PlannerInfo *root, rel->joininfo = lappend(rel->joininfo, restrictinfo); } } - -/* - * remove_join_clause_from_rels - * Delete 'restrictinfo' from all the joininfo lists it is in - * - * This reverses the effect of add_join_clause_to_rels. It's used when we - * discover that a relation need not be joined at all. - * - * 'restrictinfo' describes the join clause - * 'join_relids' is the set of relations participating in the join clause - * (some of these could be outer joins) - */ -void -remove_join_clause_from_rels(PlannerInfo *root, - RestrictInfo *restrictinfo, - Relids join_relids) -{ - int cur_relid; - - cur_relid = -1; - while ((cur_relid = bms_next_member(join_relids, cur_relid)) >= 0) - { - RelOptInfo *rel = find_base_rel_ignore_join(root, cur_relid); - - /* We would only have added the clause to baserels */ - if (rel == NULL) - continue; - - /* - * Remove the restrictinfo from the list. Pointer comparison is - * sufficient. - */ - Assert(list_member_ptr(rel->joininfo, restrictinfo)); - rel->joininfo = list_delete_ptr(rel->joininfo, restrictinfo); - } -} diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c index dd9b11885af..07171f84f61 100644 --- a/src/backend/optimizer/util/placeholder.c +++ b/src/backend/optimizer/util/placeholder.c @@ -316,33 +316,6 @@ fix_placeholder_input_needed_levels(PlannerInfo *root) } } -/* - * rebuild_placeholder_attr_needed - * Put back attr_needed bits for Vars/PHVs needed in PlaceHolderVars. - * - * This is used to rebuild attr_needed/ph_needed sets after removal of a - * useless outer join. It should match what - * fix_placeholder_input_needed_levels did, except that we call - * add_vars_to_attr_needed not add_vars_to_targetlist. - */ -void -rebuild_placeholder_attr_needed(PlannerInfo *root) -{ - ListCell *lc; - - foreach(lc, root->placeholder_list) - { - PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc); - List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr, - PVC_RECURSE_AGGREGATES | - PVC_RECURSE_WINDOWFUNCS | - PVC_INCLUDE_PLACEHOLDERS); - - add_vars_to_attr_needed(root, vars, phinfo->ph_eval_at); - list_free(vars); - } -} - /* * add_placeholders_to_base_rels * Add any required PlaceHolderVars to base rels' targetlists. diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index b318ae8a670..3653f00d383 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -64,6 +64,7 @@ static bool contain_windowfuncs_walker(Node *node, void *context); static bool locate_windowfunc_walker(Node *node, locate_windowfunc_context *context); static bool checkExprHasSubLink_walker(Node *node, void *context); +static Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid); static Node *add_nulling_relids_mutator(Node *node, add_nulling_relids_context *context); static Node *remove_nulling_relids_mutator(Node *node, @@ -528,23 +529,32 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up) * * Find all Var nodes in the given tree belonging to a specific relation * (identified by sublevels_up and rt_index), and change their varno fields - * to 'new_index'. The varnosyn fields are changed too. Also, adjust other - * nodes that contain rangetable indexes, such as RangeTblRef and JoinExpr. + * to 'new_index', and update varnosyn and varnullingrels fields similarly. + * Also adjust other nodes that contain rangetable indexes, such as + * RangeTblRef and JoinExpr. + * + * Also, new_index can be INVALID_VAR to indicate that we are deleting the + * given relid from the tree. In this case we expect to find rt_index only + * in Relids fields (varnullingrels, phnullingrels, phrels), never in any + * field that identifies a single relation. * * NOTE: although this has the form of a walker, we cheat and modify the * nodes in-place. The given expression tree should have been copied * earlier to ensure that no unwanted side-effects occur! */ +typedef struct +{ + int rt_index; + int new_index; + int sublevels_up; +} ChangeVarNodes_context; + static bool ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) { if (node == NULL) return false; - - if (context->callback && context->callback(node, context)) - return false; - if (IsA(node, Var)) { Var *var = (Var *) node; @@ -552,12 +562,18 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) if (var->varlevelsup == context->sublevels_up) { if (var->varno == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); var->varno = context->new_index; + } var->varnullingrels = adjust_relid_set(var->varnullingrels, context->rt_index, context->new_index); if (var->varnosyn == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); var->varnosyn = context->new_index; + } } return false; } @@ -567,7 +583,10 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) if (context->sublevels_up == 0 && cexpr->cvarno == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); cexpr->cvarno = context->new_index; + } return false; } if (IsA(node, RangeTblRef)) @@ -576,7 +595,10 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) if (context->sublevels_up == 0 && rtr->rtindex == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); rtr->rtindex = context->new_index; + } /* the subquery itself is visited separately */ return false; } @@ -586,7 +608,10 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) if (context->sublevels_up == 0 && j->rtindex == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); j->rtindex = context->new_index; + } /* fall through to examine children */ } if (IsA(node, PlaceHolderVar)) @@ -611,9 +636,15 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) if (context->sublevels_up == 0) { if (rowmark->rti == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); rowmark->rti = context->new_index; + } if (rowmark->prti == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); rowmark->prti = context->new_index; + } } return false; } @@ -624,9 +655,15 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) if (context->sublevels_up == 0) { if (appinfo->parent_relid == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); appinfo->parent_relid = context->new_index; + } if (appinfo->child_relid == context->rt_index) + { + Assert(context->new_index != INVALID_VAR); appinfo->child_relid = context->new_index; + } } /* fall through to examine children */ } @@ -649,28 +686,14 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) return expression_tree_walker(node, ChangeVarNodes_walker, context); } -/* - * ChangeVarNodesExtended - similar to ChangeVarNodes, but with an additional - * 'callback' param - * - * ChangeVarNodes changes a given node and all of its underlying nodes. This - * version of function additionally takes a callback, which has a chance to - * process a node before ChangeVarNodes_walker. A callback returns a boolean - * value indicating if the given node should be skipped from further processing - * by ChangeVarNodes_walker. The callback is called only for expressions and - * other children nodes of a Query processed by a walker. Initial processing - * of the root Query node doesn't invoke the callback. - */ void -ChangeVarNodesExtended(Node *node, int rt_index, int new_index, - int sublevels_up, ChangeVarNodes_callback callback) +ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up) { ChangeVarNodes_context context; context.rt_index = rt_index; context.new_index = new_index; context.sublevels_up = sublevels_up; - context.callback = callback; /* * Must be prepared to start with a Query or a bare expression tree; if @@ -694,21 +717,33 @@ ChangeVarNodesExtended(Node *node, int rt_index, int new_index, ListCell *l; if (qry->resultRelation == rt_index) + { + Assert(new_index != INVALID_VAR); qry->resultRelation = new_index; + } if (qry->mergeTargetRelation == rt_index) + { + Assert(new_index != INVALID_VAR); qry->mergeTargetRelation = new_index; + } /* this is unlikely to ever be used, but ... */ if (qry->onConflict && qry->onConflict->exclRelIndex == rt_index) + { + Assert(new_index != INVALID_VAR); qry->onConflict->exclRelIndex = new_index; + } foreach(l, qry->rowMarks) { RowMarkClause *rc = (RowMarkClause *) lfirst(l); if (rc->rti == rt_index) + { + Assert(new_index != INVALID_VAR); rc->rti = new_index; + } } } query_tree_walker(qry, ChangeVarNodes_walker, &context, 0); @@ -717,36 +752,6 @@ ChangeVarNodesExtended(Node *node, int rt_index, int new_index, ChangeVarNodes_walker(node, &context); } -void -ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up) -{ - ChangeVarNodesExtended(node, rt_index, new_index, sublevels_up, NULL); -} - -/* - * ChangeVarNodesWalkExpression - process subexpression within a callback - * function passed to ChangeVarNodesExtended. - * - * This is intended to be used by a callback that needs to recursively - * process subexpressions of some node being visited by an outer - * ChangeVarNodesExtended call, instead of relying on ChangeVarNodes_walker's - * default recursion. We invoke ChangeVarNodes_walker directly rather than - * via expression_tree_walker, because expression_tree_walker only visits - * child nodes and would fail to process the passed node itself -- - * for example, a bare Var node would not get its varno adjusted. - * - * Because this calls ChangeVarNodes_walker directly, if the passed node is - * a Query, it will be treated as a sub-Query: sublevels_up is incremented - * before recursing into it, and Query-level fields (resultRelation, - * mergeTargetRelation, rowMarks, etc.) will not be adjusted. Do not apply - * this to a top-level Query node; use ChangeVarNodesExtended for that. - */ -bool -ChangeVarNodesWalkExpression(Node *node, ChangeVarNodes_context *context) -{ - return ChangeVarNodes_walker(node, context); -} - /* * adjust_relid_set - substitute newrelid for oldrelid in a Relid set * @@ -756,7 +761,7 @@ ChangeVarNodesWalkExpression(Node *node, ChangeVarNodes_context *context) * a special varno, this function does nothing. When newrelid is a special * varno, this function behaves as delete. */ -Relids +static Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid) { if (!IS_SPECIAL_VARNO(oldrelid) && bms_is_member(oldrelid, relids)) diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 44f828cbb37..5a636d1f179 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -217,6 +217,9 @@ typedef struct Expr * row identity information during UPDATE/DELETE/MERGE. This value should * never be seen outside the planner. * + * INVALID_VAR should never appear as anything's varno. We use it in a + * few APIs to denote removal of an RTE. + * * varnullingrels is the set of RT indexes of outer joins that can force * the Var's value to null (at the point where it appears in the query). * See optimizer/README for discussion of that. @@ -244,6 +247,7 @@ typedef struct Expr #define OUTER_VAR (-2) /* reference to outer subplan */ #define INDEX_VAR (-3) /* reference to index column */ #define ROWID_VAR (-4) /* row identity column during planning */ +#define INVALID_VAR (-5) /* this is not a valid varno! */ #define IS_SPECIAL_VARNO(varno) ((int) (varno) < 0) diff --git a/src/include/optimizer/joininfo.h b/src/include/optimizer/joininfo.h index 117195fbcfa..aa26cc670f9 100644 --- a/src/include/optimizer/joininfo.h +++ b/src/include/optimizer/joininfo.h @@ -23,8 +23,5 @@ extern bool have_relevant_joinclause(PlannerInfo *root, extern void add_join_clause_to_rels(PlannerInfo *root, RestrictInfo *restrictinfo, Relids join_relids); -extern void remove_join_clause_from_rels(PlannerInfo *root, - RestrictInfo *restrictinfo, - Relids join_relids); #endif /* JOININFO_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 17f2099ec3b..051e38c1189 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -137,7 +137,6 @@ extern bool process_equivalence(PlannerInfo *root, 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 EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root, Expr *expr, List *opfamilies, @@ -208,7 +207,6 @@ 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 void ec_clear_derived_clauses(EquivalenceClass *ec); /* * pathkeys.c diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h index 60798281090..2099075e670 100644 --- a/src/include/optimizer/placeholder.h +++ b/src/include/optimizer/placeholder.h @@ -23,7 +23,6 @@ extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv); extern void find_placeholders_in_jointree(PlannerInfo *root); extern void fix_placeholder_input_needed_levels(PlannerInfo *root); -extern void rebuild_placeholder_attr_needed(PlannerInfo *root); extern void add_placeholders_to_base_rels(PlannerInfo *root); extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 71c043a25e8..c23ab568b98 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -73,12 +73,9 @@ extern void add_other_rels_to_query(PlannerInfo *root); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed); -extern void add_vars_to_attr_needed(PlannerInfo *root, List *vars, - Relids where_needed); extern void remove_useless_groupby_columns(PlannerInfo *root); extern void setup_eager_aggregation(PlannerInfo *root); extern void find_lateral_references(PlannerInfo *root); -extern void rebuild_lateral_attr_needed(PlannerInfo *root); extern void create_lateral_join_info(PlannerInfo *root); extern List *deconstruct_jointree(PlannerInfo *root); extern bool restriction_is_always_true(PlannerInfo *root, @@ -102,24 +99,19 @@ extern RestrictInfo *build_implied_join_equality(PlannerInfo *root, Expr *item2, Relids qualscope, Index security_level); -extern void rebuild_joinclause_attr_needed(PlannerInfo *root); extern void match_foreign_keys_to_quals(PlannerInfo *root); /* * prototypes for plan/analyzejoins.c */ -extern List *remove_useless_joins(PlannerInfo *root, List *joinlist); -extern void reduce_unique_semijoins(PlannerInfo *root); +extern bool remove_useless_outer_joins(PlannerInfo *root); +extern bool reduce_unique_semijoins(PlannerInfo *root); extern bool query_supports_distinctness(Query *query); extern bool query_is_distinct_for(Query *query, List *distinct_cols); extern bool innerrel_is_unique(PlannerInfo *root, Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel, JoinType jointype, List *restrictlist, bool force_cache); -extern bool innerrel_is_unique_ext(PlannerInfo *root, Relids joinrelids, - Relids outerrelids, RelOptInfo *innerrel, - JoinType jointype, List *restrictlist, - bool force_cache, List **extra_clauses); -extern List *remove_useless_self_joins(PlannerInfo *root, List *joinlist); +extern bool remove_useless_self_joins(PlannerInfo *root, List *joinlist); /* * prototypes for plan/setrefs.c diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h index 9d234cb8741..2d15d8f8f95 100644 --- a/src/include/rewrite/rewriteManip.h +++ b/src/include/rewrite/rewriteManip.h @@ -41,30 +41,11 @@ typedef enum ReplaceVarsNoMatchOption REPLACEVARS_SUBSTITUTE_NULL, /* replace with a NULL Const */ } ReplaceVarsNoMatchOption; -typedef struct ChangeVarNodes_context ChangeVarNodes_context; - -typedef bool (*ChangeVarNodes_callback) (Node *node, - ChangeVarNodes_context *arg); - -struct ChangeVarNodes_context -{ - int rt_index; - int new_index; - int sublevels_up; - ChangeVarNodes_callback callback; -}; - -pg_nodiscard extern Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid); extern void CombineRangeTables(List **dst_rtable, List **dst_perminfos, List *src_rtable, List *src_perminfos); extern void OffsetVarNodes(Node *node, int offset, int sublevels_up); extern void ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up); -extern void ChangeVarNodesExtended(Node *node, int rt_index, int new_index, - int sublevels_up, - ChangeVarNodes_callback callback); -extern bool ChangeVarNodesWalkExpression(Node *node, - ChangeVarNodes_context *context); extern void IncrementVarSublevelsUp(Node *node, int delta_sublevels_up, int min_sublevels_up); extern void IncrementVarSublevelsUp_rtable(List *rtable, diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 9261e13442d..db4fcc5a5a0 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -7681,6 +7681,38 @@ where t1.a = s.c; ---------- (0 rows) +rollback; +-- join removal bug #19560: removing a join can leave an EquivalenceClass that +-- now yields a base restriction clause, so we must redo equivalence +-- processing from scratch +begin; +create temp table items (id text, owner text); +create temp table follows (item_id text, user_id text, + unique (user_id, item_id)); +insert into items values ('item1', 'alice'); +explain (costs off) +with viewer as (select 'bob' as id) +select count(*) from items + left join follows on follows.item_id = items.id and follows.user_id = 'bob' + left join viewer on true +where items.owner = viewer.id; + QUERY PLAN +--------------------------------------- + Aggregate + -> Seq Scan on items + Filter: (owner = 'bob'::text) +(3 rows) + +with viewer as (select 'bob' as id) +select count(*) from items + left join follows on follows.item_id = items.id and follows.user_id = 'bob' + left join viewer on true +where items.owner = viewer.id; + count +------- + 0 +(1 row) + rollback; -- check handling of semijoins after join removal: we must suppress -- unique-ification of known-constant values @@ -7706,17 +7738,17 @@ where exists (select 1 from t t4 Output: t1.a Index Cond: (t1.a = 1) -> HashAggregate - Output: t5.a + Output: t4.a, t5.a Group Key: t5.a -> Hash Join - Output: t5.a + Output: t4.a, t5.a Hash Cond: (t6.b = t4.b) -> Seq Scan on pg_temp.t t6 Output: t6.a, t6.b -> Hash - Output: t4.b, t5.b, t5.a + Output: t4.b, t4.a, t5.b, t5.a -> Hash Join - Output: t4.b, t5.b, t5.a + Output: t4.b, t4.a, t5.b, t5.a Inner Unique: true Hash Cond: (t5.b = t4.b) -> Seq Scan on pg_temp.t t5 @@ -8148,7 +8180,7 @@ on q1.ax = q2.a; Nested Loop Left Join Join Filter: (t2.a = t4.a) -> Seq Scan on sj t2 - Filter: ((b IS NULL) AND (a IS NOT NULL) AND ((c * c) = (c + 2))) + Filter: ((a IS NOT NULL) AND (b IS NULL) AND ((c * c) = (c + 2))) -> Seq Scan on sj t4 Filter: (c IS NOT NULL) (6 rows) @@ -8231,12 +8263,70 @@ select t1.a from sj t1 where t1.b in ( -> Seq Scan on public.sj t1 Output: t1.a, t1.b, t1.c -> Materialize - Output: t3.c, t3.b + Output: t3.b -> Seq Scan on public.sj t3 - Output: t3.c, t3.b + Output: t3.b Filter: (t3.c IS NOT NULL) (10 rows) +-- Check that quals get hoisted to the appropriate join level after SJE removal +explain (verbose, costs off) +select a2.a +from sj b1 + join sj a1 on b1.b = a1.a + join sj a2 on a2.a = a1.a and a2.b = a1.b; + QUERY PLAN +------------------------------------------------------------- + Nested Loop + Output: a2.a + Join Filter: (b1.b = a2.a) + -> Seq Scan on public.sj a2 + Output: a2.a, a2.b, a2.c + Filter: ((a2.a IS NOT NULL) AND (a2.b IS NOT NULL)) + -> Seq Scan on public.sj b1 + Output: b1.a, b1.b, b1.c +(8 rows) + +-- Same, when a semijoin removal happens first +explain (verbose, costs off) +select a1.a from sj b1 join sj a1 on a1.a = b1.b + where exists (select 1 from sj s where s.a = a1.a); + QUERY PLAN +----------------------------------------- + Nested Loop + Output: s.a + Inner Unique: true + Join Filter: (b1.b = s.a) + -> Seq Scan on public.sj b1 + Output: b1.a, b1.b, b1.c + -> Materialize + Output: s.a + -> Seq Scan on public.sj s + Output: s.a + Filter: (s.a IS NOT NULL) +(11 rows) + +-- A different case, where modified qual is on a lower join level +explain (verbose, costs off) +select a2.a +from ((sj b1 join sj a1 on true) join sj c1 on c1.b = a1.a) + join sj a2 on a2.a = a1.a and a2.b = a1.b; + QUERY PLAN +------------------------------------------------------------------- + Nested Loop + Output: a2.a + -> Nested Loop + Output: a2.a + Join Filter: (c1.b = a2.a) + -> Seq Scan on public.sj a2 + Output: a2.a, a2.b, a2.c + Filter: ((a2.a IS NOT NULL) AND (a2.b IS NOT NULL)) + -> Seq Scan on public.sj c1 + Output: c1.a, c1.b, c1.c + -> Seq Scan on public.sj b1 + Output: b1.a, b1.b, b1.c +(12 rows) + -- -- SJE corner case: uniqueness of an inner is [partially] derived from -- baserestrictinfo clauses. @@ -8567,15 +8657,15 @@ explain (costs off) select * from sj p join sj q on p.a = q.a -> Seq Scan on sj r (6 rows) --- FIXME this constant false filter doesn't look good. Should we merge --- equivalence classes? +-- Check that we detect constant-false condition after merging ECs. explain (costs off) select * from sj p, sj q where p.a = q.a and p.b = 1 and q.b = 2; - QUERY PLAN ------------------------------------------------------ - Seq Scan on sj q - Filter: ((a IS NOT NULL) AND (b = 2) AND (b = 1)) -(2 rows) + QUERY PLAN +-------------------------- + Result + Replaces: Scan on q + One-Time Filter: false +(3 rows) -- Check that attr_needed is updated correctly after self-join removal. In this -- test, the join of j1 with j2 is removed. k1.b is required at either j1 or j2. @@ -8711,11 +8801,9 @@ where s1.x = 1; -> Seq Scan on public.emp1 t1 Output: t1.id, t1.code -> Materialize - Output: t3.id -> Seq Scan on public.emp1 t3 - Output: t3.id Filter: (1 = 1) -(9 rows) +(7 rows) -- Check that PHVs do not impose any constraints on removing self joins explain (verbose, costs off) @@ -8765,14 +8853,14 @@ SELECT 1 FROM tbl_phv t1 LEFT JOIN (SELECT y FROM tbl_phv tr) t4 ON t4.y = t3.y ON true WHERE t3.extra IS NOT NULL AND t3.x = t1.x % 2; - QUERY PLAN ---------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------- Nested Loop Output: 1 -> Seq Scan on public.tbl_phv t1 Output: t1.x, t1.y - -> Index Scan using tbl_phv_idx on public.tbl_phv tr - Output: tr.x, tr.y + -> Index Only Scan using tbl_phv_idx on public.tbl_phv tr + Output: tr.x Index Cond: (tr.x = (t1.x % 2)) Filter: (1 IS NOT NULL) (8 rows) @@ -8953,7 +9041,7 @@ where t1.b = t2.b and t2.a = 3 and t1.a = 3 --------------------------------------------------------------------------------------------- Seq Scan on public.sl t2 Output: t2.a, t2.b, t2.c, t2.a, t2.b, t2.c - Filter: ((t2.c IS NOT NULL) AND (t2.b IS NOT NULL) AND (t2.a IS NOT NULL) AND (t2.a = 3)) + Filter: ((t2.b IS NOT NULL) AND (t2.c IS NOT NULL) AND (t2.a IS NOT NULL) AND (t2.a = 3)) (3 rows) -- Join qual isn't mergejoinable, but inner is unique. @@ -8995,10 +9083,35 @@ SELECT 1 AS c1 FROM sl sl1 LEFT JOIN (sl AS sl2 NATURAL JOIN sl AS sl3) -> Nested Loop Left Join Join Filter: sl3.bool_col -> Seq Scan on sl sl3 - Filter: (bool_col AND (a IS NOT NULL) AND (b IS NOT NULL) AND (c IS NOT NULL) AND (bool_col IS NOT NULL)) + Filter: ((a IS NOT NULL) AND (b IS NOT NULL) AND (c IS NOT NULL) AND (bool_col IS NOT NULL) AND bool_col) -> Seq Scan on sl sl4 (7 rows) +-- Check that quals of a jointree node that becomes empty when the self-join +-- is removed are not lost, and that they don't migrate above an outer join +EXPLAIN (COSTS OFF) +SELECT s.a FROM (SELECT * FROM sl WHERE c IS NOT NULL) s, sl t +WHERE t.a = s.a AND t.b = s.b; + QUERY PLAN +--------------------------------------------------------------------- + Seq Scan on sl + Filter: ((c IS NOT NULL) AND (a IS NOT NULL) AND (b IS NOT NULL)) +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT t1.a, ss.a FROM sl t1 + LEFT JOIN (SELECT s.a FROM (SELECT * FROM sl WHERE c IS NOT NULL) s + JOIN sl t ON t.a = s.a AND t.b = s.b) ss + ON ss.a = t1.a; + QUERY PLAN +--------------------------------------------------------------------------- + Nested Loop Left Join + Join Filter: (sl.a = t1.a) + -> Seq Scan on sl t1 + -> Seq Scan on sl + Filter: ((c IS NOT NULL) AND (a IS NOT NULL) AND (b IS NOT NULL)) +(5 rows) + -- Check optimization disabling if it will violate special join conditions. -- Two identical joined relations satisfies self join removal conditions but -- stay in different special join infos. diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index f42085e53a0..356f118a366 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -536,6 +536,17 @@ NOTICE: f_leak => awesome science fiction 9 | 22 | 1 | regress_rls_dave | awesome science fiction (4 rows) +-- a rel with RLS quals can still be removed by outer-join removal +EXPLAIN (COSTS OFF) +SELECT c.cid FROM category c LEFT JOIN document d ON c.cid = d.did; + QUERY PLAN +---------------------------------------------------- + Seq Scan on category c + InitPlan expr_1 + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(4 rows) + -- viewpoint from regress_rls_carol SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 9248245045b..9533af8656e 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2898,6 +2898,31 @@ where t1.a = s.c; rollback; +-- join removal bug #19560: removing a join can leave an EquivalenceClass that +-- now yields a base restriction clause, so we must redo equivalence +-- processing from scratch +begin; + +create temp table items (id text, owner text); +create temp table follows (item_id text, user_id text, + unique (user_id, item_id)); +insert into items values ('item1', 'alice'); + +explain (costs off) +with viewer as (select 'bob' as id) +select count(*) from items + left join follows on follows.item_id = items.id and follows.user_id = 'bob' + left join viewer on true +where items.owner = viewer.id; + +with viewer as (select 'bob' as id) +select count(*) from items + left join follows on follows.item_id = items.id and follows.user_id = 'bob' + left join viewer on true +where items.owner = viewer.id; + +rollback; + -- check handling of semijoins after join removal: we must suppress -- unique-ification of known-constant values begin; @@ -3164,6 +3189,24 @@ explain (verbose, costs off) select t1.a from sj t1 where t1.b in ( select t2.b from sj t2 join sj t3 on t2.c=t3.c); +-- Check that quals get hoisted to the appropriate join level after SJE removal +explain (verbose, costs off) +select a2.a +from sj b1 + join sj a1 on b1.b = a1.a + join sj a2 on a2.a = a1.a and a2.b = a1.b; + +-- Same, when a semijoin removal happens first +explain (verbose, costs off) +select a1.a from sj b1 join sj a1 on a1.a = b1.b + where exists (select 1 from sj s where s.a = a1.a); + +-- A different case, where modified qual is on a lower join level +explain (verbose, costs off) +select a2.a +from ((sj b1 join sj a1 on true) join sj c1 on c1.b = a1.a) + join sj a2 on a2.a = a1.a and a2.b = a1.b; + -- -- SJE corner case: uniqueness of an inner is [partially] derived from -- baserestrictinfo clauses. @@ -3312,8 +3355,7 @@ select 1 from (select y.* from sj x, sj y where x.a = y.a) q, explain (costs off) select * from sj p join sj q on p.a = q.a left join sj r on p.a + q.a = r.a; --- FIXME this constant false filter doesn't look good. Should we merge --- equivalence classes? +-- Check that we detect constant-false condition after merging ECs. explain (costs off) select * from sj p, sj q where p.a = q.a and p.b = 1 and q.b = 2; @@ -3511,6 +3553,18 @@ EXPLAIN (COSTS OFF) SELECT 1 AS c1 FROM sl sl1 LEFT JOIN (sl AS sl2 NATURAL JOIN sl AS sl3) ON sl2.bool_col LEFT JOIN sl AS sl4 ON sl2.bool_col; +-- Check that quals of a jointree node that becomes empty when the self-join +-- is removed are not lost, and that they don't migrate above an outer join +EXPLAIN (COSTS OFF) +SELECT s.a FROM (SELECT * FROM sl WHERE c IS NOT NULL) s, sl t +WHERE t.a = s.a AND t.b = s.b; + +EXPLAIN (COSTS OFF) +SELECT t1.a, ss.a FROM sl t1 + LEFT JOIN (SELECT s.a FROM (SELECT * FROM sl WHERE c IS NOT NULL) s + JOIN sl t ON t.a = s.a AND t.b = s.b) ss + ON ss.a = t1.a; + -- Check optimization disabling if it will violate special join conditions. -- Two identical joined relations satisfies self join removal conditions but -- stay in different special join infos. diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql index ed70b1b229e..99eaba28f5a 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -307,6 +307,10 @@ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) WHERE f_leak(dtitle) ORDER BY did; +-- a rel with RLS quals can still be removed by outer-join removal +EXPLAIN (COSTS OFF) +SELECT c.cid FROM category c LEFT JOIN document d ON c.cid = d.did; + -- viewpoint from regress_rls_carol SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 59cf40b5bb0..15b10e1703f 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -431,7 +431,6 @@ CatalogId CatalogIdMapEntry CatalogIndexState ChangeContext -ChangeVarNodes_callback ChangeVarNodes_context ChannelName CheckPoint