]> granicus.if.org Git - postgresql/commitdiff
Repair planning bug introduced in 7.4: outer-join ON clauses that referenced
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 28 Sep 2005 21:17:02 +0000 (21:17 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 28 Sep 2005 21:17:02 +0000 (21:17 +0000)
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
src/backend/optimizer/plan/planmain.c
src/include/optimizer/planmain.h

index 91a23d6d1c3e9ac95aabd1f8d4156b6111063a82..7e3d5bca55b69ea20e667da606098d082a29ff42 100644 (file)
@@ -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);
 }
 
 /*
index 1aca1249d43da178474ce51ddeb331875b7b8289..24d53be9e97856eddd67276ded7fbe5754486f5f 100644 (file)
@@ -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
index 652431b894f94c04e06507d308b76e3323df77fd..59b925f8b74e6399fb577256f6986de9d5b38002 100644 (file)
@@ -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,