Commit 0816fad6 authored by Tom Lane's avatar Tom Lane

Undo 8.4-era lobotomization of subquery pullup rules.

After the planner was fixed to convert some IN/EXISTS subqueries into
semijoins or antijoins, we had to prevent it from doing that in some
cases where the plans risked getting much worse.  The reason the plans
got worse was that in the unoptimized implementation, subqueries could
reference parameters from the outer query at any join level, and so
full table scans could be avoided even if they were one or more levels
of join below where the semi/anti join would be.  Now that we have
sufficient mechanism in the planner to handle such cases properly,
it should no longer be necessary to play dumb here.

This reverts commits 07b9936a and
cd1f0d04.  The latter was a stopgap
fix that wasn't really sufficiently analyzed at the time.  Rather
than just restricting ourselves to cases where the new join can be
stacked on the right-hand input, we should also consider whether it
can be stacked on the left-hand input.
parent e2fa76d8
...@@ -58,7 +58,8 @@ typedef struct reduce_outer_joins_state ...@@ -58,7 +58,8 @@ typedef struct reduce_outer_joins_state
static Node *pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode, static Node *pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
Relids *relids); Relids *relids);
static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
Relids available_rels, Node **jtlink); Node **jtlink1, Relids available_rels1,
Node **jtlink2, Relids available_rels2);
static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte, RangeTblEntry *rte,
JoinExpr *lowest_outer_join, JoinExpr *lowest_outer_join,
...@@ -192,8 +193,9 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode, ...@@ -192,8 +193,9 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
/* Set up a link representing the rebuilt jointree */ /* Set up a link representing the rebuilt jointree */
jtlink = (Node *) newf; jtlink = (Node *) newf;
/* Now process qual --- all children are available for use */ /* Now process qual --- all children are available for use */
newf->quals = pull_up_sublinks_qual_recurse(root, f->quals, frelids, newf->quals = pull_up_sublinks_qual_recurse(root, f->quals,
&jtlink); &jtlink, frelids,
NULL, NULL);
/* /*
* Note that the result will be either newf, or a stack of JoinExprs * Note that the result will be either newf, or a stack of JoinExprs
...@@ -237,16 +239,6 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode, ...@@ -237,16 +239,6 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
* point of the available_rels machinations is to ensure that we only * point of the available_rels machinations is to ensure that we only
* pull up quals for which that's okay. * pull up quals for which that's okay.
* *
* XXX for the moment, we refrain from pulling up IN/EXISTS clauses
* appearing in LEFT or RIGHT join conditions. Although it is
* semantically valid to do so under the above conditions, we end up
* with a query in which the semijoin or antijoin must be evaluated
* below the outer join, which could perform far worse than leaving it
* as a sublink that is executed only for row pairs that meet the
* other join conditions. Fixing this seems to require considerable
* restructuring of the executor, but maybe someday it can happen.
* (See also the comparable case in pull_up_sublinks_qual_recurse.)
*
* We don't expect to see any pre-existing JOIN_SEMI or JOIN_ANTI * We don't expect to see any pre-existing JOIN_SEMI or JOIN_ANTI
* nodes here. * nodes here.
*/ */
...@@ -254,26 +246,25 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode, ...@@ -254,26 +246,25 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
{ {
case JOIN_INNER: case JOIN_INNER:
j->quals = pull_up_sublinks_qual_recurse(root, j->quals, j->quals = pull_up_sublinks_qual_recurse(root, j->quals,
&jtlink,
bms_union(leftrelids, bms_union(leftrelids,
rightrelids), rightrelids),
&jtlink); NULL, NULL);
break; break;
case JOIN_LEFT: case JOIN_LEFT:
#ifdef NOT_USED /* see XXX comment above */
j->quals = pull_up_sublinks_qual_recurse(root, j->quals, j->quals = pull_up_sublinks_qual_recurse(root, j->quals,
&j->rarg,
rightrelids, rightrelids,
&j->rarg); NULL, NULL);
#endif
break; break;
case JOIN_FULL: case JOIN_FULL:
/* can't do anything with full-join quals */ /* can't do anything with full-join quals */
break; break;
case JOIN_RIGHT: case JOIN_RIGHT:
#ifdef NOT_USED /* see XXX comment above */
j->quals = pull_up_sublinks_qual_recurse(root, j->quals, j->quals = pull_up_sublinks_qual_recurse(root, j->quals,
&j->larg,
leftrelids, leftrelids,
&j->larg); NULL, NULL);
#endif
break; break;
default: default:
elog(ERROR, "unrecognized join type: %d", elog(ERROR, "unrecognized join type: %d",
...@@ -303,14 +294,22 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode, ...@@ -303,14 +294,22 @@ pull_up_sublinks_jointree_recurse(PlannerInfo *root, Node *jtnode,
/* /*
* Recurse through top-level qual nodes for pull_up_sublinks() * Recurse through top-level qual nodes for pull_up_sublinks()
* *
* jtlink points to the link in the jointree where any new JoinExprs should be * jtlink1 points to the link in the jointree where any new JoinExprs should
* inserted. If we find multiple pull-up-able SubLinks, they'll get stacked * be inserted if they reference available_rels1 (i.e., available_rels1
* there in the order we encounter them. We rely on subsequent optimization * denotes the relations present underneath jtlink1). Optionally, jtlink2 can
* to rearrange the stack if appropriate. * point to a second link where new JoinExprs should be inserted if they
* reference available_rels2 (pass NULL for both those arguments if not used).
* Note that SubLinks referencing both sets of variables cannot be optimized.
* If we find multiple pull-up-able SubLinks, they'll get stacked onto jtlink1
* and/or jtlink2 in the order we encounter them. We rely on subsequent
* optimization to rearrange the stack if appropriate.
*
* Returns the replacement qual node, or NULL if the qual should be removed.
*/ */
static Node * static Node *
pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
Relids available_rels, Node **jtlink) Node **jtlink1, Relids available_rels1,
Node **jtlink2, Relids available_rels2)
{ {
if (node == NULL) if (node == NULL)
return NULL; return NULL;
...@@ -323,45 +322,105 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, ...@@ -323,45 +322,105 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
/* Is it a convertible ANY or EXISTS clause? */ /* Is it a convertible ANY or EXISTS clause? */
if (sublink->subLinkType == ANY_SUBLINK) if (sublink->subLinkType == ANY_SUBLINK)
{ {
j = convert_ANY_sublink_to_join(root, sublink, if ((j = convert_ANY_sublink_to_join(root, sublink,
available_rels); available_rels1)) != NULL)
if (j) {
/* Yes; insert the new join node into the join tree */
j->larg = *jtlink1;
*jtlink1 = (Node *) j;
/* Recursively process pulled-up jointree nodes */
j->rarg = pull_up_sublinks_jointree_recurse(root,
j->rarg,
&child_rels);
/*
* Now recursively process the pulled-up quals. Any inserted
* joins can get stacked onto either j->larg or j->rarg,
* depending on which rels they reference.
*/
j->quals = pull_up_sublinks_qual_recurse(root,
j->quals,
&j->larg,
available_rels1,
&j->rarg,
child_rels);
/* Return NULL representing constant TRUE */
return NULL;
}
if (available_rels2 != NULL &&
(j = convert_ANY_sublink_to_join(root, sublink,
available_rels2)) != NULL)
{ {
/* Yes; recursively process what we pulled up */ /* Yes; insert the new join node into the join tree */
j->larg = *jtlink2;
*jtlink2 = (Node *) j;
/* Recursively process pulled-up jointree nodes */
j->rarg = pull_up_sublinks_jointree_recurse(root, j->rarg = pull_up_sublinks_jointree_recurse(root,
j->rarg, j->rarg,
&child_rels); &child_rels);
/* Any inserted joins get stacked onto j->rarg */ /*
* Now recursively process the pulled-up quals. Any inserted
* joins can get stacked onto either j->larg or j->rarg,
* depending on which rels they reference.
*/
j->quals = pull_up_sublinks_qual_recurse(root, j->quals = pull_up_sublinks_qual_recurse(root,
j->quals, j->quals,
child_rels, &j->larg,
&j->rarg); available_rels2,
/* Now insert the new join node into the join tree */ &j->rarg,
j->larg = *jtlink; child_rels);
*jtlink = (Node *) j; /* Return NULL representing constant TRUE */
/* and return NULL representing constant TRUE */
return NULL; return NULL;
} }
} }
else if (sublink->subLinkType == EXISTS_SUBLINK) else if (sublink->subLinkType == EXISTS_SUBLINK)
{ {
j = convert_EXISTS_sublink_to_join(root, sublink, false, if ((j = convert_EXISTS_sublink_to_join(root, sublink, false,
available_rels); available_rels1)) != NULL)
if (j)
{ {
/* Yes; recursively process what we pulled up */ /* Yes; insert the new join node into the join tree */
j->larg = *jtlink1;
*jtlink1 = (Node *) j;
/* Recursively process pulled-up jointree nodes */
j->rarg = pull_up_sublinks_jointree_recurse(root, j->rarg = pull_up_sublinks_jointree_recurse(root,
j->rarg, j->rarg,
&child_rels); &child_rels);
/* Any inserted joins get stacked onto j->rarg */ /*
* Now recursively process the pulled-up quals. Any inserted
* joins can get stacked onto either j->larg or j->rarg,
* depending on which rels they reference.
*/
j->quals = pull_up_sublinks_qual_recurse(root, j->quals = pull_up_sublinks_qual_recurse(root,
j->quals, j->quals,
child_rels, &j->larg,
&j->rarg); available_rels1,
/* Now insert the new join node into the join tree */ &j->rarg,
j->larg = *jtlink; child_rels);
*jtlink = (Node *) j; /* Return NULL representing constant TRUE */
/* and return NULL representing constant TRUE */ return NULL;
}
if (available_rels2 != NULL &&
(j = convert_EXISTS_sublink_to_join(root, sublink, false,
available_rels2)) != NULL)
{
/* Yes; insert the new join node into the join tree */
j->larg = *jtlink2;
*jtlink2 = (Node *) j;
/* Recursively process pulled-up jointree nodes */
j->rarg = pull_up_sublinks_jointree_recurse(root,
j->rarg,
&child_rels);
/*
* Now recursively process the pulled-up quals. Any inserted
* joins can get stacked onto either j->larg or j->rarg,
* depending on which rels they reference.
*/
j->quals = pull_up_sublinks_qual_recurse(root,
j->quals,
&j->larg,
available_rels2,
&j->rarg,
child_rels);
/* Return NULL representing constant TRUE */
return NULL; return NULL;
} }
} }
...@@ -373,40 +432,59 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, ...@@ -373,40 +432,59 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
/* If the immediate argument of NOT is EXISTS, try to convert */ /* If the immediate argument of NOT is EXISTS, try to convert */
SubLink *sublink = (SubLink *) get_notclausearg((Expr *) node); SubLink *sublink = (SubLink *) get_notclausearg((Expr *) node);
JoinExpr *j; JoinExpr *j;
Relids child_rels;
if (sublink && IsA(sublink, SubLink)) if (sublink && IsA(sublink, SubLink))
{ {
if (sublink->subLinkType == EXISTS_SUBLINK) if (sublink->subLinkType == EXISTS_SUBLINK)
{ {
j = convert_EXISTS_sublink_to_join(root, sublink, true, if ((j = convert_EXISTS_sublink_to_join(root, sublink, true,
available_rels); available_rels1)) != NULL)
if (j)
{ {
/* Yes; insert the new join node into the join tree */
j->larg = *jtlink1;
*jtlink1 = (Node *) j;
/* Recursively process pulled-up jointree nodes */
j->rarg = pull_up_sublinks_jointree_recurse(root,
j->rarg,
&child_rels);
/* /*
* For the moment, refrain from recursing underneath NOT. * Now recursively process the pulled-up quals. Because
* As in pull_up_sublinks_jointree_recurse, recursing here * we are underneath a NOT, we can't pull up sublinks
* would result in inserting a join underneath an ANTI * that reference the left-hand stuff, but it's still
* join with which it could not commute, and that could * okay to pull up sublinks referencing j->rarg.
* easily lead to a worse plan than what we've
* historically generated.
*/ */
#ifdef NOT_USED j->quals = pull_up_sublinks_qual_recurse(root,
/* Yes; recursively process what we pulled up */ j->quals,
Relids child_rels; &j->rarg,
child_rels,
NULL, NULL);
/* Return NULL representing constant TRUE */
return NULL;
}
if (available_rels2 != NULL &&
(j = convert_EXISTS_sublink_to_join(root, sublink, true,
available_rels2)) != NULL)
{
/* Yes; insert the new join node into the join tree */
j->larg = *jtlink2;
*jtlink2 = (Node *) j;
/* Recursively process pulled-up jointree nodes */
j->rarg = pull_up_sublinks_jointree_recurse(root, j->rarg = pull_up_sublinks_jointree_recurse(root,
j->rarg, j->rarg,
&child_rels); &child_rels);
/* Any inserted joins get stacked onto j->rarg */ /*
* Now recursively process the pulled-up quals. Because
* we are underneath a NOT, we can't pull up sublinks
* that reference the left-hand stuff, but it's still
* okay to pull up sublinks referencing j->rarg.
*/
j->quals = pull_up_sublinks_qual_recurse(root, j->quals = pull_up_sublinks_qual_recurse(root,
j->quals, j->quals,
&j->rarg,
child_rels, child_rels,
&j->rarg); NULL, NULL);
#endif /* Return NULL representing constant TRUE */
/* Now insert the new join node into the join tree */
j->larg = *jtlink;
*jtlink = (Node *) j;
/* and return NULL representing constant TRUE */
return NULL; return NULL;
} }
} }
...@@ -427,8 +505,10 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, ...@@ -427,8 +505,10 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
newclause = pull_up_sublinks_qual_recurse(root, newclause = pull_up_sublinks_qual_recurse(root,
oldclause, oldclause,
available_rels, jtlink1,
jtlink); available_rels1,
jtlink2,
available_rels2);
if (newclause) if (newclause)
newclauses = lappend(newclauses, newclause); newclauses = lappend(newclauses, newclause);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment