Teach planner about some cases where a restriction clause can be
authorTom Lane
Sat, 2 Jul 2005 23:00:42 +0000 (23:00 +0000)
committerTom Lane
Sat, 2 Jul 2005 23:00:42 +0000 (23:00 +0000)
propagated inside an outer join.  In particular, given
LEFT JOIN ON (A = B) WHERE A = constant, we cannot conclude that
B = constant at the top level (B might be null instead), but we
can nonetheless put a restriction B = constant into the quals for
B's relation, since no inner-side rows not meeting that condition
can contribute to the final result.  Similarly, given
FULL JOIN USING (J) WHERE J = constant, we can't directly conclude
that either input J variable = constant, but it's OK to push such
quals into each input rel.  Per recent gripe from Kim Bisgaard.
Along the way, remove 'valid_everywhere' flag from RestrictInfo,
as on closer analysis it was not being used for anything, and was
defined backwards anyway.

13 files changed:
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/path/orindxpath.c
src/backend/optimizer/path/pathkeys.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/initsplan.c
src/backend/optimizer/plan/planmain.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/util/restrictinfo.c
src/include/nodes/relation.h
src/include/optimizer/restrictinfo.h

index ce16d2eba182fec697bfa0b626114839132495cf..8d42cead085d365071faa1035de3f99de53d3b2c 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.310 2005/06/28 05:08:56 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.311 2005/07/02 23:00:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1249,7 +1249,6 @@ _copyRestrictInfo(RestrictInfo *from)
 
    COPY_NODE_FIELD(clause);
    COPY_SCALAR_FIELD(is_pushed_down);
-   COPY_SCALAR_FIELD(valid_everywhere);
    COPY_SCALAR_FIELD(can_join);
    COPY_BITMAPSET_FIELD(clause_relids);
    COPY_BITMAPSET_FIELD(required_relids);
index ade4f16a090885c880ed1e4ab42479aad84a5701..e8c8a0cbc94c467bb75ab017a2f3b436fe780b7b 100644 (file)
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.247 2005/06/28 05:08:57 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.248 2005/07/02 23:00:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -603,7 +603,6 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b)
 {
    COMPARE_NODE_FIELD(clause);
    COMPARE_SCALAR_FIELD(is_pushed_down);
-   COMPARE_SCALAR_FIELD(valid_everywhere);
    COMPARE_BITMAPSET_FIELD(required_relids);
 
    /*
index 81fb9c88fa6d2bff3402fff4bc4917e90c7373ce..0b905dd043d3e95a37c34856b65157581cb45262 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.257 2005/06/28 05:08:57 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.258 2005/07/02 23:00:39 tgl Exp $
  *
  * NOTES
  *   Every node type that can appear in stored rules' parsetrees *must*
@@ -1164,9 +1164,13 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
    WRITE_NODE_FIELD(parse);
    WRITE_NODE_FIELD(join_rel_list);
    WRITE_NODE_FIELD(equi_key_list);
+   WRITE_NODE_FIELD(left_join_clauses);
+   WRITE_NODE_FIELD(right_join_clauses);
+   WRITE_NODE_FIELD(full_join_clauses);
    WRITE_NODE_FIELD(in_info_list);
    WRITE_NODE_FIELD(query_pathkeys);
    WRITE_BOOL_FIELD(hasJoinRTEs);
+   WRITE_BOOL_FIELD(hasOuterJoins);
    WRITE_BOOL_FIELD(hasHavingQual);
 }
 
@@ -1234,7 +1238,6 @@ _outRestrictInfo(StringInfo str, RestrictInfo *node)
    /* NB: this isn't a complete set of fields */
    WRITE_NODE_FIELD(clause);
    WRITE_BOOL_FIELD(is_pushed_down);
-   WRITE_BOOL_FIELD(valid_everywhere);
    WRITE_BOOL_FIELD(can_join);
    WRITE_BITMAPSET_FIELD(clause_relids);
    WRITE_BITMAPSET_FIELD(required_relids);
index be684094119c5b7f7588502edbb1ab86c187e07a..07b67a88228174e30d632ed841a48bb028a5461d 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.185 2005/06/14 04:04:30 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.186 2005/07/02 23:00:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1882,7 +1882,7 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups)
                {
                    resultquals = lappend(resultquals,
                                          make_restrictinfo(boolqual,
-                                                           true, true,
+                                                           true,
                                                            NULL));
                    continue;
                }
@@ -2132,7 +2132,7 @@ prefix_quals(Node *leftop, Oid opclass,
            elog(ERROR, "no = operator for opclass %u", opclass);
        expr = make_opclause(oproid, BOOLOID, false,
                             (Expr *) leftop, (Expr *) prefix_const);
-       result = list_make1(make_restrictinfo(expr, true, true, NULL));
+       result = list_make1(make_restrictinfo(expr, true, NULL));
        return result;
    }
 
