From 2e1254e7fa0b84e83d201d0ffc7146b63a4a4a49 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 28 Sep 2005 21:17:02 +0000 Subject: [PATCH] Repair planning bug introduced in 7.4: outer-join ON clauses that referenced only the inner-side relation would be considered as potential equijoin clauses, which is wrong because the condition doesn't necessarily hold above the point of the outer join. Per test case from Kevin Grittner (bug#1916). --- src/backend/optimizer/plan/initsplan.c | 92 ++++++++++++++++++++------ src/backend/optimizer/plan/planmain.c | 4 +- src/include/optimizer/planmain.h | 5 +- 3 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 91a23d6d1c..7e3d5bca55 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.108 2005/07/02 23:00:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.109 2005/09/28 21:17:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -38,7 +38,8 @@ static void mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels); static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, bool is_pushed_down, - bool isdeduced, + bool is_deduced, + bool below_outer_join, Relids outerjoin_nonnullable, Relids qualscope); static void add_vars_to_targetlist(PlannerInfo *root, List *vars, @@ -174,6 +175,10 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed) * with outerjoinset information, to aid in proper positioning of qual * clauses that appear above outer joins. * + * jtnode is the jointree node currently being examined. below_outer_join + * is TRUE if this node is within the nullable side of a higher-level outer + * join. + * * NOTE: when dealing with inner joins, it is appropriate to let a qual clause * be evaluated at the lowest level where all the variables it mentions are * available. However, we cannot push a qual down into the nullable side(s) @@ -189,7 +194,8 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed) * internal convenience; no outside callers pay attention to the result. */ Relids -distribute_quals_to_rels(PlannerInfo *root, Node *jtnode) +distribute_quals_to_rels(PlannerInfo *root, Node *jtnode, + bool below_outer_join) { Relids result = NULL; @@ -214,7 +220,8 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode) { result = bms_add_members(result, distribute_quals_to_rels(root, - lfirst(l))); + lfirst(l), + below_outer_join)); } /* @@ -223,7 +230,8 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode) */ foreach(l, (List *) f->quals) distribute_qual_to_rels(root, (Node *) lfirst(l), - true, false, NULL, result); + true, false, below_outer_join, + NULL, result); } else if (IsA(jtnode, JoinExpr)) { @@ -247,27 +255,47 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode) * rels from being pushed down below this level. (It's okay for * upper quals to be pushed down to the outer side, however.) */ - leftids = distribute_quals_to_rels(root, j->larg); - rightids = distribute_quals_to_rels(root, j->rarg); - - result = bms_union(leftids, rightids); - - nonnullable_rels = nullable_rels = NULL; switch (j->jointype) { case JOIN_INNER: + leftids = distribute_quals_to_rels(root, j->larg, + below_outer_join); + rightids = distribute_quals_to_rels(root, j->rarg, + below_outer_join); + + result = bms_union(leftids, rightids); /* Inner join adds no restrictions for quals */ + nonnullable_rels = NULL; + nullable_rels = NULL; break; case JOIN_LEFT: + leftids = distribute_quals_to_rels(root, j->larg, + below_outer_join); + rightids = distribute_quals_to_rels(root, j->rarg, + true); + + result = bms_union(leftids, rightids); nonnullable_rels = leftids; nullable_rels = rightids; break; case JOIN_FULL: + leftids = distribute_quals_to_rels(root, j->larg, + true); + rightids = distribute_quals_to_rels(root, j->rarg, + true); + + result = bms_union(leftids, rightids); /* each side is both outer and inner */ nonnullable_rels = result; nullable_rels = result; break; case JOIN_RIGHT: + leftids = distribute_quals_to_rels(root, j->larg, + true); + rightids = distribute_quals_to_rels(root, j->rarg, + below_outer_join); + + result = bms_union(leftids, rightids); nonnullable_rels = rightids; nullable_rels = leftids; break; @@ -280,16 +308,20 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("UNION JOIN is not implemented"))); + nonnullable_rels = NULL; /* keep compiler quiet */ + nullable_rels = NULL; break; default: elog(ERROR, "unrecognized join type: %d", (int) j->jointype); + nonnullable_rels = NULL; /* keep compiler quiet */ + nullable_rels = NULL; break; } foreach(qual, (List *) j->quals) distribute_qual_to_rels(root, (Node *) lfirst(qual), - false, false, + false, false, below_outer_join, nonnullable_rels, result); if (nullable_rels != NULL) @@ -357,7 +389,9 @@ mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels) * 'clause': the qual clause to be distributed * 'is_pushed_down': if TRUE, force the clause to be marked 'is_pushed_down' * (this indicates the clause came from a FromExpr, not a JoinExpr) - * 'isdeduced': TRUE if the qual came from implied-equality deduction + * 'is_deduced': TRUE if the qual came from implied-equality deduction + * 'below_outer_join': TRUE if the qual is from a JOIN/ON that is below the + * nullable side of a higher-level outer join. * 'outerjoin_nonnullable': NULL if not an outer-join qual, else the set of * baserels appearing on the outer (nonnullable) side of the join * 'qualscope': set of baserels the qual's syntactic scope covers @@ -369,7 +403,8 @@ mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels) static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, bool is_pushed_down, - bool isdeduced, + bool is_deduced, + bool below_outer_join, Relids outerjoin_nonnullable, Relids qualscope) { @@ -406,7 +441,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * Check to see if clause application must be delayed by outer-join * considerations. */ - if (isdeduced) + if (is_deduced) { /* * If the qual came from implied-equality deduction, we always @@ -432,14 +467,18 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * * Note: an outer-join qual that mentions only nullable-side rels can * be pushed down into the nullable side without changing the join - * result, so we treat it the same as an ordinary inner-join qual. + * result, so we treat it the same as an ordinary inner-join qual, + * except for not setting maybe_equijoin (see below). */ relids = qualscope; /* * We can't use such a clause to deduce equijoin (the left and * right sides might be unequal above the join because one of * them has gone to NULL) ... but we might be able to use it - * for more limited purposes. + * for more limited purposes. Note: for the current uses of + * deductions from an outer-join clause, it seems safe to make + * the deductions even when the clause is below a higher-level + * outer join; so we do not check below_outer_join here. */ maybe_equijoin = false; maybe_outer_join = true; @@ -473,8 +512,19 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, if (bms_is_subset(addrelids, relids)) { - /* Qual is not affected by any outer-join restriction */ - maybe_equijoin = true; + /* + * Qual is not delayed by any lower outer-join restriction. + * If it is not itself below or within an outer join, we + * can consider it "valid everywhere", so consider feeding + * it to the equijoin machinery. (If it is within an outer + * join, we can't consider it "valid everywhere": once the + * contained variables have gone to NULL, we'd be asserting + * things like NULL = NULL, which is not true.) + */ + if (!below_outer_join && outerjoin_nonnullable == NULL) + maybe_equijoin = true; + else + maybe_equijoin = false; } else { @@ -543,7 +593,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * redundancy will be detected when the join clause is moved * into a join rel's restriction list.) */ - if (!isdeduced || + if (!is_deduced || !qual_is_redundant(root, restrictinfo, rel->baserestrictinfo)) { @@ -810,7 +860,7 @@ process_implied_equality(PlannerInfo *root, * taken for an original JOIN/ON clause. */ distribute_qual_to_rels(root, (Node *) clause, - true, true, NULL, relids); + true, true, false, NULL, relids); } /* diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 1aca1249d4..24d53be9e9 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.87 2005/08/27 22:13:43 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.88 2005/09/28 21:17:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -155,7 +155,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, */ build_base_rel_tlists(root, tlist); - (void) distribute_quals_to_rels(root, (Node *) parse->jointree); + (void) distribute_quals_to_rels(root, (Node *) parse->jointree, false); /* * Use the completed lists of equijoined keys to deduce any implied diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 652431b894..59b925f8b7 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.88 2005/08/27 22:13:44 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.89 2005/09/28 21:17:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -67,7 +67,8 @@ extern bool is_projection_capable_plan(Plan *plan); */ extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); -extern Relids distribute_quals_to_rels(PlannerInfo *root, Node *jtnode); +extern Relids distribute_quals_to_rels(PlannerInfo *root, Node *jtnode, + bool below_outer_join); extern void process_implied_equality(PlannerInfo *root, Node *item1, Node *item2, Oid sortop1, Oid sortop2, -- 2.40.0