aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/prep/prepjointree.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/prep/prepjointree.c')
-rw-r--r--src/backend/optimizer/prep/prepjointree.c217
1 files changed, 160 insertions, 57 deletions
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 07b35f98bd1..f5deed4bc82 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -60,9 +60,14 @@ static Node *pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
Node **jtlink1, Relids available_rels1,
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);
@@ -71,14 +76,15 @@ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root,
int childRToffset);
static void make_setop_translation_list(Query *query, Index newvarno,
List **translated_vars);
-static bool is_simple_subquery(Query *subquery);
+static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte,
+ JoinExpr *lowest_outer_join);
static bool is_simple_union_all(Query *subquery);
static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery,
List *colTypes);
static bool is_safe_append_member(Query *subquery);
static void replace_vars_in_jointree(Node *jtnode,
pullup_replace_vars_context *context,
- JoinExpr *lowest_outer_join);
+ JoinExpr *lowest_nulling_outer_join);
static Node *pullup_replace_vars(Node *expr,
pullup_replace_vars_context *context);
static Node *pullup_replace_vars_callback(Var *var,
@@ -585,10 +591,28 @@ inline_set_returning_functions(PlannerInfo *root)
* Also, subqueries that are simple UNION ALL structures can be
* converted into "append relations".
*
+ * This recursively processes the jointree and returns a modified jointree.
+ */
+Node *
+pull_up_subqueries(PlannerInfo *root, Node *jtnode)
+{
+ /* Start off with no containing join nor appendrel */
+ return pull_up_subqueries_recurse(root, jtnode, NULL, NULL, NULL);
+}
+
+/*
+ * pull_up_subqueries_recurse
+ * Recursive guts of pull_up_subqueries.
+ *
+ * If this jointree node is within either side of an outer join, then
+ * 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_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.
+ * 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.
@@ -603,15 +627,17 @@ inline_set_returning_functions(PlannerInfo *root)
* subquery RangeTblRef entries will be replaced. Also, we can't turn
* pullup_replace_vars loose on the whole jointree, because it'll 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 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.
+ * 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.
*/
-Node *
-pull_up_subqueries(PlannerInfo *root, Node *jtnode,
- JoinExpr *lowest_outer_join,
- AppendRelInfo *containing_appendrel)
+static Node *
+pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
+ JoinExpr *lowest_outer_join,
+ JoinExpr *lowest_nulling_outer_join,
+ AppendRelInfo *containing_appendrel)
{
if (jtnode == NULL)
return NULL;
@@ -628,12 +654,12 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
* unless is_safe_append_member says so.
*/
if (rte->rtekind == RTE_SUBQUERY &&
- is_simple_subquery(rte->subquery) &&
- !rte->security_barrier &&
+ is_simple_subquery(rte->subquery, rte, lowest_outer_join) &&
(containing_appendrel == NULL ||
is_safe_append_member(rte->subquery)))
return pull_up_simple_subquery(root, jtnode, rte,
lowest_outer_join,
+ lowest_nulling_outer_join,
containing_appendrel);
/*
@@ -658,8 +684,10 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
Assert(containing_appendrel == NULL);
foreach(l, f->fromlist)
- lfirst(l) = pull_up_subqueries(root, lfirst(l),
- lowest_outer_join, NULL);
+ lfirst(l) = pull_up_subqueries_recurse(root, lfirst(l),
+ lowest_outer_join,
+ lowest_nulling_outer_join,
+ NULL);
}
else if (IsA(jtnode, JoinExpr))
{
@@ -670,30 +698,46 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
switch (j->jointype)
{
case JOIN_INNER:
- j->larg = pull_up_subqueries(root, j->larg,
- lowest_outer_join, NULL);
- j->rarg = pull_up_subqueries(root, j->rarg,
- lowest_outer_join, NULL);
+ 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:
case JOIN_SEMI:
case JOIN_ANTI:
- j->larg = pull_up_subqueries(root, j->larg,
- lowest_outer_join, NULL);
- j->rarg = pull_up_subqueries(root, j->rarg,
- j, NULL);
+ 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(root, j->larg,
- j, NULL);
- j->rarg = pull_up_subqueries(root, j->rarg,
- j, NULL);
+ 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(root, j->larg,
- j, NULL);
- j->rarg = pull_up_subqueries(root, j->rarg,
- lowest_outer_join, NULL);
+ 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:
elog(ERROR, "unrecognized join type: %d",
@@ -717,11 +761,12 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
* all.
*
* rte is the RangeTblEntry referenced by jtnode. Remaining parameters are
- * as for pull_up_subqueries.
+ * as for pull_up_subqueries_recurse.
*/
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;
@@ -788,7 +833,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* handling an appendrel member.
*/
subquery->jointree = (FromExpr *)
- pull_up_subqueries(subroot, (Node *) subquery->jointree, NULL, NULL);
+ pull_up_subqueries_recurse(subroot, (Node *) subquery->jointree,
+ NULL, NULL, NULL);
/*
* Now we must recheck whether the subquery is still simple enough to pull
@@ -796,10 +842,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
*
* We don't really need to recheck all the conditions involved, but it's
* easier just to keep this "if" looking the same as the one in
- * pull_up_subqueries.
+ * pull_up_subqueries_recurse.
*/
- if (is_simple_subquery(subquery) &&
- !rte->security_barrier &&
+ if (is_simple_subquery(subquery, rte, lowest_outer_join) &&
(containing_appendrel == NULL || is_safe_append_member(subquery)))
{
/* good to go */
@@ -846,7 +891,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
rvcontext.target_rte = rte;
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = varno;
- rvcontext.need_phvs = (lowest_outer_join != NULL ||
+ rvcontext.need_phvs = (lowest_nulling_outer_join != NULL ||
containing_appendrel != NULL);
rvcontext.wrap_non_vars = (containing_appendrel != NULL);
/* initialize cache array with indexes 0 .. length(tlist) */
@@ -867,7 +912,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
parse->returningList = (List *)
pullup_replace_vars((Node *) parse->returningList, &rvcontext);
replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
- lowest_outer_join);
+ lowest_nulling_outer_join);
Assert(parse->setOperations == NULL);
parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext);
@@ -894,10 +939,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* 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_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.)
+ * 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)
{
@@ -910,6 +955,38 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
}
/*
+ * If the subquery had a LATERAL marker, propagate that to any of its
+ * child RTEs that could possibly now contain lateral cross-references.
+ * The children might or might not contain any actual lateral
+ * cross-references, but we have to mark the pulled-up child RTEs so that
+ * later planner stages will check for such.
+ *
+ * NB: although the parser only sets the lateral flag in subquery and
+ * function RTEs, after this step it can also be set in VALUES RTEs.
+ */
+ if (rte->lateral)
+ {
+ foreach(lc, subquery->rtable)
+ {
+ RangeTblEntry *child_rte = (RangeTblEntry *) lfirst(lc);
+
+ switch (child_rte->rtekind)
+ {
+ case RTE_SUBQUERY:
+ case RTE_FUNCTION:
+ case RTE_VALUES:
+ child_rte->lateral = true;
+ break;
+ case RTE_RELATION:
+ case RTE_JOIN:
+ case RTE_CTE:
+ /* these can't contain any lateral references */
+ break;
+ }
+ }
+ }
+
+ /*
* Now append the adjusted rtable entries to upper query. (We hold off
* until after fixing the upper rtable entries; no point in running that
* code on the subquery ones too.)
@@ -1104,11 +1181,12 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
* must build the AppendRelInfo first, because this will modify it.)
* Note that we can pass NULL for containing-join info even if we're
* actually under an outer join, because the child's expressions
- * aren't going to propagate up above the join.
+ * aren't going to propagate up to the join.
*/
rtr = makeNode(RangeTblRef);
rtr->rtindex = childRTindex;
- (void) pull_up_subqueries(root, (Node *) rtr, NULL, appinfo);
+ (void) pull_up_subqueries_recurse(root, (Node *) rtr,
+ NULL, NULL, appinfo);
}
else if (IsA(setOp, SetOperationStmt))
{
@@ -1158,9 +1236,15 @@ make_setop_translation_list(Query *query, Index newvarno,
* is_simple_subquery
* Check a subquery in the range table to see if it's simple enough
* to pull up into the parent query.
+ *
+ * rte is the RTE_SUBQUERY RangeTblEntry that contained the subquery.
+ * (Note subquery is not necessarily equal to rte->subquery; it could be a
+ * processed copy of that.)
+ * lowest_outer_join is the lowest outer join above the subquery, or NULL.
*/
static bool
-is_simple_subquery(Query *subquery)
+is_simple_subquery(Query *subquery, RangeTblEntry *rte,
+ JoinExpr *lowest_outer_join)
{
/*
* Let's just make sure it's a valid subselect ...
@@ -1201,13 +1285,31 @@ is_simple_subquery(Query *subquery)
return false;
/*
- * Don't pull up a LATERAL subquery (hopefully, this is just a temporary
- * implementation restriction).
+ * Don't pull up if the RTE represents a security-barrier view; we couldn't
+ * prevent information leakage once the RTE's Vars are scattered about in
+ * the upper query.
*/
- if (contain_vars_of_level((Node *) subquery, 1))
+ if (rte->security_barrier)
return false;
/*
+ * If the subquery is LATERAL, and we're below any outer join, and the
+ * subquery contains lateral references to rels outside the outer join,
+ * don't pull up. Doing so would risk creating outer-join quals that
+ * contain references to rels outside the outer join, which is a semantic
+ * mess that doesn't seem worth addressing at the moment.
+ */
+ if (rte->lateral && lowest_outer_join != NULL)
+ {
+ Relids lvarnos = pull_varnos_of_level((Node *) subquery, 1);
+ Relids jvarnos = get_relids_in_jointree((Node *) lowest_outer_join,
+ true);
+
+ if (!bms_is_subset(lvarnos, jvarnos))
+ return false;
+ }
+
+ /*
* Don't pull up a subquery that has any set-returning functions in its
* targetlist. Otherwise we might well wind up inserting set-returning
* functions into places where they mustn't go, such as quals of higher
@@ -1358,13 +1460,13 @@ is_safe_append_member(Query *subquery)
* expression in the jointree, without changing the jointree structure itself.
* Ugly, but there's no other way...
*
- * If we are at or below lowest_outer_join, we can suppress use of
+ * 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_outer_join)
+ JoinExpr *lowest_nulling_outer_join)
{
if (jtnode == NULL)
return;
@@ -1378,7 +1480,8 @@ replace_vars_in_jointree(Node *jtnode,
ListCell *l;
foreach(l, f->fromlist)
- replace_vars_in_jointree(lfirst(l), context, lowest_outer_join);
+ replace_vars_in_jointree(lfirst(l), context,
+ lowest_nulling_outer_join);
f->quals = pullup_replace_vars(f->quals, context);
}
else if (IsA(jtnode, JoinExpr))
@@ -1386,14 +1489,14 @@ replace_vars_in_jointree(Node *jtnode,
JoinExpr *j = (JoinExpr *) jtnode;
bool save_need_phvs = context->need_phvs;
- if (j == lowest_outer_join)
+ if (j == lowest_nulling_outer_join)
{
/* no more PHVs in or below this join */
context->need_phvs = false;
- lowest_outer_join = NULL;
+ lowest_nulling_outer_join = NULL;
}
- replace_vars_in_jointree(j->larg, context, lowest_outer_join);
- replace_vars_in_jointree(j->rarg, context, lowest_outer_join);
+ replace_vars_in_jointree(j->larg, context, lowest_nulling_outer_join);
+ replace_vars_in_jointree(j->rarg, context, lowest_nulling_outer_join);
j->quals = pullup_replace_vars(j->quals, context);
/*