Commit 719012e0 authored by Tom Lane's avatar Tom Lane

Add some defenses against constant-FALSE outer join conditions. Since

eval_const_expressions will generally throw away anything that's ANDed with
constant FALSE, what we're left with given an example like

select * from tenk1 a where (unique1,0) in (select unique2,1 from tenk1 b);

is a cartesian product computation, which is really not acceptable.
This is a regression in CVS HEAD compared to previous releases, which were
able to notice the impossible join condition in this case --- though not in
some related cases that are also improved by this patch, such as

select * from tenk1 a left join tenk1 b on (a.unique1=b.unique2 and 0=1);

Fix by skipping evaluation of the appropriate side of the outer join in
cases where it's demonstrably unnecessary.
parent f2689e42
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.93 2008/08/14 18:47:59 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.94 2008/08/17 19:40:11 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -28,7 +28,8 @@ static List *make_rels_by_clauseless_joins(PlannerInfo *root, ...@@ -28,7 +28,8 @@ static List *make_rels_by_clauseless_joins(PlannerInfo *root,
static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel); static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel); static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
static bool is_dummy_rel(RelOptInfo *rel); static bool is_dummy_rel(RelOptInfo *rel);
static void mark_dummy_join(RelOptInfo *rel); static void mark_dummy_rel(RelOptInfo *rel);
static bool restriction_is_constant_false(List *restrictlist);
/* /*
...@@ -558,15 +559,20 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) ...@@ -558,15 +559,20 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
* this way since it's conceivable that dummy-ness of a multi-element * this way since it's conceivable that dummy-ness of a multi-element
* join might only be noticeable for certain construction paths.) * join might only be noticeable for certain construction paths.)
* *
* Also, a provably constant-false join restriction typically means that
* we can skip evaluating one or both sides of the join. We do this
* by marking the appropriate rel as dummy.
*
* We need only consider the jointypes that appear in join_info_list, * We need only consider the jointypes that appear in join_info_list,
* plus JOIN_INNER. * plus JOIN_INNER.
*/ */
switch (sjinfo->jointype) switch (sjinfo->jointype)
{ {
case JOIN_INNER: case JOIN_INNER:
if (is_dummy_rel(rel1) || is_dummy_rel(rel2)) if (is_dummy_rel(rel1) || is_dummy_rel(rel2) ||
restriction_is_constant_false(restrictlist))
{ {
mark_dummy_join(joinrel); mark_dummy_rel(joinrel);
break; break;
} }
add_paths_to_joinrel(root, joinrel, rel1, rel2, add_paths_to_joinrel(root, joinrel, rel1, rel2,
...@@ -579,9 +585,12 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) ...@@ -579,9 +585,12 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
case JOIN_LEFT: case JOIN_LEFT:
if (is_dummy_rel(rel1)) if (is_dummy_rel(rel1))
{ {
mark_dummy_join(joinrel); mark_dummy_rel(joinrel);
break; break;
} }
if (restriction_is_constant_false(restrictlist) &&
bms_is_subset(rel2->relids, sjinfo->syn_righthand))
mark_dummy_rel(rel2);
add_paths_to_joinrel(root, joinrel, rel1, rel2, add_paths_to_joinrel(root, joinrel, rel1, rel2,
JOIN_LEFT, sjinfo, JOIN_LEFT, sjinfo,
restrictlist); restrictlist);
...@@ -592,7 +601,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) ...@@ -592,7 +601,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
case JOIN_FULL: case JOIN_FULL:
if (is_dummy_rel(rel1) && is_dummy_rel(rel2)) if (is_dummy_rel(rel1) && is_dummy_rel(rel2))
{ {
mark_dummy_join(joinrel); mark_dummy_rel(joinrel);
break; break;
} }
add_paths_to_joinrel(root, joinrel, rel1, rel2, add_paths_to_joinrel(root, joinrel, rel1, rel2,
...@@ -603,9 +612,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) ...@@ -603,9 +612,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
restrictlist); restrictlist);
break; break;
case JOIN_SEMI: case JOIN_SEMI:
if (is_dummy_rel(rel1) || is_dummy_rel(rel2)) if (is_dummy_rel(rel1) || is_dummy_rel(rel2) ||
restriction_is_constant_false(restrictlist))
{ {
mark_dummy_join(joinrel); mark_dummy_rel(joinrel);
break; break;
} }
add_paths_to_joinrel(root, joinrel, rel1, rel2, add_paths_to_joinrel(root, joinrel, rel1, rel2,
...@@ -632,9 +642,12 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) ...@@ -632,9 +642,12 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
case JOIN_ANTI: case JOIN_ANTI:
if (is_dummy_rel(rel1)) if (is_dummy_rel(rel1))
{ {
mark_dummy_join(joinrel); mark_dummy_rel(joinrel);
break; break;
} }
if (restriction_is_constant_false(restrictlist) &&
bms_is_subset(rel2->relids, sjinfo->syn_righthand))
mark_dummy_rel(rel2);
add_paths_to_joinrel(root, joinrel, rel1, rel2, add_paths_to_joinrel(root, joinrel, rel1, rel2,
JOIN_ANTI, sjinfo, JOIN_ANTI, sjinfo,
restrictlist); restrictlist);
...@@ -851,10 +864,10 @@ is_dummy_rel(RelOptInfo *rel) ...@@ -851,10 +864,10 @@ is_dummy_rel(RelOptInfo *rel)
} }
/* /*
* Mark a joinrel as proven empty. * Mark a rel as proven empty.
*/ */
static void static void
mark_dummy_join(RelOptInfo *rel) mark_dummy_rel(RelOptInfo *rel)
{ {
/* Set dummy size estimate */ /* Set dummy size estimate */
rel->rows = 0; rel->rows = 0;
...@@ -865,10 +878,46 @@ mark_dummy_join(RelOptInfo *rel) ...@@ -865,10 +878,46 @@ mark_dummy_join(RelOptInfo *rel)
/* Set up the dummy path */ /* Set up the dummy path */
add_path(rel, (Path *) create_append_path(rel, NIL)); add_path(rel, (Path *) create_append_path(rel, NIL));
/* Set or update cheapest_total_path */
set_cheapest(rel);
}
/*
* restriction_is_constant_false --- is a restrictlist just FALSE?
*
* In cases where a qual is provably constant FALSE, eval_const_expressions
* will generally have thrown away anything that's ANDed with it. In outer
* join situations this will leave us computing cartesian products only to
* decide there's no match for an outer row, which is pretty stupid. So,
* we need to detect the case.
*/
static bool
restriction_is_constant_false(List *restrictlist)
{
ListCell *lc;
/* /*
* Although set_cheapest will be done again later, we do it immediately * Despite the above comment, the restriction list we see here might
* in order to keep is_dummy_rel as cheap as possible (ie, not have * possibly have other members besides the FALSE constant, since other
* to examine the pathlist). * quals could get "pushed down" to the outer join level. So we check
* each member of the list.
*/ */
set_cheapest(rel); foreach(lc, restrictlist)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
Assert(IsA(rinfo, RestrictInfo));
if (rinfo->clause && IsA(rinfo->clause, Const))
{
Const *con = (Const *) rinfo->clause;
/* constant NULL is as good as constant FALSE for our purposes */
if (con->constisnull)
return true;
if (!DatumGetBool(con->constvalue))
return true;
}
}
return false;
} }
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