@@ -2147,7 +2147,7 @@ prefix_quals(Node *leftop, Oid opclass,
        elog(ERROR, "no >= operator for opclass %u", opclass);
    expr = make_opclause(oproid, BOOLOID, false,
                         (Expr *) leftop, (Expr *) prefix_const);
-   result = list_make1(make_restrictinfo(expr, true, true, NULL));
+   result = list_make1(make_restrictinfo(expr, true, NULL));
 
    /*-------
     * If we can create a string larger than the prefix, we can say
@@ -2163,7 +2163,7 @@ prefix_quals(Node *leftop, Oid opclass,
            elog(ERROR, "no < operator for opclass %u", opclass);
        expr = make_opclause(oproid, BOOLOID, false,
                             (Expr *) leftop, (Expr *) greaterstr);
-       result = lappend(result, make_restrictinfo(expr, true, true, NULL));
+       result = lappend(result, make_restrictinfo(expr, true, NULL));
    }
 
    return result;
@@ -2234,7 +2234,7 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop)
                         (Expr *) leftop,
                         (Expr *) makeConst(datatype, -1, opr1right,
                                            false, false));
-   result = list_make1(make_restrictinfo(expr, true, true, NULL));
+   result = list_make1(make_restrictinfo(expr, true, NULL));
 
    /* create clause "key <= network_scan_last( rightop )" */
 
@@ -2249,7 +2249,7 @@ network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, Datum rightop)
                         (Expr *) leftop,
                         (Expr *) makeConst(datatype, -1, opr2right,
                                            false, false));
-   result = lappend(result, make_restrictinfo(expr, true, true, NULL));
+   result = lappend(result, make_restrictinfo(expr, true, NULL));
 
    return result;
 }
