foreach(rt, rtable)
{
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+ RangeTblEntry *rte = lfirst_node(RangeTblEntry, rt);
- /*
- * Walkers might need to examine the RTE node itself either before or
- * after visiting its contents (or, conceivably, both). Note that if
- * you specify neither flag, the walker won't visit the RTE at all.
- */
- if (flags & QTW_EXAMINE_RTES_BEFORE)
- if (walker(rte, context))
- return true;
+ if (range_table_entry_walker(rte, walker, context, flags))
+ return true;
+ }
+ return false;
+}
- switch (rte->rtekind)
- {
- case RTE_RELATION:
- if (walker(rte->tablesample, context))
- return true;
- break;
- case RTE_SUBQUERY:
- if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
- if (walker(rte->subquery, context))
- return true;
- break;
- case RTE_JOIN:
- if (!(flags & QTW_IGNORE_JOINALIASES))
- if (walker(rte->joinaliasvars, context))
- return true;
- break;
- case RTE_FUNCTION:
- if (walker(rte->functions, context))
- return true;
- break;
- case RTE_TABLEFUNC:
- if (walker(rte->tablefunc, context))
+/*
+ * Some callers even want to scan the expressions in individual RTEs.
+ */
+bool
+range_table_entry_walker(RangeTblEntry *rte,
+ bool (*walker) (),
+ void *context,
+ int flags)
+{
+ /*
+ * Walkers might need to examine the RTE node itself either before or
+ * after visiting its contents (or, conceivably, both). Note that if you
+ * specify neither flag, the walker won't be called on the RTE at all.
+ */
+ if (flags & QTW_EXAMINE_RTES_BEFORE)
+ if (walker(rte, context))
+ return true;
+
+ switch (rte->rtekind)
+ {
+ case RTE_RELATION:
+ if (walker(rte->tablesample, context))
+ return true;
+ break;
+ case RTE_SUBQUERY:
+ if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
+ if (walker(rte->subquery, context))
return true;
- break;
- case RTE_VALUES:
- if (walker(rte->values_lists, context))
+ break;
+ case RTE_JOIN:
+ if (!(flags & QTW_IGNORE_JOINALIASES))
+ if (walker(rte->joinaliasvars, context))
return true;
- break;
- case RTE_CTE:
- case RTE_NAMEDTUPLESTORE:
- case RTE_RESULT:
- /* nothing to do */
- break;
- }
+ break;
+ case RTE_FUNCTION:
+ if (walker(rte->functions, context))
+ return true;
+ break;
+ case RTE_TABLEFUNC:
+ if (walker(rte->tablefunc, context))
+ return true;
+ break;
+ case RTE_VALUES:
+ if (walker(rte->values_lists, context))
+ return true;
+ break;
+ case RTE_CTE:
+ case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
+ /* nothing to do */
+ break;
+ }
- if (walker(rte->securityQuals, context))
+ if (walker(rte->securityQuals, context))
+ return true;
+
+ if (flags & QTW_EXAMINE_RTES_AFTER)
+ if (walker(rte, context))
return true;
- if (flags & QTW_EXAMINE_RTES_AFTER)
- if (walker(rte, context))
- return true;
- }
return false;
}
static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode);
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(Node *node, int varno);
+static bool find_dependent_phvs(PlannerInfo *root, int varno);
+static bool find_dependent_phvs_in_jointree(PlannerInfo *root,
+ Node *node, int varno);
static void substitute_phv_relids(Node *node,
int varno, Relids subrelids);
static void fix_append_rel_relids(List *append_rel_list, int varno,
* and not remove the RTE_RESULT: there is noplace else to evaluate the
* PlaceHolderVar. (That is, in such cases the RTE_RESULT *does* have output
* columns.) But if the RTE_RESULT is an immediate child of an inner join,
- * we can change the PlaceHolderVar's phrels so as to evaluate it at the
- * inner join instead. This is OK because we really only care that PHVs are
- * evaluated above or below the correct outer joins.
+ * we can usually change the PlaceHolderVar's phrels so as to evaluate it at
+ * the inner join instead. This is OK because we really only care that PHVs
+ * are evaluated above or below the correct outer joins. We can't, however,
+ * postpone the evaluation of a PHV to above where it is used; so there are
+ * some checks below on whether output PHVs are laterally referenced in the
+ * other join input rel(s).
*
* We used to try to do this work as part of pull_up_subqueries() where the
* potentially-optimizable cases get introduced; but it's way simpler, and
/*
* We can drop RTE_RESULT rels from the fromlist so long as at least
* one child remains, since joining to a one-row table changes
- * nothing. The easiest way to mechanize this rule is to modify the
- * list in-place, using list_delete_cell.
+ * nothing. (But we can't drop a RTE_RESULT that computes PHV(s) that
+ * are needed by some sibling. The cleanup transformation below would
+ * reassign the PHVs to be computed at the join, which is too late for
+ * the sibling's use.) The easiest way to mechanize this rule is to
+ * modify the list in-place, using list_delete_cell.
*/
prev = NULL;
for (cell = list_head(f->fromlist); cell; cell = next)
next = lnext(cell);
/*
- * If it's an RTE_RESULT with at least one sibling, we can drop
- * it. We don't yet know what the inner join's final relid set
- * will be, so postpone cleanup of PHVs etc till after this loop.
+ * If it's an RTE_RESULT with at least one sibling, and no sibling
+ * references dependent PHVs, we can drop it. We don't yet know
+ * what the inner join's final relid set will be, so postpone
+ * cleanup of PHVs etc till after this loop.
*/
if (list_length(f->fromlist) > 1 &&
- (varno = get_result_relid(root, child)) != 0)
+ (varno = get_result_relid(root, child)) != 0 &&
+ !find_dependent_phvs_in_jointree(root, (Node *) f, varno))
{
f->fromlist = list_delete_cell(f->fromlist, cell, prev);
result_relids = bms_add_member(result_relids, varno);
* the join with a FromExpr with just the other side; and if
* the qual is empty (JOIN ON TRUE) then we can omit the
* FromExpr as well.
+ *
+ * Just as in the FromExpr case, we can't simplify if the
+ * other input rel references any PHVs that are marked as to
+ * be evaluated at the RTE_RESULT rel, because we can't
+ * postpone their evaluation in that case. But we only have
+ * to check this in cases where it's syntactically legal for
+ * the other input to have a LATERAL reference to the
+ * RTE_RESULT rel. Only RHSes of inner and left joins are
+ * allowed to have such refs.
*/
- if ((varno = get_result_relid(root, j->larg)) != 0)
+ if ((varno = get_result_relid(root, j->larg)) != 0 &&
+ !find_dependent_phvs_in_jointree(root, j->rarg, varno))
{
remove_result_refs(root, varno, j->rarg);
if (j->quals)
* strength-reduced to a plain inner join, since each LHS row
* necessarily has exactly one join partner. So we can always
* discard the RHS, much as in the JOIN_INNER case above.
+ * (Again, the LHS could not contain a lateral reference to
+ * the RHS.)
*
* Otherwise, it's still true that each LHS row should be
* returned exactly once, and since the RHS returns no columns
*/
if ((varno = get_result_relid(root, j->rarg)) != 0 &&
(j->quals == NULL ||
- !find_dependent_phvs((Node *) root->parse, varno)))
+ !find_dependent_phvs(root, varno)))
{
remove_result_refs(root, varno, j->larg);
jtnode = j->larg;
/* Mirror-image of the JOIN_LEFT case */
if ((varno = get_result_relid(root, j->larg)) != 0 &&
(j->quals == NULL ||
- !find_dependent_phvs((Node *) root->parse, varno)))
+ !find_dependent_phvs(root, varno)))
{
remove_result_refs(root, varno, j->rarg);
jtnode = j->rarg;
*/
if ((varno = get_result_relid(root, j->rarg)) != 0)
{
- Assert(!find_dependent_phvs((Node *) root->parse, varno));
+ Assert(!find_dependent_phvs(root, varno));
remove_result_refs(root, varno, j->larg);
if (j->quals)
jtnode = (Node *)
/*
* find_dependent_phvs - are there any PlaceHolderVars whose relids are
* exactly the given varno?
+ *
+ * find_dependent_phvs should be used when we want to see if there are
+ * any such PHVs anywhere in the Query. Another use-case is to see if
+ * a subtree of the join tree contains such PHVs; but for that, we have
+ * to look not only at the join tree nodes themselves but at the
+ * referenced RTEs. For that, use find_dependent_phvs_in_jointree.
*/
typedef struct
}
static bool
-find_dependent_phvs(Node *node, int varno)
+find_dependent_phvs(PlannerInfo *root, int varno)
+{
+ find_dependent_phvs_context context;
+
+ /* If there are no PHVs anywhere, we needn't work hard */
+ if (root->glob->lastPHId == 0)
+ return false;
+
+ context.relids = bms_make_singleton(varno);
+ context.sublevels_up = 0;
+
+ return query_tree_walker(root->parse,
+ find_dependent_phvs_walker,
+ (void *) &context,
+ 0);
+}
+
+static bool
+find_dependent_phvs_in_jointree(PlannerInfo *root, Node *node, int varno)
{
find_dependent_phvs_context context;
+ Relids subrelids;
+ int relid;
+
+ /* If there are no PHVs anywhere, we needn't work hard */
+ if (root->glob->lastPHId == 0)
+ return false;
context.relids = bms_make_singleton(varno);
context.sublevels_up = 0;
/*
- * Must be prepared to start with a Query or a bare expression tree.
+ * See if the jointree fragment itself contains references (in join quals)
+ */
+ if (find_dependent_phvs_walker(node, &context))
+ return true;
+
+ /*
+ * Otherwise, identify the set of referenced RTEs (we can ignore joins,
+ * since they should be flattened already, so their join alias lists no
+ * longer matter), and tediously check each RTE. We can ignore RTEs that
+ * are not marked LATERAL, though, since they couldn't possibly contain
+ * any cross-references to other RTEs.
*/
- return query_or_expression_tree_walker(node,
- find_dependent_phvs_walker,
- (void *) &context,
- 0);
+ subrelids = get_relids_in_jointree(node, false);
+ relid = -1;
+ while ((relid = bms_next_member(subrelids, relid)) >= 0)
+ {
+ RangeTblEntry *rte = rt_fetch(relid, root->parse->rtable);
+
+ if (rte->lateral &&
+ range_table_entry_walker(rte,
+ find_dependent_phvs_walker,
+ (void *) &context,
+ 0))
+ return true;
+ }
+
+ return false;
}
/*