From: Tom Lane Date: Sun, 17 Aug 2008 19:40:11 +0000 (+0000) Subject: Add some defenses against constant-FALSE outer join conditions. Since X-Git-Tag: REL8_4_BETA1~1074 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=719012e013f10f72938520c46889c52df40fa329;p=postgresql 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. --- diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index b517f09e00..9ac0cc9743 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -8,7 +8,7 @@ * * * 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, static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel); static bool has_legal_joinclause(PlannerInfo *root, 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) * this way since it's conceivable that dummy-ness of a multi-element * 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, * plus JOIN_INNER. */ switch (sjinfo->jointype) { 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; } add_paths_to_joinrel(root, joinrel, rel1, rel2, @@ -579,9 +585,12 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) case JOIN_LEFT: if (is_dummy_rel(rel1)) { - mark_dummy_join(joinrel); + mark_dummy_rel(joinrel); 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, JOIN_LEFT, sjinfo, restrictlist); @@ -592,7 +601,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) case JOIN_FULL: if (is_dummy_rel(rel1) && is_dummy_rel(rel2)) { - mark_dummy_join(joinrel); + mark_dummy_rel(joinrel); break; } add_paths_to_joinrel(root, joinrel, rel1, rel2, @@ -603,9 +612,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) restrictlist); break; 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; } add_paths_to_joinrel(root, joinrel, rel1, rel2, @@ -632,9 +642,12 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2) case JOIN_ANTI: if (is_dummy_rel(rel1)) { - mark_dummy_join(joinrel); + mark_dummy_rel(joinrel); 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, JOIN_ANTI, sjinfo, restrictlist); @@ -851,10 +864,10 @@ is_dummy_rel(RelOptInfo *rel) } /* - * Mark a joinrel as proven empty. + * Mark a rel as proven empty. */ static void -mark_dummy_join(RelOptInfo *rel) +mark_dummy_rel(RelOptInfo *rel) { /* Set dummy size estimate */ rel->rows = 0; @@ -865,10 +878,46 @@ mark_dummy_join(RelOptInfo *rel) /* Set up the dummy path */ 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 - * in order to keep is_dummy_rel as cheap as possible (ie, not have - * to examine the pathlist). + * Despite the above comment, the restriction list we see here might + * possibly have other members besides the FALSE constant, since other + * 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; }