index 7eadd220b949ba085ac71c7e762698a996631883..884329edd9723f5b130576fb1db3119f5279cddb 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.72 2005/06/09 04:18:59 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/orindxpath.c,v 1.73 2005/07/02 23:00:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -90,16 +90,13 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel)
    ListCell   *i;
 
    /*
-    * Find potentially interesting OR joinclauses.  We must ignore any
-    * joinclauses that are not marked valid_everywhere, because they
-    * cannot be pushed down due to outer-join rules.
+    * Find potentially interesting OR joinclauses.
     */
    foreach(i, rel->joininfo)
    {
        RestrictInfo *rinfo = (RestrictInfo *) lfirst(i);
 
-       if (restriction_is_or_clause(rinfo) &&
-           rinfo->valid_everywhere)
+       if (restriction_is_or_clause(rinfo))
        {
            /*
             * Use the generate_bitmap_or_paths() machinery to estimate
@@ -140,8 +137,7 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel)
     * Convert the path's indexclauses structure to a RestrictInfo tree,
     * and add it to the rel's restriction list.
     */
-   newrinfos = make_restrictinfo_from_bitmapqual((Path *) bestpath,
-                                                 true, true);
+   newrinfos = make_restrictinfo_from_bitmapqual((Path *) bestpath, true);
    Assert(list_length(newrinfos) == 1);
    or_rinfo = (RestrictInfo *) linitial(newrinfos);
    Assert(IsA(or_rinfo, RestrictInfo));
index 52075fbf4656c2e9a8d8d3b676ccd76a2890f1f5..389d89f0d8da80682dc3b6f35042948513ee38c9 100644 (file)
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/pathkeys.c,v 1.68 2005/06/09 04:18:59 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/pathkeys.c,v 1.69 2005/07/02 23:00:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 
 static PathKeyItem *makePathKeyItem(Node *key, Oid sortop, bool checkType);
+static void generate_outer_join_implications(PlannerInfo *root,
+                                            List *equi_key_set,
+                                            Relids *relids);
+static void process_implied_const_eq(PlannerInfo *root,
+                                    List *equi_key_set, Relids *relids,
+                                    Node *item1, Oid sortop1,
+                                    Relids item1_relids,
+                                    bool delete_it);
 static List *make_canonical_pathkey(PlannerInfo *root, PathKeyItem *item);
 static Var *find_indexkey_var(PlannerInfo *root, RelOptInfo *rel,
                  AttrNumber varattno);
@@ -193,6 +201,10 @@ add_equijoined_keys(PlannerInfo *root, RestrictInfo *restrictinfo)
  * functions; but we will never consider such an expression to be a pathkey
  * at all, because check_mergejoinable() will reject it.)
  *
+ * Also, when we have constants in an equi_key_list we can try to propagate
+ * the constants into outer joins; see generate_outer_join_implications
+ * for discussion.
+ *
  * This routine just walks the equi_key_list to find all pairwise equalities.
  * We call process_implied_equality (in plan/initsplan.c) to adjust the
  * restrictinfo datastructures for each pair.
@@ -213,9 +225,10 @@ generate_implied_equalities(PlannerInfo *root)
 
        /*
         * A set containing only two items cannot imply any equalities
-        * beyond the one that created the set, so we can skip it.
+        * beyond the one that created the set, so we can skip it ---
+        * unless outer joins appear in the query.
         */
-       if (nitems < 3)
+       if (nitems < 3 && !root->hasOuterJoins)
            continue;
 
        /*
@@ -237,6 +250,20 @@ generate_implied_equalities(PlannerInfo *root)
            i1++;
        }
 
+       /*
+        * If we have constant(s) and outer joins, try to propagate the
+        * constants through outer-join quals.
+        */
+       if (have_consts && root->hasOuterJoins)
+           generate_outer_join_implications(root, curset, relids);
+
+       /*
+        * A set containing only two items cannot imply any equalities
+        * beyond the one that created the set, so we can skip it.
+        */
+       if (nitems < 3)
+           continue;
+
        /*
         * Match each item in the set with all that appear after it (it's
         * sufficient to generate A=B, need not process B=A too).
@@ -285,6 +312,264 @@ generate_implied_equalities(PlannerInfo *root)
    }
 }
 
+/*
+ * generate_outer_join_implications
+ *   Generate clauses that can be deduced in outer-join situations.
+ *
+ * When we have mergejoinable clauses A = B that are outer-join clauses,
+ * we can't blindly combine them with other clauses A = C to deduce B = C,
+ * since in fact the "equality" A = B won't necessarily hold above the
+ * outer join (one of the variables might be NULL instead).  Nonetheless
+ * there are cases where we can add qual clauses using transitivity.
+ *
+ * One case that we look for here is an outer-join clause OUTERVAR = INNERVAR
+ * combined with a pushed-down (valid everywhere) clause OUTERVAR = CONSTANT.
+ * It is safe and useful to push a clause INNERVAR = CONSTANT into the
+ * evaluation of the inner (nullable) relation, because any inner rows not
+ * meeting this condition will not contribute to the outer-join result anyway.
+ * (Any outer rows they could join to will be eliminated by the pushed-down
+ * clause.)
+ *
+ * Note that the above rule does not work for full outer joins, nor for
+ * pushed-down restrictions on an inner-side variable; nor is it very
+ * interesting to consider cases where the pushed-down clause involves
+ * relations entirely outside the outer join, since such clauses couldn't
+ * be pushed into the inner side's scan anyway.  So the restriction to
+ * outervar = pseudoconstant is not really giving up anything.
+ *
+ * For full-join cases, we can only do something useful if it's a FULL JOIN
+ * USING and a merged column has a restriction MERGEDVAR = CONSTANT.  By
+ * the time it gets here, the restriction will look like
+ *     COALESCE(LEFTVAR, RIGHTVAR) = CONSTANT
+ * and we will have a join clause LEFTVAR = RIGHTVAR that we can match the
+ * COALESCE expression to.  In this situation we can push LEFTVAR = CONSTANT
+ * and RIGHTVAR = CONSTANT into the input relations, since any rows not
+ * meeting these conditions cannot contribute to the join result.
+ *
+ * Again, there isn't any traction to be gained by trying to deal with
+ * clauses comparing a mergedvar to a non-pseudoconstant.  So we can make
+ * use of the equi_key_lists to quickly find the interesting pushed-down
+ * clauses.  The interesting outer-join clauses were accumulated for us by
+ * distribute_qual_to_rels.
+ *
+ * equi_key_set: a list of PathKeyItems that are known globally equivalent,
+ * at least one of which is a pseudoconstant.
+ * relids: an array of Relids sets showing the relation membership of each
+ * PathKeyItem in equi_key_set.
+ */
+static void
+generate_outer_join_implications(PlannerInfo *root,
+                                List *equi_key_set,
+                                Relids *relids)
+{
+   ListCell   *l1;
+
+   /* Examine each mergejoinable outer-join clause with OUTERVAR on left */
+   foreach(l1, root->left_join_clauses)
+   {
+       RestrictInfo *rinfo = (RestrictInfo *) lfirst(l1);
+       Node   *leftop = get_leftop(rinfo->clause);
+       Node   *rightop = get_rightop(rinfo->clause);
+       ListCell   *l2;
+
+       /* Scan to see if it matches any element of equi_key_set */
+       foreach(l2, equi_key_set)
+       {
+           PathKeyItem *item1 = (PathKeyItem *) lfirst(l2);
+
+           if (equal(leftop, item1->key) &&
+               rinfo->left_sortop == item1->sortop)
+           {
+               /*
+                * Yes, so find constant member(s) of set and generate
+                * implied INNERVAR = CONSTANT
+                */
+               process_implied_const_eq(root, equi_key_set, relids,
+                                        rightop,
+                                        rinfo->right_sortop,
+                                        rinfo->right_relids,
+                                        false);
+               /*
+                * We can remove the explicit outer join qual, too,
+                * since we now have tests forcing each of its sides
+                * to the same value.
+                */
+               process_implied_equality(root,
+                                        leftop,
+                                        rightop,
+                                        rinfo->left_sortop,
+                                        rinfo->right_sortop,
+                                        rinfo->left_relids,
+                                        rinfo->right_relids,
+                                        true);
+
+               /* No need to match against remaining set members */
+               break;
+           }
+       }
+   }
+
+   /* Examine each mergejoinable outer-join clause with OUTERVAR on right */
+   foreach(l1, root->right_join_clauses)
+   {
+       RestrictInfo *rinfo = (RestrictInfo *) lfirst(l1);
+       Node   *leftop = get_leftop(rinfo->clause);
+       Node   *rightop = get_rightop(rinfo->clause);
+       ListCell   *l2;
+
+       /* Scan to see if it matches any element of equi_key_set */
+       foreach(l2, equi_key_set)
+       {
+           PathKeyItem *item1 = (PathKeyItem *) lfirst(l2);
+
+           if (equal(rightop, item1->key) &&
+               rinfo->right_sortop == item1->sortop)
+           {
+               /*
+                * Yes, so find constant member(s) of set and generate
+                * implied INNERVAR = CONSTANT
+                */
+               process_implied_const_eq(root, equi_key_set, relids,
+                                        leftop,
+                                        rinfo->left_sortop,
+                                        rinfo->left_relids,
+                                        false);
+               /*
+                * We can remove the explicit outer join qual, too,
+                * since we now have tests forcing each of its sides
+                * to the same value.
+                */
+               process_implied_equality(root,
+                                        leftop,
+                                        rightop,
+                                        rinfo->left_sortop,
+                                        rinfo->right_sortop,
+                                        rinfo->left_relids,
+                                        rinfo->right_relids,
+                                        true);
+
+               /* No need to match against remaining set members */
+               break;
+           }
+       }
+   }
+
+   /* Examine each mergejoinable full-join clause */
+   foreach(l1, root->full_join_clauses)
+   {
+       RestrictInfo *rinfo = (RestrictInfo *) lfirst(l1);
+       Node   *leftop = get_leftop(rinfo->clause);
+       Node   *rightop = get_rightop(rinfo->clause);
+       int i1 = 0;
+       ListCell   *l2;
+
+       /* Scan to see if it matches any element of equi_key_set */
+       foreach(l2, equi_key_set)
+       {
+           PathKeyItem *item1 = (PathKeyItem *) lfirst(l2);
+           CoalesceExpr *cexpr = (CoalesceExpr *) item1->key;
+
+           /*
+            * Try to match a pathkey containing a COALESCE() expression
+            * to the join clause.  We can assume the COALESCE() inputs
+            * are in the same order as the join clause, since both were
+            * automatically generated in the cases we care about.
+            *
+            * XXX currently this may fail to match in cross-type cases
+            * because the COALESCE will contain typecast operations while
+            * the join clause may not (if there is a cross-type mergejoin
+            * operator available for the two column types).
+            * Is it OK to strip implicit coercions from the COALESCE
+            * arguments?  What of the sortops in such cases?
+            */
+           if (IsA(cexpr, CoalesceExpr) &&
+               list_length(cexpr->args) == 2 &&
+               equal(leftop, (Node *) linitial(cexpr->args)) &&
+               equal(rightop, (Node *) lsecond(cexpr->args)) &&
+               rinfo->left_sortop == item1->sortop &&
+               rinfo->right_sortop == item1->sortop)
+           {
+               /*
+                * Yes, so find constant member(s) of set and generate
+                * implied LEFTVAR = CONSTANT
+                */
+               process_implied_const_eq(root, equi_key_set, relids,
+                                        leftop,
+                                        rinfo->left_sortop,
+                                        rinfo->left_relids,
+                                        false);
+               /* ... and RIGHTVAR = CONSTANT */
+               process_implied_const_eq(root, equi_key_set, relids,
+                                        rightop,
+                                        rinfo->right_sortop,
+                                        rinfo->right_relids,
+                                        false);
+               /* ... and remove COALESCE() = CONSTANT */
+               process_implied_const_eq(root, equi_key_set, relids,
+                                        item1->key,
+                                        item1->sortop,
+                                        relids[i1],
+                                        true);
+               /*
+                * We can remove the explicit outer join qual, too,
+                * since we now have tests forcing each of its sides
+                * to the same value.
+                */
+               process_implied_equality(root,
+                                        leftop,
+                                        rightop,
+                                        rinfo->left_sortop,
+                                        rinfo->right_sortop,
+                                        rinfo->left_relids,
+                                        rinfo->right_relids,
+                                        true);
+
+               /* No need to match against remaining set members */
+               break;
+           }
+           i1++;
+       }
+   }
+}
+
+/*
+ * process_implied_const_eq
+ *   Apply process_implied_equality with the given item and each
+ *   pseudoconstant member of equi_key_set.
+ *
+ * This is just a subroutine to save some cruft in
+ * generate_outer_join_implications.  equi_key_set and relids are as in
+ * generate_outer_join_implications, the other parameters as for
+ * process_implied_equality.
+ */
+static void
+process_implied_const_eq(PlannerInfo *root, List *equi_key_set, Relids *relids,
+                        Node *item1, Oid sortop1, Relids item1_relids,
+                        bool delete_it)
+{
+   ListCell   *l;
+   bool        found = false;
+   int         i = 0;
+
+   foreach(l, equi_key_set)
+   {
+       PathKeyItem *item2 = (PathKeyItem *) lfirst(l);
+
+       if (bms_is_empty(relids[i]))
+       {
+           process_implied_equality(root,
+                                    item1, item2->key,
+                                    sortop1, item2->sortop,
+                                    item1_relids, NULL,
+                                    delete_it);
+           found = true;
+       }
+       i++;
+   }
+   /* Caller screwed up if no constants in list */
+   Assert(found);
+}
+
 /*
  * exprs_known_equal
  *   Detect whether two expressions are known equal due to equijoin clauses.
index 76812e2e4b32b229321919165ebc0a7a0a317dc5..959c17206c082485d690fcf036c49b859d416ca0 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.192 2005/06/10 22:25:36 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.193 2005/07/02 23:00:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1176,8 +1176,7 @@ create_nestloop_plan(PlannerInfo *root,
            List       *bitmapclauses;
 
            bitmapclauses =
-               make_restrictinfo_from_bitmapqual(innerpath->bitmapqual,
-                                                 true, true);
+               make_restrictinfo_from_bitmapqual(innerpath->bitmapqual, true);
            joinrestrictclauses =
                select_nonredundant_join_clauses(root,
                                                 joinrestrictclauses,
index f7066e4906ce9e8f73d8109cfb969689c8d43e20..91a23d6d1c3e9ac95aabd1f8d4156b6111063a82 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.107 2005/06/09 04:18:59 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.108 2005/07/02 23:00:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -374,8 +374,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                        Relids qualscope)
 {
    Relids      relids;
-   bool        valid_everywhere;
-   bool        can_be_equijoin;
+   bool        maybe_equijoin;
+   bool        maybe_outer_join;
    RestrictInfo *restrictinfo;
    RelOptInfo *rel;
    List       *vars;
@@ -409,14 +409,15 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
    if (isdeduced)
    {
        /*
-        * If the qual came from implied-equality deduction, we can
-        * evaluate the qual at its natural semantic level.  It is not
-        * affected by any outer-join rules (else we'd not have decided
-        * the vars were equal).
+        * If the qual came from implied-equality deduction, we always
+        * evaluate the qual at its natural semantic level.  It is the
+        * responsibility of the deducer not to create any quals that
+        * should be delayed by outer-join rules.
         */
        Assert(bms_equal(relids, qualscope));
-       valid_everywhere = true;
-       can_be_equijoin = true;
+       /* Needn't feed it back for more deductions */
+       maybe_equijoin = false;
+       maybe_outer_join = false;
    }
    else if (bms_overlap(relids, outerjoin_nonnullable))
    {
@@ -434,8 +435,14 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
         * result, so we treat it the same as an ordinary inner-join qual.
         */
        relids = qualscope;
-       valid_everywhere = false;
-       can_be_equijoin = false;
+       /*
+        * 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.
+        */
+       maybe_equijoin = false;
+       maybe_outer_join = true;
    }
    else
    {
@@ -449,34 +456,25 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
         * time we are called, the outerjoinset of each baserel will show
         * exactly those outer joins that are below the qual in the join
         * tree.
-        *
-        * We also need to determine whether the qual is "valid everywhere",
-        * which is true if the qual mentions no variables that are
-        * involved in lower-level outer joins (this may be an overly
-        * strong test).
         */
        Relids      addrelids = NULL;
        Relids      tmprelids;
        int         relno;
 
-       valid_everywhere = true;
        tmprelids = bms_copy(relids);
        while ((relno = bms_first_member(tmprelids)) >= 0)
        {
            RelOptInfo *rel = find_base_rel(root, relno);
 
            if (rel->outerjoinset != NULL)
-           {
                addrelids = bms_add_members(addrelids, rel->outerjoinset);
-               valid_everywhere = false;
-           }
        }
        bms_free(tmprelids);
 
        if (bms_is_subset(addrelids, relids))
        {
            /* Qual is not affected by any outer-join restriction */
-           can_be_equijoin = true;
+           maybe_equijoin = true;
        }
        else
        {
@@ -488,9 +486,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
             * Because application of the qual will be delayed by outer
             * join, we mustn't assume its vars are equal everywhere.
             */
-           can_be_equijoin = false;
+           maybe_equijoin = false;
        }
        bms_free(addrelids);
+       maybe_outer_join = false;
    }
 
    /*
@@ -508,7 +507,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
     */
    restrictinfo = make_restrictinfo((Expr *) clause,
                                     is_pushed_down,
-                                    valid_everywhere,
                                     relids);
 
    /*
@@ -533,8 +531,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
             * allows us to consider z and q equal after their rels are
             * joined.
             */
