aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/plan/subselect.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2008-08-17 01:20:00 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2008-08-17 01:20:00 +0000
commit19e34b62395b36513a8e6c35ddfbeef12dd1e89f (patch)
treecf74ae45a1d9ea3c6f2ffc471d5dea75fb510984 /src/backend/optimizer/plan/subselect.c
parent909346eff0ca2c7a73e889122d6f54669494141b (diff)
downloadpostgresql-19e34b62395b36513a8e6c35ddfbeef12dd1e89f.tar.gz
postgresql-19e34b62395b36513a8e6c35ddfbeef12dd1e89f.zip
Improve sublink pullup code to handle ANY/EXISTS sublinks that are at top
level of a JOIN/ON clause, not only at top level of WHERE. (However, we can't do this in an outer join's ON clause, unless the ANY/EXISTS refers only to the nullable side of the outer join, so that it can effectively be pushed down into the nullable side.) Per request from Kevin Grittner. In passing, fix a bug in the initial implementation of EXISTS pullup: it would Assert if the EXIST's WHERE clause used a join alias variable. Since we haven't yet flattened join aliases when this transformation happens, it's necessary to include join relids in the computed set of RHS relids.
Diffstat (limited to 'src/backend/optimizer/plan/subselect.c')
-rw-r--r--src/backend/optimizer/plan/subselect.c126
1 files changed, 74 insertions, 52 deletions
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 1e4e9fe565b..d4374516ac8 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.133 2008/08/14 18:47:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.134 2008/08/17 01:20:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -725,18 +725,31 @@ hash_ok_operator(OpExpr *expr)
/*
* convert_ANY_sublink_to_join: can we convert an ANY SubLink to a join?
*
- * The caller has found an ANY SubLink at the top level of WHERE, but has not
- * checked the properties of the SubLink further. Decide whether it is
- * appropriate to process this SubLink in join style. If not, return NULL.
- * If so, build the qual clause(s) to replace the SubLink, and return them.
- * The qual clauses are wrapped in a FlattenedSubLink node to help later
- * processing place them properly.
+ * The caller has found an ANY SubLink at the top level of one of the query's
+ * qual clauses, but has not checked the properties of the SubLink further.
+ * Decide whether it is appropriate to process this SubLink in join style.
+ * Return TRUE if so, FALSE if the SubLink cannot be converted.
+ *
+ * The only non-obvious input parameter is available_rels: this is the set
+ * of query rels that can safely be referenced in the sublink expression.
+ * (We must restrict this to avoid changing the semantics when a sublink
+ * is present in an outer join's ON qual.) The conversion must fail if
+ * the converted qual would reference any but these parent-query relids.
+ *
+ * On success, two output parameters are returned:
+ * *new_qual is set to the qual tree that should replace the SubLink in
+ * the parent query's qual tree. The qual clauses are wrapped in a
+ * FlattenedSubLink node to help later processing place them properly.
+ * *fromlist is set to a list of pulled-up jointree item(s) that must be
+ * added at the proper spot in the parent query's jointree.
*
* Side effects of a successful conversion include adding the SubLink's
* subselect to the query's rangetable.
*/
-Node *
-convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink)
+bool
+convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
+ Relids available_rels,
+ Node **new_qual, List **fromlist)
{
Query *parse = root->parse;
Query *subselect = (Query *) sublink->subselect;
@@ -755,7 +768,7 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink)
* higher levels should be okay, though.)
*/
if (contain_vars_of_level((Node *) subselect, 1))
- return NULL;
+ return false;
/*
* The test expression must contain some Vars of the current query,
@@ -764,16 +777,22 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink)
*/
left_varnos = pull_varnos(sublink->testexpr);
if (bms_is_empty(left_varnos))
- return NULL;
+ return false;
+
+ /*
+ * However, it can't refer to anything outside available_rels.
+ */
+ if (!bms_is_subset(left_varnos, available_rels))
+ return false;
/*
* The combining operators and left-hand expressions mustn't be volatile.
*/
if (contain_volatile_functions(sublink->testexpr))
- return NULL;
+ return false;
/*
- * Okay, pull up the sub-select into top range table and jointree.
+ * Okay, pull up the sub-select into upper range table.
*
* We rely here on the assumption that the outer query has no references
* to the inner (necessarily true, other than the Vars that we build
@@ -786,16 +805,15 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink)
false);
parse->rtable = lappend(parse->rtable, rte);
rtindex = list_length(parse->rtable);
- rtr = makeNode(RangeTblRef);
- rtr->rtindex = rtindex;
/*
- * We assume it's okay to add the pulled-up subquery to the topmost FROM
- * list. This should be all right for ANY clauses appearing in WHERE
- * or in upper-level plain JOIN/ON clauses. ANYs appearing below any
- * outer joins couldn't be placed there, however.
+ * Form a RangeTblRef for the pulled-up sub-select. This must be added
+ * to the upper jointree, but it is caller's responsibility to figure
+ * out where.
*/
- parse->jointree->fromlist = lappend(parse->jointree->fromlist, rtr);
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = rtindex;
+ *fromlist = list_make1(rtr);
/*
* Build a list of Vars representing the subselect outputs.
@@ -805,14 +823,14 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink)
rtindex);
/*
- * Build the result qual expression, replacing Params with these Vars.
+ * Build the replacement qual expression, replacing Params with these Vars.
*/
quals = (Expr *) convert_testexpr(root,
sublink->testexpr,
subquery_vars);
/*
- * Now build the FlattenedSubLink node.
+ * And finally, build the FlattenedSubLink node.
*/
fslink = makeNode(FlattenedSubLink);
fslink->jointype = JOIN_SEMI;
@@ -820,7 +838,9 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink)
fslink->righthand = bms_make_singleton(rtindex);
fslink->quals = quals;
- return (Node *) fslink;
+ *new_qual = (Node *) fslink;
+
+ return true;
}
/*
@@ -883,20 +903,15 @@ simplify_EXISTS_query(Query *query)
/*
* convert_EXISTS_sublink_to_join: can we convert an EXISTS SubLink to a join?
*
- * The caller has found an EXISTS SubLink at the top level of WHERE, or just
- * underneath a NOT, but has not checked the properties of the SubLink
- * further. Decide whether it is appropriate to process this SubLink in join
- * style. If not, return NULL. If so, build the qual clause(s) to replace
- * the SubLink, and return them. (In the NOT case, the returned clauses are
- * intended to replace the NOT as well.) The qual clauses are wrapped in a
- * FlattenedSubLink node to help later processing place them properly.
- *
- * Side effects of a successful conversion include adding the SubLink's
- * subselect to the query's rangetable.
+ * The API of this function is identical to convert_ANY_sublink_to_join's,
+ * except that we also support the case where the caller has found NOT EXISTS,
+ * so we need an additional input parameter "under_not".
*/
-Node *
+bool
convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
- bool under_not)
+ bool under_not,
+ Relids available_rels,
+ Node **new_qual, List **fromlist)
{
Query *parse = root->parse;
Query *subselect = (Query *) sublink->subselect;
@@ -924,7 +939,7 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
* us with noplace to evaluate the targetlist.
*/
if (!simplify_EXISTS_query(subselect))
- return NULL;
+ return false;
/*
* Separate out the WHERE clause. (We could theoretically also remove
@@ -939,20 +954,20 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
* query. (Vars of higher levels should be okay, though.)
*/
if (contain_vars_of_level((Node *) subselect, 1))
- return NULL;
+ return false;
/*
* On the other hand, the WHERE clause must contain some Vars of the
* parent query, else it's not gonna be a join.
*/
if (!contain_vars_of_level(whereClause, 1))
- return NULL;
+ return false;
/*
* We don't risk optimizing if the WHERE clause is volatile, either.
*/
if (contain_volatile_functions(whereClause))
- return NULL;
+ return false;
/*
* Also disallow SubLinks within the WHERE clause. (XXX this could
@@ -960,10 +975,10 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
* below, and it doesn't seem worth worrying about in a first pass.)
*/
if (contain_subplans(whereClause))
- return NULL;
+ return false;
/*
- * Okay, pull up the sub-select into top range table and jointree.
+ * Prepare to pull up the sub-select into top range table.
*
* We rely here on the assumption that the outer query has no references
* to the inner (necessarily true). Therefore this is a lot easier than
@@ -973,7 +988,7 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
* to do. The machinations of simplify_EXISTS_query ensured that there
* is nothing interesting in the subquery except an rtable and jointree,
* and even the jointree FromExpr no longer has quals. So we can just
- * append the rtable to our own and append the fromlist to our own.
+ * append the rtable to our own and attach the fromlist to our own.
* But first, adjust all level-zero varnos in the subquery to account
* for the rtable merger.
*/
@@ -1007,24 +1022,29 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
bms_free(clause_varnos);
Assert(!bms_is_empty(left_varnos));
- /* Also identify all the rels syntactically within the subselect */
- subselect_varnos = get_relids_in_jointree((Node *) subselect->jointree);
+ /*
+ * Now that we've got the set of upper-level varnos, we can make the
+ * last check: only available_rels can be referenced.
+ */
+ if (!bms_is_subset(left_varnos, available_rels))
+ return false;
+
+ /* Identify all the rels syntactically within the subselect */
+ subselect_varnos = get_relids_in_jointree((Node *) subselect->jointree,
+ true);
Assert(bms_is_subset(right_varnos, subselect_varnos));
/* Now we can attach the modified subquery rtable to the parent */
parse->rtable = list_concat(parse->rtable, subselect->rtable);
/*
- * We assume it's okay to add the pulled-up subquery to the topmost FROM
- * list. This should be all right for EXISTS clauses appearing in WHERE
- * or in upper-level plain JOIN/ON clauses. EXISTS appearing below any
- * outer joins couldn't be placed there, however.
+ * Pass back the subquery fromlist to be attached to upper jointree
+ * in a suitable place.
*/
- parse->jointree->fromlist = list_concat(parse->jointree->fromlist,
- subselect->jointree->fromlist);
+ *fromlist = subselect->jointree->fromlist;
/*
- * Now build the FlattenedSubLink node.
+ * And finally, build the FlattenedSubLink node.
*/
fslink = makeNode(FlattenedSubLink);
fslink->jointype = under_not ? JOIN_ANTI : JOIN_SEMI;
@@ -1032,7 +1052,9 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
fslink->righthand = subselect_varnos;
fslink->quals = (Expr *) whereClause;
- return (Node *) fslink;
+ *new_qual = (Node *) fslink;
+
+ return true;
}
/*