diff options
Diffstat (limited to 'src/backend/optimizer/prep/prepjointree.c')
-rw-r--r-- | src/backend/optimizer/prep/prepjointree.c | 544 |
1 files changed, 317 insertions, 227 deletions
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 37a7af8c669..9c96a558fc3 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -51,17 +51,28 @@ typedef struct pullup_replace_vars_context * pullup (set only if target_rte->lateral) */ bool *outer_hasSubLinks; /* -> outer query's hasSubLinks */ int varno; /* varno of subquery */ - bool need_phvs; /* do we need PlaceHolderVars? */ - bool wrap_non_vars; /* do we need 'em on *all* non-Vars? */ + bool wrap_non_vars; /* do we need all non-Var outputs to be PHVs? */ Node **rv_cache; /* cache for results with PHVs */ } pullup_replace_vars_context; -typedef struct reduce_outer_joins_state +typedef struct reduce_outer_joins_pass1_state { Relids relids; /* base relids within this subtree */ bool contains_outer; /* does subtree contain outer join(s)? */ List *sub_states; /* List of states for subtree components */ -} reduce_outer_joins_state; +} reduce_outer_joins_pass1_state; + +typedef struct reduce_outer_joins_pass2_state +{ + Relids inner_reduced; /* OJ relids reduced to plain inner joins */ + List *partial_reduced; /* List of partially reduced FULL joins */ +} reduce_outer_joins_pass2_state; + +typedef struct reduce_outer_joins_partial_state +{ + int full_join_rti; /* RT index of a formerly-FULL join */ + Relids unreduced_side; /* relids in its still-nullable side */ +} reduce_outer_joins_partial_state; static Node *pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode, Relids *relids); @@ -70,12 +81,10 @@ static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, Node **jtlink2, Relids available_rels2); static Node *pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, JoinExpr *lowest_outer_join, - JoinExpr *lowest_nulling_outer_join, AppendRelInfo *containing_appendrel); static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, JoinExpr *lowest_outer_join, - JoinExpr *lowest_nulling_outer_join, AppendRelInfo *containing_appendrel); static Node *pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte); @@ -92,7 +101,6 @@ static Node *pull_up_simple_values(PlannerInfo *root, Node *jtnode, static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte); static Node *pull_up_constant_function(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, - JoinExpr *lowest_nulling_outer_join, AppendRelInfo *containing_appendrel); static bool is_simple_union_all(Query *subquery); static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, @@ -103,24 +111,26 @@ static bool jointree_contains_lateral_outer_refs(PlannerInfo *root, Relids safe_upper_varnos); static void perform_pullup_replace_vars(PlannerInfo *root, pullup_replace_vars_context *rvcontext, - JoinExpr *lowest_nulling_outer_join, AppendRelInfo *containing_appendrel); static void replace_vars_in_jointree(Node *jtnode, - pullup_replace_vars_context *context, - JoinExpr *lowest_nulling_outer_join); + pullup_replace_vars_context *context); static Node *pullup_replace_vars(Node *expr, pullup_replace_vars_context *context); static Node *pullup_replace_vars_callback(Var *var, replace_rte_variables_context *context); static Query *pullup_replace_vars_subquery(Query *query, pullup_replace_vars_context *context); -static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode); +static reduce_outer_joins_pass1_state *reduce_outer_joins_pass1(Node *jtnode); static void reduce_outer_joins_pass2(Node *jtnode, - reduce_outer_joins_state *state, + reduce_outer_joins_pass1_state *state1, + reduce_outer_joins_pass2_state *state2, PlannerInfo *root, Relids nonnullable_rels, List *forced_null_vars); -static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode); +static void report_reduced_full_join(reduce_outer_joins_pass2_state *state2, + int rtindex, Relids relids); +static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, + Relids *dropped_outer_joins); static int get_result_relid(PlannerInfo *root, Node *jtnode); static void remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc); static bool find_dependent_phvs(PlannerInfo *root, int varno); @@ -761,7 +771,7 @@ pull_up_subqueries(PlannerInfo *root) /* Recursion starts with no containing join nor appendrel */ root->parse->jointree = (FromExpr *) pull_up_subqueries_recurse(root, (Node *) root->parse->jointree, - NULL, NULL, NULL); + NULL, NULL); /* We should still have a FromExpr */ Assert(IsA(root->parse->jointree, FromExpr)); } @@ -776,12 +786,6 @@ pull_up_subqueries(PlannerInfo *root) * lowest_outer_join references the lowest such JoinExpr node; otherwise * it is NULL. We use this to constrain the effects of LATERAL subqueries. * - * If this jointree node is within the nullable side of an outer join, then - * lowest_nulling_outer_join references the lowest such JoinExpr node; - * otherwise it is NULL. This forces use of the PlaceHolderVar mechanism for - * references to non-nullable targetlist items, but only for references above - * that join. - * * If we are looking at a member subquery of an append relation, * containing_appendrel describes that relation; else it is NULL. * This forces use of the PlaceHolderVar mechanism for all non-Var targetlist @@ -798,15 +802,14 @@ pull_up_subqueries(PlannerInfo *root) * Notice also that we can't turn pullup_replace_vars loose on the whole * jointree, because it'd return a mutated copy of the tree; we have to * invoke it just on the quals, instead. This behavior is what makes it - * reasonable to pass lowest_outer_join and lowest_nulling_outer_join as - * pointers rather than some more-indirect way of identifying the lowest - * OJs. Likewise, we don't replace append_rel_list members but only their - * substructure, so the containing_appendrel reference is safe to use. + * reasonable to pass lowest_outer_join as a pointer rather than some + * more-indirect way of identifying the lowest OJ. Likewise, we don't + * replace append_rel_list members but only their substructure, so the + * containing_appendrel reference is safe to use. */ static Node * pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, JoinExpr *lowest_outer_join, - JoinExpr *lowest_nulling_outer_join, AppendRelInfo *containing_appendrel) { /* Since this function recurses, it could be driven to stack overflow. */ @@ -833,7 +836,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, is_safe_append_member(rte->subquery))) return pull_up_simple_subquery(root, jtnode, rte, lowest_outer_join, - lowest_nulling_outer_join, containing_appendrel); /* @@ -866,7 +868,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, */ if (rte->rtekind == RTE_FUNCTION) return pull_up_constant_function(root, jtnode, rte, - lowest_nulling_outer_join, containing_appendrel); /* Otherwise, do nothing at this node. */ @@ -882,7 +883,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, { lfirst(l) = pull_up_subqueries_recurse(root, lfirst(l), lowest_outer_join, - lowest_nulling_outer_join, NULL); } } @@ -897,11 +897,9 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, case JOIN_INNER: j->larg = pull_up_subqueries_recurse(root, j->larg, lowest_outer_join, - lowest_nulling_outer_join, NULL); j->rarg = pull_up_subqueries_recurse(root, j->rarg, lowest_outer_join, - lowest_nulling_outer_join, NULL); break; case JOIN_LEFT: @@ -909,31 +907,25 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, case JOIN_ANTI: j->larg = pull_up_subqueries_recurse(root, j->larg, j, - lowest_nulling_outer_join, NULL); j->rarg = pull_up_subqueries_recurse(root, j->rarg, j, - j, NULL); break; case JOIN_FULL: j->larg = pull_up_subqueries_recurse(root, j->larg, j, - j, NULL); j->rarg = pull_up_subqueries_recurse(root, j->rarg, j, - j, NULL); break; case JOIN_RIGHT: j->larg = pull_up_subqueries_recurse(root, j->larg, j, - j, NULL); j->rarg = pull_up_subqueries_recurse(root, j->rarg, j, - lowest_nulling_outer_join, NULL); break; default: @@ -963,7 +955,6 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, static Node * pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, JoinExpr *lowest_outer_join, - JoinExpr *lowest_nulling_outer_join, AppendRelInfo *containing_appendrel) { Query *parse = root->parse; @@ -1001,6 +992,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->multiexpr_params = NIL; subroot->eq_classes = NIL; subroot->ec_merging_done = false; + subroot->last_rinfo_serial = 0; subroot->all_result_relids = NULL; subroot->leaf_result_relids = NULL; subroot->append_rel_list = NIL; @@ -1090,7 +1082,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, * maybe even in the rewriter; but for now let's just fix this case here.) */ subquery->targetList = (List *) - flatten_join_alias_vars(subroot->parse, (Node *) subquery->targetList); + flatten_join_alias_vars(subroot, subroot->parse, + (Node *) subquery->targetList); /* * Adjust level-0 varnos in subquery so that we can append its rangetable @@ -1112,32 +1105,26 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, * The subquery's targetlist items are now in the appropriate form to * insert into the top query, except that we may need to wrap them in * PlaceHolderVars. Set up required context data for pullup_replace_vars. + * (Note that we should include the subquery's inner joins in relids, + * since it may include join alias vars referencing them.) */ rvcontext.root = root; rvcontext.targetlist = subquery->targetList; rvcontext.target_rte = rte; if (rte->lateral) rvcontext.relids = get_relids_in_jointree((Node *) subquery->jointree, - true); + true, true); else /* won't need relids */ rvcontext.relids = NULL; rvcontext.outer_hasSubLinks = &parse->hasSubLinks; rvcontext.varno = varno; - /* these flags will be set below, if needed */ - rvcontext.need_phvs = false; + /* this flag will be set below, if needed */ rvcontext.wrap_non_vars = false; /* initialize cache array with indexes 0 .. length(tlist) */ rvcontext.rv_cache = palloc0((list_length(subquery->targetList) + 1) * sizeof(Node *)); /* - * If we are under an outer join then non-nullable items and lateral - * references may have to be turned into PlaceHolderVars. - */ - if (lowest_nulling_outer_join != NULL) - rvcontext.need_phvs = true; - - /* * If we are dealing with an appendrel member then anything that's not a * simple Var has to be turned into a PlaceHolderVar. We force this to * ensure that what we pull up doesn't get merged into a surrounding @@ -1145,10 +1132,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, * expression actually available from the appendrel. */ if (containing_appendrel != NULL) - { - rvcontext.need_phvs = true; rvcontext.wrap_non_vars = true; - } /* * If the parent query uses grouping sets, we need a PlaceHolderVar for @@ -1160,10 +1144,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, * that pullup_replace_vars hasn't currently got.) */ if (parse->groupingSets) - { - rvcontext.need_phvs = true; rvcontext.wrap_non_vars = true; - } /* * Replace all of the top query's references to the subquery's outputs @@ -1171,7 +1152,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, * replace any of the jointree structure. */ perform_pullup_replace_vars(root, &rvcontext, - lowest_nulling_outer_join, containing_appendrel); /* @@ -1238,7 +1218,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, { Relids subrelids; - subrelids = get_relids_in_jointree((Node *) subquery->jointree, false); + subrelids = get_relids_in_jointree((Node *) subquery->jointree, + true, false); if (root->glob->lastPHId != 0) substitute_phv_relids((Node *) parse, varno, subrelids); fix_append_rel_relids(root, varno, subrelids); @@ -1434,7 +1415,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex, rtr = makeNode(RangeTblRef); rtr->rtindex = childRTindex; (void) pull_up_subqueries_recurse(root, (Node *) rtr, - NULL, NULL, appinfo); + NULL, appinfo); } else if (IsA(setOp, SetOperationStmt)) { @@ -1571,7 +1552,7 @@ is_simple_subquery(PlannerInfo *root, Query *subquery, RangeTblEntry *rte, { restricted = true; safe_upper_varnos = get_relids_in_jointree((Node *) lowest_outer_join, - true); + true, true); } else { @@ -1683,7 +1664,6 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) rvcontext.relids = NULL; rvcontext.outer_hasSubLinks = &parse->hasSubLinks; rvcontext.varno = varno; - rvcontext.need_phvs = false; rvcontext.wrap_non_vars = false; /* initialize cache array with indexes 0 .. length(tlist) */ rvcontext.rv_cache = palloc0((list_length(tlist) + 1) * @@ -1695,7 +1675,7 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) * any of the jointree structure. We can assume there's no outer joins or * appendrels in the dummy Query that surrounds a VALUES RTE. */ - perform_pullup_replace_vars(root, &rvcontext, NULL, NULL); + perform_pullup_replace_vars(root, &rvcontext, NULL); /* * There should be no appendrels to fix, nor any outer joins and hence no @@ -1794,7 +1774,6 @@ is_simple_values(PlannerInfo *root, RangeTblEntry *rte) static Node * pull_up_constant_function(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, - JoinExpr *lowest_nulling_outer_join, AppendRelInfo *containing_appendrel) { Query *parse = root->parse; @@ -1846,40 +1825,26 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode, rvcontext.outer_hasSubLinks = &parse->hasSubLinks; rvcontext.varno = ((RangeTblRef *) jtnode)->rtindex; - /* these flags will be set below, if needed */ - rvcontext.need_phvs = false; + /* this flag will be set below, if needed */ rvcontext.wrap_non_vars = false; /* initialize cache array with indexes 0 .. length(tlist) */ rvcontext.rv_cache = palloc0((list_length(rvcontext.targetlist) + 1) * sizeof(Node *)); /* - * If we are under an outer join then non-nullable items and lateral - * references may have to be turned into PlaceHolderVars. - */ - if (lowest_nulling_outer_join != NULL) - rvcontext.need_phvs = true; - - /* * If we are dealing with an appendrel member then anything that's not a * simple Var has to be turned into a PlaceHolderVar. (See comments in * pull_up_simple_subquery().) */ if (containing_appendrel != NULL) - { - rvcontext.need_phvs = true; rvcontext.wrap_non_vars = true; - } /* * If the parent query uses grouping sets, we need a PlaceHolderVar for * anything that's not a simple Var. */ if (parse->groupingSets) - { - rvcontext.need_phvs = true; rvcontext.wrap_non_vars = true; - } /* * Replace all of the top query's references to the RTE's output with @@ -1887,7 +1852,6 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode, * jointree structure. */ perform_pullup_replace_vars(root, &rvcontext, - lowest_nulling_outer_join, containing_appendrel); /* @@ -2112,13 +2076,11 @@ jointree_contains_lateral_outer_refs(PlannerInfo *root, Node *jtnode, * * Caller has already filled *rvcontext with data describing what to * substitute for Vars referencing the target subquery. In addition - * we need the identity of the lowest outer join that can null the - * target subquery, and its containing appendrel if any. + * we need the identity of the containing appendrel if any. */ static void perform_pullup_replace_vars(PlannerInfo *root, pullup_replace_vars_context *rvcontext, - JoinExpr *lowest_nulling_outer_join, AppendRelInfo *containing_appendrel) { Query *parse = root->parse; @@ -2128,18 +2090,18 @@ perform_pullup_replace_vars(PlannerInfo *root, * If we are considering an appendrel child subquery (that is, a UNION ALL * member query that we're pulling up), then the only part of the upper * query that could reference the child yet is the translated_vars list of - * the associated AppendRelInfo. Furthermore, we do not need to insert - * PHVs in the AppendRelInfo --- there isn't any outer join between. + * the associated AppendRelInfo. Furthermore, we do not want to force use + * of PHVs in the AppendRelInfo --- there isn't any outer join between. */ if (containing_appendrel) { - bool save_need_phvs = rvcontext->need_phvs; + bool save_wrap_non_vars = rvcontext->wrap_non_vars; - rvcontext->need_phvs = false; + rvcontext->wrap_non_vars = false; containing_appendrel->translated_vars = (List *) pullup_replace_vars((Node *) containing_appendrel->translated_vars, rvcontext); - rvcontext->need_phvs = save_need_phvs; + rvcontext->wrap_non_vars = save_wrap_non_vars; return; } @@ -2190,8 +2152,7 @@ perform_pullup_replace_vars(PlannerInfo *root, pullup_replace_vars((Node *) action->targetList, rvcontext); } } - replace_vars_in_jointree((Node *) parse->jointree, rvcontext, - lowest_nulling_outer_join); + replace_vars_in_jointree((Node *) parse->jointree, rvcontext); Assert(parse->setOperations == NULL); parse->havingQual = pullup_replace_vars(parse->havingQual, rvcontext); @@ -2208,12 +2169,6 @@ perform_pullup_replace_vars(PlannerInfo *root, /* * Replace references in the joinaliasvars lists of join RTEs. - * - * You might think that we could avoid using PHVs for alias vars of joins - * below lowest_nulling_outer_join, but that doesn't work because the - * alias vars could be referenced above that join; we need the PHVs to be - * present in such references after the alias vars get flattened. (It - * might be worth trying to be smarter here, someday.) */ foreach(lc, parse->rtable) { @@ -2230,14 +2185,10 @@ perform_pullup_replace_vars(PlannerInfo *root, * Helper routine for perform_pullup_replace_vars: do pullup_replace_vars on * every expression in the jointree, without changing the jointree structure * itself. Ugly, but there's no other way... - * - * If we are at or below lowest_nulling_outer_join, we can suppress use of - * PlaceHolderVars wrapped around the replacement expressions. */ static void replace_vars_in_jointree(Node *jtnode, - pullup_replace_vars_context *context, - JoinExpr *lowest_nulling_outer_join) + pullup_replace_vars_context *context) { if (jtnode == NULL) return; @@ -2247,10 +2198,8 @@ replace_vars_in_jointree(Node *jtnode, * If the RangeTblRef refers to a LATERAL subquery (that isn't the * same subquery we're pulling up), it might contain references to the * target subquery, which we must replace. We drive this from the - * jointree scan, rather than a scan of the rtable, for a couple of - * reasons: we can avoid processing no-longer-referenced RTEs, and we - * can use the appropriate setting of need_phvs depending on whether - * the RTE is above possibly-nulling outer joins or not. + * jointree scan, rather than a scan of the rtable, so that we can + * avoid processing no-longer-referenced RTEs. */ int varno = ((RangeTblRef *) jtnode)->rtindex; @@ -2307,42 +2256,30 @@ replace_vars_in_jointree(Node *jtnode, ListCell *l; foreach(l, f->fromlist) - replace_vars_in_jointree(lfirst(l), context, - lowest_nulling_outer_join); + replace_vars_in_jointree(lfirst(l), context); f->quals = pullup_replace_vars(f->quals, context); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - bool save_need_phvs = context->need_phvs; + bool save_wrap_non_vars = context->wrap_non_vars; - if (j == lowest_nulling_outer_join) - { - /* no more PHVs in or below this join */ - context->need_phvs = false; - lowest_nulling_outer_join = NULL; - } - replace_vars_in_jointree(j->larg, context, lowest_nulling_outer_join); - replace_vars_in_jointree(j->rarg, context, lowest_nulling_outer_join); + replace_vars_in_jointree(j->larg, context); + replace_vars_in_jointree(j->rarg, context); /* - * Use PHVs within the join quals of a full join, even when it's the - * lowest nulling outer join. Otherwise, we cannot identify which - * side of the join a pulled-up var-free expression came from, which - * can lead to failure to make a plan at all because none of the quals - * appear to be mergeable or hashable conditions. For this purpose we - * don't care about the state of wrap_non_vars, so leave it alone. + * Use PHVs within the join quals of a full join. Otherwise, we + * cannot identify which side of the join a pulled-up var-free + * expression came from, which can lead to failure to make a plan at + * all because none of the quals appear to be mergeable or hashable + * conditions. */ if (j->jointype == JOIN_FULL) - context->need_phvs = true; + context->wrap_non_vars = true; j->quals = pullup_replace_vars(j->quals, context); - /* - * We don't bother to update the colvars list, since it won't be used - * again ... - */ - context->need_phvs = save_need_phvs; + context->wrap_non_vars = save_wrap_non_vars; } else elog(ERROR, "unrecognized node type: %d", @@ -2371,9 +2308,19 @@ pullup_replace_vars_callback(Var *var, { pullup_replace_vars_context *rcon = (pullup_replace_vars_context *) context->callback_arg; int varattno = var->varattno; + bool need_phv; Node *newnode; /* + * We need a PlaceHolderVar if the Var-to-be-replaced has nonempty + * varnullingrels (unless we find below that the replacement expression is + * a Var or PlaceHolderVar that we can just add the nullingrels to). We + * also need one if the caller has instructed us that all non-Var/PHV + * replacements need to be wrapped for identification purposes. + */ + need_phv = (var->varnullingrels != NULL) || rcon->wrap_non_vars; + + /* * If PlaceHolderVars are needed, we cache the modified expressions in * rcon->rv_cache[]. This is not in hopes of any material speed gain * within this function, but to avoid generating identical PHVs with @@ -2381,13 +2328,16 @@ pullup_replace_vars_callback(Var *var, * and possibly prevent optimizations that rely on recognizing different * references to the same subquery output as being equal(). So it's worth * a bit of extra effort to avoid it. + * + * The cached items have phlevelsup = 0 and phnullingrels = NULL; we'll + * copy them and adjust those values for this reference site below. */ - if (rcon->need_phvs && + if (need_phv && varattno >= InvalidAttrNumber && varattno <= list_length(rcon->targetlist) && rcon->rv_cache[varattno] != NULL) { - /* Just copy the entry and fall through to adjust its varlevelsup */ + /* Just copy the entry and fall through to adjust phlevelsup etc */ newnode = copyObject(rcon->rv_cache[varattno]); } else if (varattno == InvalidAttrNumber) @@ -2396,7 +2346,7 @@ pullup_replace_vars_callback(Var *var, RowExpr *rowexpr; List *colnames; List *fields; - bool save_need_phvs = rcon->need_phvs; + bool save_wrap_non_vars = rcon->wrap_non_vars; int save_sublevelsup = context->sublevels_up; /* @@ -2407,18 +2357,18 @@ pullup_replace_vars_callback(Var *var, * the RowExpr for use of the executor and ruleutils.c. * * In order to be able to cache the results, we always generate the - * expansion with varlevelsup = 0, and then adjust if needed. + * expansion with varlevelsup = 0, and then adjust below if needed. */ expandRTE(rcon->target_rte, var->varno, 0 /* not varlevelsup */ , var->location, (var->vartype != RECORDOID), &colnames, &fields); - /* Adjust the generated per-field Vars, but don't insert PHVs */ - rcon->need_phvs = false; + /* Expand the generated per-field Vars, but don't insert PHVs there */ + rcon->wrap_non_vars = false; context->sublevels_up = 0; /* to match the expandRTE output */ fields = (List *) replace_rte_variables_mutator((Node *) fields, context); - rcon->need_phvs = save_need_phvs; + rcon->wrap_non_vars = save_wrap_non_vars; context->sublevels_up = save_sublevelsup; rowexpr = makeNode(RowExpr); @@ -2436,14 +2386,13 @@ pullup_replace_vars_callback(Var *var, * expression to yield NULL, not ROW(NULL,NULL,...) when it is forced * to null by an outer join. */ - if (rcon->need_phvs) + if (need_phv) { - /* RowExpr is certainly not strict, so always need PHV */ newnode = (Node *) make_placeholder_expr(rcon->root, (Expr *) newnode, bms_make_singleton(rcon->varno)); - /* cache it with the PHV, and with varlevelsup still zero */ + /* cache it with the PHV, and with phlevelsup etc not set yet */ rcon->rv_cache[InvalidAttrNumber] = copyObject(newnode); } } @@ -2460,7 +2409,7 @@ pullup_replace_vars_callback(Var *var, newnode = (Node *) copyObject(tle->expr); /* Insert PlaceHolderVar if needed */ - if (rcon->need_phvs) + if (need_phv) { bool wrap; @@ -2486,69 +2435,61 @@ pullup_replace_vars_callback(Var *var, /* No need to wrap a PlaceHolderVar with another one, either */ wrap = false; } - else if (rcon->wrap_non_vars) - { - /* Wrap all non-Vars in a PlaceHolderVar */ - wrap = true; - } else { /* - * If it contains a Var of the subquery being pulled up, and - * does not contain any non-strict constructs, then it's - * certainly nullable so we don't need to insert a - * PlaceHolderVar. - * - * This analysis could be tighter: in particular, a non-strict - * construct hidden within a lower-level PlaceHolderVar is not - * reason to add another PHV. But for now it doesn't seem - * worth the code to be more exact. - * - * Note: in future maybe we should insert a PlaceHolderVar - * anyway, if the tlist item is expensive to evaluate? - * - * For a LATERAL subquery, we have to check the actual var - * membership of the node, but if it's non-lateral then any - * level-zero var must belong to the subquery. + * Must wrap, either because we need a place to insert + * varnullingrels or because caller told us to wrap + * everything. */ - if ((rcon->target_rte->lateral ? - bms_overlap(pull_varnos(rcon->root, (Node *) newnode), - rcon->relids) : - contain_vars_of_level((Node *) newnode, 0)) && - !contain_nonstrict_functions((Node *) newnode)) - { - /* No wrap needed */ - wrap = false; - } - else - { - /* Else wrap it in a PlaceHolderVar */ - wrap = true; - } + wrap = true; } if (wrap) + { newnode = (Node *) make_placeholder_expr(rcon->root, (Expr *) newnode, bms_make_singleton(rcon->varno)); - /* - * Cache it if possible (ie, if the attno is in range, which it - * probably always should be). We can cache the value even if we - * decided we didn't need a PHV, since this result will be - * suitable for any request that has need_phvs. - */ - if (varattno > InvalidAttrNumber && - varattno <= list_length(rcon->targetlist)) - rcon->rv_cache[varattno] = copyObject(newnode); + /* + * Cache it if possible (ie, if the attno is in range, which + * it probably always should be). + */ + if (varattno > InvalidAttrNumber && + varattno <= list_length(rcon->targetlist)) + rcon->rv_cache[varattno] = copyObject(newnode); + } } } - /* Must adjust varlevelsup if tlist item is from higher query */ + /* Must adjust varlevelsup if replaced Var is within a subquery */ if (var->varlevelsup > 0) IncrementVarSublevelsUp(newnode, var->varlevelsup, 0); + /* Propagate any varnullingrels into the replacement Var or PHV */ + if (var->varnullingrels != NULL) + { + if (IsA(newnode, Var)) + { + Var *newvar = (Var *) newnode; + + Assert(newvar->varlevelsup == var->varlevelsup); + newvar->varnullingrels = bms_add_members(newvar->varnullingrels, + var->varnullingrels); + } + else if (IsA(newnode, PlaceHolderVar)) + { + PlaceHolderVar *newphv = (PlaceHolderVar *) newnode; + + Assert(newphv->phlevelsup == var->varlevelsup); + newphv->phnullingrels = bms_add_members(newphv->phnullingrels, + var->varnullingrels); + } + else + elog(ERROR, "failed to wrap a non-Var"); + } + return newnode; } @@ -2707,7 +2648,9 @@ flatten_simple_union_all(PlannerInfo *root) void reduce_outer_joins(PlannerInfo *root) { - reduce_outer_joins_state *state; + reduce_outer_joins_pass1_state *state1; + reduce_outer_joins_pass2_state state2; + ListCell *lc; /* * To avoid doing strictness checks on more quals than necessary, we want @@ -2718,14 +2661,56 @@ reduce_outer_joins(PlannerInfo *root) * join(s) below each side of each join clause. The second pass examines * qual clauses and changes join types as it descends the tree. */ - state = reduce_outer_joins_pass1((Node *) root->parse->jointree); + state1 = reduce_outer_joins_pass1((Node *) root->parse->jointree); /* planner.c shouldn't have called me if no outer joins */ - if (state == NULL || !state->contains_outer) + if (state1 == NULL || !state1->contains_outer) elog(ERROR, "so where are the outer joins?"); + state2.inner_reduced = NULL; + state2.partial_reduced = NIL; + reduce_outer_joins_pass2((Node *) root->parse->jointree, - state, root, NULL, NIL); + state1, &state2, + root, NULL, NIL); + + /* + * If we successfully reduced the strength of any outer joins, we must + * remove references to those joins as nulling rels. This is handled as + * an additional pass, for simplicity and because we can handle all + * fully-reduced joins in a single pass over the parse tree. + */ + if (!bms_is_empty(state2.inner_reduced)) + { + root->parse = (Query *) + remove_nulling_relids((Node *) root->parse, + state2.inner_reduced, + NULL); + /* There could be references in the append_rel_list, too */ + root->append_rel_list = (List *) + remove_nulling_relids((Node *) root->append_rel_list, + state2.inner_reduced, + NULL); + } + + /* + * Partially-reduced full joins have to be done one at a time, since + * they'll each need a different setting of except_relids. + */ + foreach(lc, state2.partial_reduced) + { + reduce_outer_joins_partial_state *statep = lfirst(lc); + Relids full_join_relids = bms_make_singleton(statep->full_join_rti); + + root->parse = (Query *) + remove_nulling_relids((Node *) root->parse, + full_join_relids, + statep->unreduced_side); + root->append_rel_list = (List *) + remove_nulling_relids((Node *) root->append_rel_list, + full_join_relids, + statep->unreduced_side); + } } /* @@ -2733,13 +2718,13 @@ reduce_outer_joins(PlannerInfo *root) * * Returns a state node describing the given jointree node. */ -static reduce_outer_joins_state * +static reduce_outer_joins_pass1_state * reduce_outer_joins_pass1(Node *jtnode) { - reduce_outer_joins_state *result; + reduce_outer_joins_pass1_state *result; - result = (reduce_outer_joins_state *) - palloc(sizeof(reduce_outer_joins_state)); + result = (reduce_outer_joins_pass1_state *) + palloc(sizeof(reduce_outer_joins_pass1_state)); result->relids = NULL; result->contains_outer = false; result->sub_states = NIL; @@ -2759,7 +2744,7 @@ reduce_outer_joins_pass1(Node *jtnode) foreach(l, f->fromlist) { - reduce_outer_joins_state *sub_state; + reduce_outer_joins_pass1_state *sub_state; sub_state = reduce_outer_joins_pass1(lfirst(l)); result->relids = bms_add_members(result->relids, @@ -2771,7 +2756,7 @@ reduce_outer_joins_pass1(Node *jtnode) else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - reduce_outer_joins_state *sub_state; + reduce_outer_joins_pass1_state *sub_state; /* join's own RT index is not wanted in result->relids */ if (IS_OUTER_JOIN(j->jointype)) @@ -2799,14 +2784,22 @@ reduce_outer_joins_pass1(Node *jtnode) * reduce_outer_joins_pass2 - phase 2 processing * * jtnode: current jointree node - * state: state data collected by phase 1 for this node + * state1: state data collected by phase 1 for this node + * state2: where to accumulate info about successfully-reduced joins * root: toplevel planner state * nonnullable_rels: set of base relids forced non-null by upper quals * forced_null_vars: multibitmapset of Vars forced null by upper quals + * + * Returns info in state2 about outer joins that were successfully simplified. + * Joins that were fully reduced to inner joins are all added to + * state2->inner_reduced. If a full join is reduced to a left join, + * it needs its own entry in state2->partial_reduced, since that will + * require custom processing to remove only the correct nullingrel markers. */ static void reduce_outer_joins_pass2(Node *jtnode, - reduce_outer_joins_state *state, + reduce_outer_joins_pass1_state *state1, + reduce_outer_joins_pass2_state *state2, PlannerInfo *root, Relids nonnullable_rels, List *forced_null_vars) @@ -2835,13 +2828,14 @@ reduce_outer_joins_pass2(Node *jtnode, pass_forced_null_vars = mbms_add_members(pass_forced_null_vars, forced_null_vars); /* And recurse --- but only into interesting subtrees */ - Assert(list_length(f->fromlist) == list_length(state->sub_states)); - forboth(l, f->fromlist, s, state->sub_states) + Assert(list_length(f->fromlist) == list_length(state1->sub_states)); + forboth(l, f->fromlist, s, state1->sub_states) { - reduce_outer_joins_state *sub_state = lfirst(s); + reduce_outer_joins_pass1_state *sub_state = lfirst(s); if (sub_state->contains_outer) - reduce_outer_joins_pass2(lfirst(l), sub_state, root, + reduce_outer_joins_pass2(lfirst(l), sub_state, + state2, root, pass_nonnullable_rels, pass_forced_null_vars); } @@ -2853,8 +2847,8 @@ reduce_outer_joins_pass2(Node *jtnode, JoinExpr *j = (JoinExpr *) jtnode; int rtindex = j->rtindex; JoinType jointype = j->jointype; - reduce_outer_joins_state *left_state = linitial(state->sub_states); - reduce_outer_joins_state *right_state = lsecond(state->sub_states); + reduce_outer_joins_pass1_state *left_state = linitial(state1->sub_states); + reduce_outer_joins_pass1_state *right_state = lsecond(state1->sub_states); /* Can we simplify this join? */ switch (jointype) @@ -2875,12 +2869,22 @@ reduce_outer_joins_pass2(Node *jtnode, if (bms_overlap(nonnullable_rels, right_state->relids)) jointype = JOIN_INNER; else + { jointype = JOIN_LEFT; + /* Also report partial reduction in state2 */ + report_reduced_full_join(state2, rtindex, + right_state->relids); + } } else { if (bms_overlap(nonnullable_rels, right_state->relids)) + { jointype = JOIN_RIGHT; + /* Also report partial reduction in state2 */ + report_reduced_full_join(state2, rtindex, + left_state->relids); + } } break; case JOIN_SEMI: @@ -2913,8 +2917,8 @@ reduce_outer_joins_pass2(Node *jtnode, j->larg = j->rarg; j->rarg = tmparg; jointype = JOIN_LEFT; - right_state = linitial(state->sub_states); - left_state = lsecond(state->sub_states); + right_state = linitial(state1->sub_states); + left_state = lsecond(state1->sub_states); } /* @@ -2945,7 +2949,10 @@ reduce_outer_joins_pass2(Node *jtnode, jointype = JOIN_ANTI; } - /* Apply the jointype change, if any, to both jointree node and RTE */ + /* + * Apply the jointype change, if any, to both jointree node and RTE. + * Also, if we changed an RTE to INNER, add its RTI to inner_reduced. + */ if (rtindex && jointype != j->jointype) { RangeTblEntry *rte = rt_fetch(rtindex, root->parse->rtable); @@ -2953,6 +2960,9 @@ reduce_outer_joins_pass2(Node *jtnode, Assert(rte->rtekind == RTE_JOIN); Assert(rte->jointype == j->jointype); rte->jointype = jointype; + if (jointype == JOIN_INNER) + state2->inner_reduced = bms_add_member(state2->inner_reduced, + rtindex); } j->jointype = jointype; @@ -3025,7 +3035,8 @@ reduce_outer_joins_pass2(Node *jtnode, pass_nonnullable_rels = NULL; pass_forced_null_vars = NIL; } - reduce_outer_joins_pass2(j->larg, left_state, root, + reduce_outer_joins_pass2(j->larg, left_state, + state2, root, pass_nonnullable_rels, pass_forced_null_vars); } @@ -3044,7 +3055,8 @@ reduce_outer_joins_pass2(Node *jtnode, pass_nonnullable_rels = NULL; pass_forced_null_vars = NIL; } - reduce_outer_joins_pass2(j->rarg, right_state, root, + reduce_outer_joins_pass2(j->rarg, right_state, + state2, root, pass_nonnullable_rels, pass_forced_null_vars); } @@ -3056,6 +3068,19 @@ reduce_outer_joins_pass2(Node *jtnode, (int) nodeTag(jtnode)); } +/* Helper for reduce_outer_joins_pass2 */ +static void +report_reduced_full_join(reduce_outer_joins_pass2_state *state2, + int rtindex, Relids relids) +{ + reduce_outer_joins_partial_state *statep; + + statep = palloc(sizeof(reduce_outer_joins_partial_state)); + statep->full_join_rti = rtindex; + statep->unreduced_side = relids; + state2->partial_reduced = lappend(state2->partial_reduced, statep); +} + /* * remove_useless_result_rtes @@ -3097,17 +3122,42 @@ reduce_outer_joins_pass2(Node *jtnode, void remove_useless_result_rtes(PlannerInfo *root) { + Relids dropped_outer_joins = NULL; ListCell *cell; /* Top level of jointree must always be a FromExpr */ Assert(IsA(root->parse->jointree, FromExpr)); /* Recurse ... */ root->parse->jointree = (FromExpr *) - remove_useless_results_recurse(root, (Node *) root->parse->jointree); + remove_useless_results_recurse(root, + (Node *) root->parse->jointree, + &dropped_outer_joins); /* We should still have a FromExpr */ Assert(IsA(root->parse->jointree, FromExpr)); /* + * If we removed any outer-join nodes from the jointree, run around and + * remove references to those joins as nulling rels. (There could be such + * references in PHVs that we pulled up out of the original subquery that + * the RESULT rel replaced. This is kosher on the grounds that we now + * know that such an outer join wouldn't really have nulled anything.) We + * don't do this during the main recursion, for simplicity and because we + * can handle all such joins in a single pass over the parse tree. + */ + if (!bms_is_empty(dropped_outer_joins)) + { + root->parse = (Query *) + remove_nulling_relids((Node *) root->parse, + dropped_outer_joins, + NULL); + /* There could be references in the append_rel_list, too */ + root->append_rel_list = (List *) + remove_nulling_relids((Node *) root->append_rel_list, + dropped_outer_joins, + NULL); + } + + /* * Remove any PlanRowMark referencing an RTE_RESULT RTE. We obviously * must do that for any RTE_RESULT that we just removed. But one for a * RTE that we did not remove can be dropped anyway: since the RTE has @@ -3132,9 +3182,12 @@ remove_useless_result_rtes(PlannerInfo *root) * Recursive guts of remove_useless_result_rtes. * * This recursively processes the jointree and returns a modified jointree. + * In addition, the RT indexes of any removed outer-join nodes are added to + * *dropped_outer_joins. */ static Node * -remove_useless_results_recurse(PlannerInfo *root, Node *jtnode) +remove_useless_results_recurse(PlannerInfo *root, Node *jtnode, + Relids *dropped_outer_joins) { Assert(jtnode != NULL); if (IsA(jtnode, RangeTblRef)) @@ -3162,7 +3215,8 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode) int varno; /* Recursively transform child ... */ - child = remove_useless_results_recurse(root, child); + child = remove_useless_results_recurse(root, child, + dropped_outer_joins); /* ... and stick it back into the tree */ lfirst(cell) = child; @@ -3211,8 +3265,10 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode) int varno; /* First, recurse */ - j->larg = remove_useless_results_recurse(root, j->larg); - j->rarg = remove_useless_results_recurse(root, j->rarg); + j->larg = remove_useless_results_recurse(root, j->larg, + dropped_outer_joins); + j->rarg = remove_useless_results_recurse(root, j->rarg, + dropped_outer_joins); /* Apply join-type-specific optimization rules */ switch (j->jointype) @@ -3280,6 +3336,8 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode) !find_dependent_phvs(root, varno))) { remove_result_refs(root, varno, j->larg); + *dropped_outer_joins = bms_add_member(*dropped_outer_joins, + j->rtindex); jtnode = j->larg; } break; @@ -3299,9 +3357,13 @@ remove_useless_results_recurse(PlannerInfo *root, Node *jtnode) * it'd be OK to just remove the PHV wrapping. We don't have * infrastructure for that, but remove_result_refs() will * relabel them as to be evaluated at the LHS, which is fine. + * + * Also, we don't need to worry about removing traces of the + * join's rtindex, since it hasn't got one. */ if ((varno = get_result_relid(root, j->rarg)) != 0) { + Assert(j->rtindex == 0); remove_result_refs(root, varno, j->larg); if (j->quals) jtnode = (Node *) @@ -3371,7 +3433,7 @@ remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc) { Relids subrelids; - subrelids = get_relids_in_jointree(newjtloc, false); + subrelids = get_relids_in_jointree(newjtloc, true, false); Assert(!bms_is_empty(subrelids)); substitute_phv_relids((Node *) root->parse, varno, subrelids); fix_append_rel_relids(root, varno, subrelids); @@ -3428,9 +3490,8 @@ find_dependent_phvs_walker(Node *node, context->sublevels_up--; return result; } - /* Shouldn't need to handle planner auxiliary nodes here */ + /* Shouldn't need to handle most planner auxiliary nodes here */ Assert(!IsA(node, SpecialJoinInfo)); - Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); @@ -3450,10 +3511,17 @@ find_dependent_phvs(PlannerInfo *root, int varno) context.relids = bms_make_singleton(varno); context.sublevels_up = 0; - return query_tree_walker(root->parse, - find_dependent_phvs_walker, - (void *) &context, - 0); + if (query_tree_walker(root->parse, + find_dependent_phvs_walker, + (void *) &context, + 0)) + return true; + /* The append_rel_list could be populated already, so check it too */ + if (expression_tree_walker((Node *) root->append_rel_list, + find_dependent_phvs_walker, + (void *) &context)) + return true; + return false; } static bool @@ -3483,7 +3551,7 @@ find_dependent_phvs_in_jointree(PlannerInfo *root, Node *node, int varno) * are not marked LATERAL, though, since they couldn't possibly contain * any cross-references to other RTEs. */ - subrelids = get_relids_in_jointree(node, false); + subrelids = get_relids_in_jointree(node, false, false); relid = -1; while ((relid = bms_next_member(subrelids, relid)) >= 0) { @@ -3628,11 +3696,17 @@ fix_append_rel_relids(PlannerInfo *root, int varno, Relids subrelids) /* * get_relids_in_jointree: get set of RT indexes present in a jointree * - * If include_joins is true, join RT indexes are included; if false, - * only base rels are included. + * Base-relation relids are always included in the result. + * If include_outer_joins is true, outer-join RT indexes are included. + * If include_inner_joins is true, inner-join RT indexes are included. + * + * Note that for most purposes in the planner, outer joins are included + * in standard relid sets. Setting include_inner_joins true is only + * appropriate for special purposes during subquery flattening. */ Relids -get_relids_in_jointree(Node *jtnode, bool include_joins) +get_relids_in_jointree(Node *jtnode, bool include_outer_joins, + bool include_inner_joins) { Relids result = NULL; @@ -3653,18 +3727,34 @@ get_relids_in_jointree(Node *jtnode, bool include_joins) { result = bms_join(result, get_relids_in_jointree(lfirst(l), - include_joins)); + include_outer_joins, + include_inner_joins)); } } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - result = get_relids_in_jointree(j->larg, include_joins); + result = get_relids_in_jointree(j->larg, + include_outer_joins, + include_inner_joins); result = bms_join(result, - get_relids_in_jointree(j->rarg, include_joins)); - if (include_joins && j->rtindex) - result = bms_add_member(result, j->rtindex); + get_relids_in_jointree(j->rarg, + include_outer_joins, + include_inner_joins)); + if (j->rtindex) + { + if (j->jointype == JOIN_INNER) + { + if (include_inner_joins) + result = bms_add_member(result, j->rtindex); + } + else + { + if (include_outer_joins) + result = bms_add_member(result, j->rtindex); + } + } } else elog(ERROR, "unrecognized node type: %d", @@ -3673,7 +3763,7 @@ get_relids_in_jointree(Node *jtnode, bool include_joins) } /* - * get_relids_for_join: get set of base RT indexes making up a join + * get_relids_for_join: get set of base+OJ RT indexes making up a join */ Relids get_relids_for_join(Query *query, int joinrelid) @@ -3684,7 +3774,7 @@ get_relids_for_join(Query *query, int joinrelid) joinrelid); if (!jtnode) elog(ERROR, "could not find join node %d", joinrelid); - return get_relids_in_jointree(jtnode, false); + return get_relids_in_jointree(jtnode, true, false); } /* |