-           if (can_be_equijoin)
-               check_mergejoinable(restrictinfo);
+           check_mergejoinable(restrictinfo);
 
            /*
             * If the clause was deduced from implied equality, check to
@@ -601,18 +598,60 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
    }
 
    /*
-    * If the clause has a mergejoinable operator, and is not an
-    * outer-join qualification nor bubbled up due to an outer join, then
-    * the two sides represent equivalent PathKeyItems for path keys: any
-    * path that is sorted by one side will also be sorted by the other
-    * (as soon as the two rels are joined, that is).  Record the key
-    * equivalence for future use.  (We can skip this for a deduced
-    * clause, since the keys are already known equivalent in that case.)
+    * If the clause has a mergejoinable operator, we may be able to
+    * deduce more things from it under the principle of transitivity.
+    *
+    * If it is not an outer-join qualification nor bubbled up due to an outer
+    * join, then the two sides represent equivalent PathKeyItems for path
+    * keys: any path that is sorted by one side will also be sorted by the
+    * other (as soon as the two rels are joined, that is).  Pass such clauses
+    * to add_equijoined_keys.
+    *
+    * If it is a left or right outer-join qualification that relates the two
+    * sides of the outer join (no funny business like leftvar1 = leftvar2 +
+    * rightvar), we add it to root->left_join_clauses or
+    * root->right_join_clauses according to which side the nonnullable
+    * variable appears on.
+    *
+    * If it is a full outer-join qualification, we add it to
+    * root->full_join_clauses.  (Ideally we'd discard cases that aren't
+    * leftvar = rightvar, as we do for left/right joins, but this routine
+    * doesn't have the info needed to do that; and the current usage of the
+    * full_join_clauses list doesn't require that, so it's not currently
+    * worth complicating this routine's API to make it possible.)
     */
-   if (can_be_equijoin &&
-       restrictinfo->mergejoinoperator != InvalidOid &&
-       !isdeduced)
-       add_equijoined_keys(root, restrictinfo);
+   if (restrictinfo->mergejoinoperator != InvalidOid)
+   {
+       if (maybe_equijoin)
+           add_equijoined_keys(root, restrictinfo);
+       else if (maybe_outer_join && restrictinfo->can_join)
+       {
+           if (bms_is_subset(restrictinfo->left_relids,
+                             outerjoin_nonnullable) &&
+               !bms_overlap(restrictinfo->right_relids,
+                            outerjoin_nonnullable))
+           {
+               /* we have outervar = innervar */
+               root->left_join_clauses = lappend(root->left_join_clauses,
+                                                 restrictinfo);
+           }
+           else if (bms_is_subset(restrictinfo->right_relids,
+                                  outerjoin_nonnullable) &&
+                    !bms_overlap(restrictinfo->left_relids,
+                                 outerjoin_nonnullable))
+           {
+               /* we have innervar = outervar */
+               root->right_join_clauses = lappend(root->right_join_clauses,
+                                                  restrictinfo);
+           }
+           else if (bms_equal(outerjoin_nonnullable, qualscope))
+           {
+               /* FULL JOIN (above tests cannot match in this case) */
+               root->full_join_clauses = lappend(root->full_join_clauses,
+                                                 restrictinfo);
+           }
+       }
+   }
 }
 
 /*
index 788fd2f3249e005d8fbd5188a7da18fd3f9ee75c..7038a45ac64aac657f336cd694924ca7224cb77d 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.85 2005/06/10 03:32:23 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.86 2005/07/02 23:00:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -121,6 +121,9 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
    root->join_rel_list = NIL;
    root->join_rel_hash = NULL;
    root->equi_key_list = NIL;
+   root->left_join_clauses = NIL;
+   root->right_join_clauses = NIL;
+   root->full_join_clauses = NIL;
 
    /*
     * Construct RelOptInfo nodes for all base relations in query.
index df8d0556b42cfdc9dbbce31abe716811d8664769..334f8504dff2b0c4e4cb04e2c913b80b04190386 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.189 2005/06/10 02:21:04 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.190 2005/07/02 23:00:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -194,7 +194,6 @@ subquery_planner(Query *parse, double tuple_fraction,
    int         saved_planid = PlannerPlanId;
    PlannerInfo *root;
    Plan       *plan;
-   bool        hasOuterJoins;
    List       *newHaving;
    List       *lst;
    ListCell   *l;
@@ -228,12 +227,16 @@ subquery_planner(Query *parse, double tuple_fraction,
    /*
     * Detect whether any rangetable entries are RTE_JOIN kind; if not, we
     * can avoid the expense of doing flatten_join_alias_vars().  Also
-    * check for outer joins --- if none, we can skip
-    * reduce_outer_joins(). This must be done after we have done
+    * check for outer joins --- if none, we can skip reduce_outer_joins()
+    * and some other processing.  This must be done after we have done
     * pull_up_subqueries, of course.
+    *
+    * Note: if reduce_outer_joins manages to eliminate all outer joins,
+    * root->hasOuterJoins is not reset currently.  This is OK since its
+    * purpose is merely to suppress unnecessary processing in simple cases.
     */
    root->hasJoinRTEs = false;
-   hasOuterJoins = false;
+   root->hasOuterJoins = false;
    foreach(l, parse->rtable)
    {
        RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
@@ -243,7 +246,7 @@ subquery_planner(Query *parse, double tuple_fraction,
            root->hasJoinRTEs = true;
            if (IS_OUTER_JOIN(rte->jointype))
            {
-               hasOuterJoins = true;
+               root->hasOuterJoins = true;
                /* Can quit scanning once we find an outer join */
                break;
            }
@@ -347,7 +350,7 @@ subquery_planner(Query *parse, double tuple_fraction,
     * joins. This step is most easily done after we've done expression
     * preprocessing.
     */
-   if (hasOuterJoins)
+   if (root->hasOuterJoins)
        reduce_outer_joins(root);
 
    /*
index a76f1bb7a2e44f4bee9cc3075ac1567bdd272401..ba3d82dd30756c176622c1d0439170409ceb98e0 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.37 2005/06/09 04:19:00 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.38 2005/07/02 23:00:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 static RestrictInfo *make_restrictinfo_internal(Expr *clause,
                           Expr *orclause,
                           bool is_pushed_down,
-                          bool valid_everywhere,
                           Relids required_relids);
 static Expr *make_sub_restrictinfos(Expr *clause,
-                      bool is_pushed_down,
-                      bool valid_everywhere);
+                      bool is_pushed_down);
 static RestrictInfo *join_clause_is_redundant(PlannerInfo *root,
                         RestrictInfo *rinfo,
                         List *reference_list,
@@ -40,8 +38,8 @@ static RestrictInfo *join_clause_is_redundant(PlannerInfo *root,
  *
  * Build a RestrictInfo node containing the given subexpression.
  *
- * The is_pushed_down and valid_everywhere flags must be supplied by the
- * caller.  required_relids can be NULL, in which case it defaults to the
+ * The is_pushed_down flag must be supplied by the caller.
+ * required_relids can be NULL, in which case it defaults to the
  * actual clause contents (i.e., clause_relids).
  *
  * We initialize fields that depend only on the given subexpression, leaving
@@ -49,23 +47,19 @@ static RestrictInfo *join_clause_is_redundant(PlannerInfo *root,
  * later.
  */
 RestrictInfo *
-make_restrictinfo(Expr *clause, bool is_pushed_down, bool valid_everywhere,
-                 Relids required_relids)
+make_restrictinfo(Expr *clause, bool is_pushed_down, Relids required_relids)
 {
    /*
     * If it's an OR clause, build a modified copy with RestrictInfos
     * inserted above each subclause of the top-level AND/OR structure.
     */
    if (or_clause((Node *) clause))
-       return (RestrictInfo *) make_sub_restrictinfos(clause,
-                                                      is_pushed_down,
-                                                      valid_everywhere);
+       return (RestrictInfo *) make_sub_restrictinfos(clause, is_pushed_down);
 
    /* Shouldn't be an AND clause, else AND/OR flattening messed up */
    Assert(!and_clause((Node *) clause));
 
-   return make_restrictinfo_internal(clause, NULL,
-                                     is_pushed_down, valid_everywhere,
+   return make_restrictinfo_internal(clause, NULL, is_pushed_down,
                                      required_relids);
 }
 
@@ -84,9 +78,7 @@ make_restrictinfo(Expr *clause, bool is_pushed_down, bool valid_everywhere,
  * a specialized routine to allow sharing of RestrictInfos.
  */
 List *
-make_restrictinfo_from_bitmapqual(Path *bitmapqual,
-                                 bool is_pushed_down,
-                                 bool valid_everywhere)
+make_restrictinfo_from_bitmapqual(Path *bitmapqual, bool is_pushed_down)
 {
    List       *result;
 
@@ -101,8 +93,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual,
            List       *sublist;
 
            sublist = make_restrictinfo_from_bitmapqual((Path *) lfirst(l),
-                                                       is_pushed_down,
-                                                       valid_everywhere);
+                                                       is_pushed_down);
            result = list_concat(result, sublist);
        }
    }
@@ -118,8 +109,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual,
            List       *sublist;
 
            sublist = make_restrictinfo_from_bitmapqual((Path *) lfirst(l),
-                                                       is_pushed_down,
-                                                       valid_everywhere);
+                                                       is_pushed_down);
            if (sublist == NIL)
            {
                /* constant TRUE input yields constant TRUE OR result */
@@ -137,7 +127,6 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual,
            list_make1(make_restrictinfo_internal(make_orclause(withoutris),
                                                  make_orclause(withris),
                                                  is_pushed_down,
-                                                 valid_everywhere,
                                                  NULL));
    }
    else if (IsA(bitmapqual, IndexPath))
@@ -162,15 +151,13 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual,
  */
 static RestrictInfo *
 make_restrictinfo_internal(Expr *clause, Expr *orclause,
-                          bool is_pushed_down, bool valid_everywhere,
-                          Relids required_relids)
+                          bool is_pushed_down, Relids required_relids)
 {
    RestrictInfo *restrictinfo = makeNode(RestrictInfo);
 
    restrictinfo->clause = clause;
    restrictinfo->orclause = orclause;
    restrictinfo->is_pushed_down = is_pushed_down;
-   restrictinfo->valid_everywhere = valid_everywhere;
    restrictinfo->can_join = false;     /* may get set below */
 
    /*
@@ -250,8 +237,7 @@ make_restrictinfo_internal(Expr *clause, Expr *orclause,
  * simple clauses are valid RestrictInfos.
  */
 static Expr *
-make_sub_restrictinfos(Expr *clause, bool is_pushed_down,
-                      bool valid_everywhere)
+make_sub_restrictinfos(Expr *clause, bool is_pushed_down)
 {
    if (or_clause((Node *) clause))
    {
@@ -261,12 +247,10 @@ make_sub_restrictinfos(Expr *clause, bool is_pushed_down,
        foreach(temp, ((BoolExpr *) clause)->args)
            orlist = lappend(orlist,
                             make_sub_restrictinfos(lfirst(temp),
-                                                   is_pushed_down,
-                                                   valid_everywhere));
+                                                   is_pushed_down));
        return (Expr *) make_restrictinfo_internal(clause,
                                                   make_orclause(orlist),
                                                   is_pushed_down,
-                                                  valid_everywhere,
                                                   NULL);
    }
    else if (and_clause((Node *) clause))
@@ -277,15 +261,13 @@ make_sub_restrictinfos(Expr *clause, bool is_pushed_down,
        foreach(temp, ((BoolExpr *) clause)->args)
            andlist = lappend(andlist,
                              make_sub_restrictinfos(lfirst(temp),
-                                                    is_pushed_down,
-                                                    valid_everywhere));
+                                                    is_pushed_down));
        return make_andclause(andlist);
    }
    else
        return (Expr *) make_restrictinfo_internal(clause,
                                                   NULL,
                                                   is_pushed_down,
-                                                  valid_everywhere,
                                                   NULL);
 }
 
index 0a2ca0e5f33fd957333a35299ceb8d6e8bc2723e..2f906c6a47297ac15a9b69a2ec2c679923023356 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/nodes/relation.h,v 1.115 2005/06/13 23:14:49 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.116 2005/07/02 23:00:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -87,6 +87,15 @@ typedef struct PlannerInfo
    List       *equi_key_list;  /* list of lists of equijoined
                                 * PathKeyItems */
 
+   List       *left_join_clauses;  /* list of RestrictInfos for outer join
+                                    * clauses w/nonnullable var on left */
+
+   List       *right_join_clauses; /* list of RestrictInfos for outer join
+                                    * clauses w/nonnullable var on right */
+
+   List       *full_join_clauses;  /* list of RestrictInfos for full outer
+                                    * join clauses */
+
    List       *in_info_list;   /* list of InClauseInfos */
 
    List       *query_pathkeys; /* desired pathkeys for query_planner(),
@@ -95,6 +104,7 @@ typedef struct PlannerInfo
    double      tuple_fraction; /* tuple_fraction passed to query_planner */
 
    bool        hasJoinRTEs;    /* true if any RTEs are RTE_JOIN kind */
+   bool        hasOuterJoins;  /* true if any RTEs are outer joins */
    bool        hasHavingQual;  /* true if havingQual was non-null */
 } PlannerInfo;
 
@@ -695,10 +705,6 @@ typedef struct HashPath
  * joined, will also have is_pushed_down set because it will get attached to
  * some lower joinrel.
  *
- * We also store a valid_everywhere flag, which says that the clause is not
- * affected by any lower-level outer join, and therefore any conditions it
- * asserts can be presumed true throughout the plan tree.
- *
  * In general, the referenced clause might be arbitrarily complex. The
  * kinds of clauses we can handle as indexscan quals, mergejoin clauses,
  * or hashjoin clauses are fairly limited --- the code for each kind of
@@ -725,8 +731,6 @@ typedef struct RestrictInfo
 
    bool        is_pushed_down; /* TRUE if clause was pushed down in level */
 
-   bool        valid_everywhere;       /* TRUE if valid on every level */
-
    /*
     * This flag is set true if the clause looks potentially useful as a
     * merge or hash join clause, that is if it is a binary opclause with
index 20f51c582045fe8fa560aec5455b5e1cd40758fc..5a9c2f2722488478ae3c951555441dec1ff9a3c9 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/restrictinfo.h,v 1.31 2005/06/09 04:19:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/restrictinfo.h,v 1.32 2005/07/02 23:00:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 extern RestrictInfo *make_restrictinfo(Expr *clause,
                                       bool is_pushed_down,
-                                      bool valid_everywhere,
                                       Relids required_relids);
 extern List *make_restrictinfo_from_bitmapqual(Path *bitmapqual,
-                                              bool is_pushed_down,
-                                              bool valid_everywhere);
+                                              bool is_pushed_down);
 extern bool restriction_is_or_clause(RestrictInfo *restrictinfo);
 extern List *get_actual_clauses(List *restrictinfo_list);
 extern void get_actual_join_clauses(List *restrictinfo_list,