]> granicus.if.org Git - postgresql/commitdiff
Teach planner how to rearrange join order for some classes of OUTER JOIN.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 20 Dec 2005 02:30:36 +0000 (02:30 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 20 Dec 2005 02:30:36 +0000 (02:30 +0000)
Per my recent proposal.  I ended up basing the implementation on the
existing mechanism for enforcing valid join orders of IN joins --- the
rules for valid outer-join orders are somewhat similar.

23 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/perform.sgml
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/optimizer/README
src/backend/optimizer/geqo/geqo_eval.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/joinrels.c
src/backend/optimizer/plan/initsplan.c
src/backend/optimizer/plan/planmain.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/relnode.c
src/backend/utils/misc/guc.c
src/include/nodes/nodes.h
src/include/nodes/primnodes.h
src/include/nodes/relation.h
src/include/optimizer/clauses.h
src/include/optimizer/paths.h
src/include/optimizer/planmain.h
src/include/optimizer/prep.h

index 2d1e50818346cd54c5a0fa92a54b6f47de7dbc92..8322463cea783d6eb43ca057d458eb38a8370b8e 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.38 2005/12/09 15:51:13 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.39 2005/12/20 02:30:35 tgl Exp $
 -->
 <chapter Id="runtime-config">
   <title>Server Configuration</title>
@@ -2028,6 +2028,7 @@ SELECT * FROM parent WHERE key = 2400;
         this many items.  Smaller values reduce planning time but may
         yield inferior query plans.  The default is 8.  It is usually
         wise to keep this less than <xref linkend="guc-geqo-threshold">.
+        For more information see <xref linkend="explicit-joins">.
        </para>
       </listitem>
      </varlistentry>
@@ -2039,48 +2040,24 @@ SELECT * FROM parent WHERE key = 2400;
       </indexterm>
       <listitem>
        <para>
-        The planner will rewrite explicit inner <literal>JOIN</>
-        constructs into lists of <literal>FROM</> items whenever a
-        list of no more than this many items in total would
-        result. Prior to <productname>PostgreSQL</> 7.4, joins
-        specified via the <literal>JOIN</literal> construct would
-        never be reordered by the query planner. The query planner has
-        subsequently been improved so that inner joins written in this
-        form can be reordered; this configuration parameter controls
-        the extent to which this reordering is performed.
-        <note>
-         <para>
-          At present, the order of outer joins specified via the
-          <literal>JOIN</> construct is never adjusted by the query
-          planner; therefore, <varname>join_collapse_limit</> has no
-          effect on this behavior. The planner may be improved to
-          reorder some classes of outer joins in a future release of
-          <productname>PostgreSQL</productname>.
-         </para>
-        </note>
+        The planner will rewrite explicit <literal>JOIN</>
+        constructs (except <literal>FULL JOIN</>s) into lists of
+        <literal>FROM</> items whenever a list of no more than this many items
+        would result.  Smaller values reduce planning time but may
+        yield inferior query plans.
        </para>
 
        <para>
         By default, this variable is set the same as
         <varname>from_collapse_limit</varname>, which is appropriate
         for most uses. Setting it to 1 prevents any reordering of
-        inner <literal>JOIN</>s. Thus, the explicit join order
+        explicit <literal>JOIN</>s. Thus, the explicit join order
         specified in the query will be the actual order in which the
         relations are joined. The query planner does not always choose
         the optimal join order; advanced users may elect to
         temporarily set this variable to 1, and then specify the join
-        order they desire explicitly. Another consequence of setting
-        this variable to 1 is that the query planner will behave more
-        like the <productname>PostgreSQL</productname> 7.3 query
-        planner, which some users might find useful for backward
-        compatibility reasons.
-       </para>
-
-       <para>
-        Setting this variable to a value between 1 and
-        <varname>from_collapse_limit</varname> might be useful to
-        trade off planning time against the quality of the chosen plan
-        (higher values produce better plans).
+        order they desire explicitly.
+        For more information see <xref linkend="explicit-joins">.
        </para>
       </listitem>
      </varlistentry>
index 53fa8210f8309d70c10a7005c500504d37afe75f..9632fc9a496930eb4cdcb4204d66cf8c5e12d718 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/perform.sgml,v 1.54 2005/11/04 23:14:00 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/perform.sgml,v 1.55 2005/12/20 02:30:35 tgl Exp $
 -->
 
  <chapter id="performance-tips">
@@ -627,7 +627,7 @@ SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
   </para>
 
   <para>
-   When the query involves outer joins, the planner has much less freedom
+   When the query involves outer joins, the planner has less freedom
    than it does for plain (inner) joins. For example, consider
 <programlisting>
 SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
@@ -637,16 +637,30 @@ SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
    emitted for each row of A that has no matching row in the join of B and C.
    Therefore the planner has no choice of join order here: it must join
    B to C and then join A to that result.  Accordingly, this query takes
-   less time to plan than the previous query.
+   less time to plan than the previous query.  In other cases, the planner
+   may be able to determine that more than one join order is safe.
+   For example, given
+<programlisting>
+SELECT * FROM a LEFT JOIN b ON (a.bid = b.id) LEFT JOIN c ON (a.cid = c.id);
+</programlisting>
+   it is valid to join A to either B or C first.  Currently, only
+   <literal>FULL JOIN</> completely constrains the join order.  Most
+   practical cases involving <literal>LEFT JOIN</> or <literal>RIGHT JOIN</>
+   can be rearranged to some extent.
   </para>
 
   <para>
    Explicit inner join syntax (<literal>INNER JOIN</>, <literal>CROSS
    JOIN</>, or unadorned <literal>JOIN</>) is semantically the same as
-   listing the input relations in <literal>FROM</>, so it does not need to
-   constrain the join order.  But it is possible to instruct the
-   <productname>PostgreSQL</productname> query planner to treat
-   explicit inner <literal>JOIN</>s as constraining the join order anyway.
+   listing the input relations in <literal>FROM</>, so it does not
+   constrain the join order.
+  </para>
+
+  <para>
+   Even though most kinds of <literal>JOIN</> don't completely constrain
+   the join order, it is possible to instruct the
+   <productname>PostgreSQL</productname> query planner to treat all
+   <literal>JOIN</> clauses as constraining the join order anyway.
    For example, these three queries are logically equivalent:
 <programlisting>
 SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
@@ -660,7 +674,8 @@ SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
   </para>
 
   <para>
-   To force the planner to follow the <literal>JOIN</> order for inner joins,
+   To force the planner to follow the join order laid out by explicit
+   <literal>JOIN</>s,
    set the <xref linkend="guc-join-collapse-limit"> run-time parameter to 1.
    (Other possible values are discussed below.)
   </para>
@@ -697,9 +712,9 @@ FROM x, y,
 WHERE somethingelse;
 </programlisting>
    This situation might arise from use of a view that contains a join;
-   the view's <literal>SELECT</> rule will be inserted in place of the view reference,
-   yielding a query much like the above.  Normally, the planner will try
-   to collapse the subquery into the parent, yielding
+   the view's <literal>SELECT</> rule will be inserted in place of the view
+   reference, yielding a query much like the above.  Normally, the planner
+   will try to collapse the subquery into the parent, yielding
 <programlisting>
 SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;
 </programlisting>
@@ -722,12 +737,12 @@ SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;
    linkend="guc-join-collapse-limit">
    are similarly named because they do almost the same thing: one controls
    when the planner will <quote>flatten out</> subselects, and the
-   other controls when it will flatten out explicit inner joins.  Typically
+   other controls when it will flatten out explicit joins.  Typically
    you would either set <varname>join_collapse_limit</> equal to
    <varname>from_collapse_limit</> (so that explicit joins and subselects
    act similarly) or set <varname>join_collapse_limit</> to 1 (if you want
    to control join order with explicit joins).  But you might set them
-   differently if you are trying to fine-tune the trade off between planning
+   differently if you are trying to fine-tune the trade-off between planning
    time and run time.
   </para>
  </sect1>
index 7d708e3fb1dc16b2cb3f56d5a1e43633d1cb4f4c..1d816ead3a24fa52d97077c95fdffeea55b1058b 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.322 2005/11/26 22:14:56 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.323 2005/12/20 02:30:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1277,6 +1277,22 @@ _copyRestrictInfo(RestrictInfo *from)
        return newnode;
 }
 
+/*
+ * _copyOuterJoinInfo
+ */
+static OuterJoinInfo *
+_copyOuterJoinInfo(OuterJoinInfo *from)
+{
+       OuterJoinInfo *newnode = makeNode(OuterJoinInfo);
+
+       COPY_BITMAPSET_FIELD(min_lefthand);
+       COPY_BITMAPSET_FIELD(min_righthand);
+       COPY_SCALAR_FIELD(is_full_join);
+       COPY_SCALAR_FIELD(lhs_strict);
+
+       return newnode;
+}
+
 /*
  * _copyInClauseInfo
  */
@@ -2906,6 +2922,9 @@ copyObject(void *from)
                case T_RestrictInfo:
                        retval = _copyRestrictInfo(from);
                        break;
+               case T_OuterJoinInfo:
+                       retval = _copyOuterJoinInfo(from);
+                       break;
                case T_InClauseInfo:
                        retval = _copyInClauseInfo(from);
                        break;
index 91d54b462c4fe779acfefe72f7d5022b11cb165b..824a7ff82c34ba36d237fe86de575003e15765e8 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.258 2005/11/22 18:17:11 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.259 2005/12/20 02:30:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -613,6 +613,17 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b)
        return true;
 }
 
+static bool
+_equalOuterJoinInfo(OuterJoinInfo *a, OuterJoinInfo *b)
+{
+       COMPARE_BITMAPSET_FIELD(min_lefthand);
+       COMPARE_BITMAPSET_FIELD(min_righthand);
+       COMPARE_SCALAR_FIELD(is_full_join);
+       COMPARE_SCALAR_FIELD(lhs_strict);
+
+       return true;
+}
+
 static bool
 _equalInClauseInfo(InClauseInfo *a, InClauseInfo *b)
 {
@@ -1954,6 +1965,9 @@ equal(void *a, void *b)
                case T_RestrictInfo:
                        retval = _equalRestrictInfo(a, b);
                        break;
+               case T_OuterJoinInfo:
+                       retval = _equalOuterJoinInfo(a, b);
+                       break;
                case T_InClauseInfo:
                        retval = _equalInClauseInfo(a, b);
                        break;
index 646ac6daabf63b7f03e574f31506b3ba89880aa6..aa5fd99db86436eab4b60e8af3f7453a3f40359e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.264 2005/11/28 04:35:30 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.265 2005/12/20 02:30:35 tgl Exp $
  *
  * NOTES
  *       Every node type that can appear in stored rules' parsetrees *must*
@@ -1167,6 +1167,7 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
        WRITE_NODE_FIELD(left_join_clauses);
        WRITE_NODE_FIELD(right_join_clauses);
        WRITE_NODE_FIELD(full_join_clauses);
+       WRITE_NODE_FIELD(oj_info_list);
        WRITE_NODE_FIELD(in_info_list);
        WRITE_NODE_FIELD(query_pathkeys);
        WRITE_NODE_FIELD(group_pathkeys);
@@ -1201,7 +1202,6 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node)
        WRITE_FLOAT_FIELD(tuples, "%.0f");
        WRITE_NODE_FIELD(subplan);
        WRITE_NODE_FIELD(baserestrictinfo);
-       WRITE_BITMAPSET_FIELD(outerjoinset);
        WRITE_NODE_FIELD(joininfo);
        WRITE_BITMAPSET_FIELD(index_outer_relids);
        WRITE_NODE_FIELD(index_inner_paths);
@@ -1265,6 +1265,17 @@ _outInnerIndexscanInfo(StringInfo str, InnerIndexscanInfo *node)
        WRITE_NODE_FIELD(best_innerpath);
 }
 
+static void
+_outOuterJoinInfo(StringInfo str, OuterJoinInfo *node)
+{
+       WRITE_NODE_TYPE("OUTERJOININFO");
+
+       WRITE_BITMAPSET_FIELD(min_lefthand);
+       WRITE_BITMAPSET_FIELD(min_righthand);
+       WRITE_BOOL_FIELD(is_full_join);
+       WRITE_BOOL_FIELD(lhs_strict);
+}
+
 static void
 _outInClauseInfo(StringInfo str, InClauseInfo *node)
 {
@@ -2019,6 +2030,9 @@ _outNode(StringInfo str, void *obj)
                        case T_InnerIndexscanInfo:
                                _outInnerIndexscanInfo(str, obj);
                                break;
+                       case T_OuterJoinInfo:
+                               _outOuterJoinInfo(str, obj);
+                               break;
                        case T_InClauseInfo:
                                _outInClauseInfo(str, obj);
                                break;
index b19f6118ff184f6b1beedd66fdbc6459323d7ac4..df9828b21505500e7ed240367607fafcae985e2e 100644 (file)
@@ -40,10 +40,11 @@ is derived from the cheapest Path for the RelOptInfo that includes all the
 base rels of the query.
 
 Possible Paths for a primitive table relation include plain old sequential
-scan, plus index scans for any indexes that exist on the table.  A subquery
-base relation just has one Path, a "SubqueryScan" path (which links to the
-subplan that was built by a recursive invocation of the planner).  Likewise
-a function-RTE base relation has only one possible Path.
+scan, plus index scans for any indexes that exist on the table, plus bitmap
+index scans using one or more indexes.  A subquery base relation just has
+one Path, a "SubqueryScan" path (which links to the subplan that was built
+by a recursive invocation of the planner).  Likewise a function-RTE base
+relation has only one possible Path.
 
 Joins always occur using two RelOptInfos.  One is outer, the other inner.
 Outers drive lookups of values in the inner.  In a nested loop, lookups of
@@ -84,20 +85,26 @@ If we have only a single base relation in the query, we are done.
 Otherwise we have to figure out how to join the base relations into a
 single join relation.
 
-2) If the query's FROM clause contains explicit JOIN clauses, we join
-those pairs of relations in exactly the tree structure indicated by the
-JOIN clauses.  (This is absolutely necessary when dealing with outer JOINs.
-For inner JOINs we have more flexibility in theory, but don't currently
-exploit it in practice.)  For each such join pair, we generate a Path
-for each feasible join method, and select the cheapest Path.  Note that
-the JOIN clause structure determines the join Path structure, but it
-doesn't constrain the join implementation method at each join (nestloop,
-merge, hash), nor does it say which rel is considered outer or inner at
-each join.  We consider all these possibilities in building Paths.
+2) Normally, any explicit JOIN clauses are "flattened" so that we just
+have a list of relations to join.  However, FULL OUTER JOIN clauses are
+never flattened, and other kinds of JOIN might not be either, if the
+flattening process is stopped by join_collapse_limit or from_collapse_limit
+restrictions.  Therefore, we end up with a planning problem that contains
+both lists of relations to be joined in any order, and JOIN nodes that
+force a particular join order.  For each un-flattened JOIN node, we join
+exactly that pair of relations (after recursively planning their inputs,
+if the inputs aren't single base relations).  We generate a Path for each
+feasible join method, and select the cheapest Path.  Note that the JOIN
+clause structure determines the join Path structure, but it doesn't
+constrain the join implementation method at each join (nestloop, merge,
+hash), nor does it say which rel is considered outer or inner at each
+join.  We consider all these possibilities in building Paths.
 
 3) At the top level of the FROM clause we will have a list of relations
-that are either base rels or joinrels constructed per JOIN directives.
-We can join these rels together in any order the planner sees fit.
+that are either base rels or joinrels constructed per un-flattened JOIN
+directives.  (This is also the situation, recursively, when we can flatten
+sub-joins underneath an un-flattenable JOIN into a list of relations to
+join.)  We can join these rels together in any order the planner sees fit.
 The standard (non-GEQO) planner does this as follows:
 
 Consider joining each RelOptInfo to each other RelOptInfo specified in its
@@ -156,12 +163,76 @@ joining {1 2 3} to {4} (left-handed), {4} to {1 2 3} (right-handed), and
 scanning code produces these potential join combinations one at a time,
 all the ways to produce the same set of joined base rels will share the
 same RelOptInfo, so the paths produced from different join combinations
-that produce equivalent joinrels will compete in add_path.
+that produce equivalent joinrels will compete in add_path().
 
 Once we have built the final join rel, we use either the cheapest path
 for it or the cheapest path with the desired ordering (if that's cheaper
 than applying a sort to the cheapest other path).
 
+If the query contains one-sided outer joins (LEFT or RIGHT joins), or
+"IN (sub-select)" WHERE clauses that were converted to joins, then some of
+the possible join orders may be illegal.  These are excluded by having
+make_join_rel consult side lists of outer joins and IN joins to see
+whether a proposed join is illegal.  (The same consultation allows it
+to see which join style should be applied for a valid join, ie,
+JOIN_INNER, JOIN_LEFT, etc.)
+
+
+Valid OUTER JOIN optimizations
+------------------------------
+
+The planner's treatment of outer join reordering is based on the following
+identities:
+
+1.     (A leftjoin B on (Pab)) innerjoin C on (Pac)
+       = (A innerjoin C on (Pac)) leftjoin B on (Pab)
+
+where Pac is a predicate referencing A and C, etc (in this case, clearly
+Pac cannot reference B, or the transformation is nonsensical).
+
+2.     (A leftjoin B on (Pab)) leftjoin C on (Pac)
+       = (A leftjoin C on (Pac)) leftjoin B on (Pab)
+
+3.     (A leftjoin B on (Pab)) leftjoin C on (Pbc)
+       = A leftjoin (B leftjoin C on (Pbc)) on (Pab)
+
+Identity 3 only holds if predicate Pbc must fail for all-null B rows
+(that is, Pbc is strict for at least one column of B).  If Pbc is not
+strict, the first form might produce some rows with nonnull C columns
+where the second form would make those entries null.
+
+RIGHT JOIN is equivalent to LEFT JOIN after switching the two input
+tables, so the same identities work for right joins.  Only FULL JOIN
+cannot be re-ordered at all.
+
+An example of a case that does *not* work is moving an innerjoin into or
+out of the nullable side of an outer join:
+
+       A leftjoin (B join C on (Pbc)) on (Pab)
+       != (A leftjoin B on (Pab)) join C on (Pbc)
+
+FULL JOIN ordering is enforced by not collapsing FULL JOIN nodes when
+translating the jointree to "joinlist" representation.  LEFT and RIGHT
+JOIN nodes are normally collapsed so that they participate fully in the
+join order search.  To avoid generating illegal join orders, the planner
+creates an OuterJoinInfo node for each outer join, and make_join_rel
+checks this list to decide if a proposed join is legal.
+
+What we store in OuterJoinInfo nodes are the minimum sets of Relids
+required on each side of the join to form the outer join.  Note that
+these are minimums; there's no explicit maximum, since joining other
+rels to the OJ's syntactic rels may be legal.  Per identities 1 and 2,
+non-FULL joins can be freely associated into the lefthand side of an
+OJ, but in general they can't be associated into the righthand side.
+So the restriction enforced by make_join_rel is that a proposed join
+can't join across a RHS boundary (ie, join anything inside the RHS
+to anything else) unless the join validly implements some outer join.
+(To support use of identity 3, we have to allow cases where an apparent
+violation of a lower OJ's RHS is committed while forming an upper OJ.
+If this wouldn't in fact be legal, the upper OJ's minimum LHS or RHS
+set must be expanded to include the whole of the lower OJ, thereby
+preventing it from being formed before the lower OJ is.)
+
 
 Pulling up subqueries
 ---------------------
@@ -180,13 +251,13 @@ of the join tree.  Each FROM-list is planned using the dynamic-programming
 search method described above.
 
 If pulling up a subquery produces a FROM-list as a direct child of another
-FROM-list (with no explicit JOIN directives between), then we can merge the
-two FROM-lists together.  Once that's done, the subquery is an absolutely
-integral part of the outer query and will not constrain the join tree
-search space at all.  However, that could result in unpleasant growth of
-planning time, since the dynamic-programming search has runtime exponential
-in the number of FROM-items considered.  Therefore, we don't merge
-FROM-lists if the result would have too many FROM-items in one list.
+FROM-list, then we can merge the two FROM-lists together.  Once that's
+done, the subquery is an absolutely integral part of the outer query and
+will not constrain the join tree search space at all.  However, that could
+result in unpleasant growth of planning time, since the dynamic-programming
+search has runtime exponential in the number of FROM-items considered.
+Therefore, we don't merge FROM-lists if the result would have too many
+FROM-items in one list.
 
 
 Optimizer Functions
index b6c859b7675d15cde9a95f51b1e2a1be9c3822c4..29a4390a28cc4651fa1b24ac5f6084e1236b36c7 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.78 2005/11/22 18:17:11 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.79 2005/12/20 02:30:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -216,12 +216,11 @@ gimme_tree(Gene *tour, int num_gene, GeqoEvalData *evaldata)
 
                        /*
                         * Construct a RelOptInfo representing the join of these two input
-                        * relations.  These are always inner joins. Note that we expect
-                        * the joinrel not to exist in root->join_rel_list yet, and so the
-                        * paths constructed for it will only include the ones we want.
+                        * relations.  Note that we expect the joinrel not to exist in
+                        * root->join_rel_list yet, and so the paths constructed for it
+                        * will only include the ones we want.
                         */
-                       joinrel = make_join_rel(evaldata->root, outer_rel, inner_rel,
-                                                                       JOIN_INNER);
+                       joinrel = make_join_rel(evaldata->root, outer_rel, inner_rel);
 
                        /* Can't pop stack here if join order is not valid */
                        if (!joinrel)
@@ -262,6 +261,20 @@ desirable_join(PlannerInfo *root,
        if (have_relevant_joinclause(outer_rel, inner_rel))
                return true;
 
+       /*
+        * Join if the rels are members of the same outer-join RHS. This is needed
+        * to improve the odds that we will find a valid solution in a case where
+        * an OJ RHS has a clauseless join.
+        */
+       foreach(l, root->oj_info_list)
+       {
+               OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
+
+               if (bms_is_subset(outer_rel->relids, ojinfo->min_righthand) &&
+                       bms_is_subset(inner_rel->relids, ojinfo->min_righthand))
+                       return true;
+       }
+
        /*
         * Join if the rels are members of the same IN sub-select.      This is needed
         * to improve the odds that we will find a valid solution in a case where
index 1a0ff1ac209f5414c7aa89b52542983e73ad6ba3..19b1cfcaad496cd59cadfea4fd4a4a3230ae2387 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.138 2005/11/22 18:17:12 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.139 2005/12/20 02:30:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,6 +51,7 @@ static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                          Index rti, RangeTblEntry *rte);
 static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
                                          RangeTblEntry *rte);
+static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
 static RelOptInfo *make_one_rel_by_joins(PlannerInfo *root, int levels_needed,
                                          List *initial_rels);
 static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery,
@@ -73,7 +74,7 @@ static void recurse_push_qual(Node *setOp, Query *topquery,
  *       single rel that represents the join of all base rels in the query.
  */
 RelOptInfo *
-make_one_rel(PlannerInfo *root)
+make_one_rel(PlannerInfo *root, List *joinlist)
 {
        RelOptInfo *rel;
 
@@ -85,10 +86,7 @@ make_one_rel(PlannerInfo *root)
        /*
         * Generate access paths for the entire join tree.
         */
-       Assert(root->parse->jointree != NULL &&
-                  IsA(root->parse->jointree, FromExpr));
-
-       rel = make_fromexpr_rel(root, root->parse->jointree);
+       rel = make_rel_from_joinlist(root, joinlist);
 
        /*
         * The result should join all and only the query's base rels.
@@ -528,43 +526,65 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 }
 
 /*
- * make_fromexpr_rel
- *       Build access paths for a FromExpr jointree node.
+ * make_rel_from_joinlist
+ *       Build access paths using a "joinlist" to guide the join path search.
+ *
+ * See comments for deconstruct_jointree() for definition of the joinlist
+ * data structure.
  */
-RelOptInfo *
-make_fromexpr_rel(PlannerInfo *root, FromExpr *from)
+static RelOptInfo *
+make_rel_from_joinlist(PlannerInfo *root, List *joinlist)
 {
        int                     levels_needed;
-       List       *initial_rels = NIL;
-       ListCell   *jt;
+       List       *initial_rels;
+       ListCell   *jl;
 
        /*
-        * Count the number of child jointree nodes.  This is the depth of the
+        * Count the number of child joinlist nodes.  This is the depth of the
         * dynamic-programming algorithm we must employ to consider all ways of
         * joining the child nodes.
         */
-       levels_needed = list_length(from->fromlist);
+       levels_needed = list_length(joinlist);
 
        if (levels_needed <= 0)
                return NULL;                    /* nothing to do? */
 
        /*
-        * Construct a list of rels corresponding to the child jointree nodes.
+        * Construct a list of rels corresponding to the child joinlist nodes.
         * This may contain both base rels and rels constructed according to
-        * explicit JOIN directives.
+        * sub-joinlists.
         */
-       foreach(jt, from->fromlist)
+       initial_rels = NIL;
+       foreach(jl, joinlist)
        {
-               Node       *jtnode = (Node *) lfirst(jt);
+               Node       *jlnode = (Node *) lfirst(jl);
+               RelOptInfo *thisrel;
+
+               if (IsA(jlnode, RangeTblRef))
+               {
+                       int                     varno = ((RangeTblRef *) jlnode)->rtindex;
+
+                       thisrel = find_base_rel(root, varno);
+               }
+               else if (IsA(jlnode, List))
+               {
+                       /* Recurse to handle subproblem */
+                       thisrel = make_rel_from_joinlist(root, (List *) jlnode);
+               }
+               else
+               {
+                       elog(ERROR, "unrecognized joinlist node type: %d",
+                                (int) nodeTag(jlnode));
+                       thisrel = NULL;         /* keep compiler quiet */
+               }
 
-               initial_rels = lappend(initial_rels,
-                                                          make_jointree_rel(root, jtnode));
+               initial_rels = lappend(initial_rels, thisrel);
        }
 
        if (levels_needed == 1)
        {
                /*
-                * Single jointree node, so we're done.
+                * Single joinlist node, so we're done.
                 */
                return (RelOptInfo *) linitial(initial_rels);
        }
index 778b16761807d27c7ef8b5cb18d2fead81221bc1..b5762c97ba914790c095099e208535e0dda8d8c0 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.77 2005/11/22 18:17:12 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/path/joinrels.c,v 1.78 2005/12/20 02:30:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,7 +25,7 @@ static List *make_rels_by_clause_joins(PlannerInfo *root,
 static List *make_rels_by_clauseless_joins(PlannerInfo *root,
                                                          RelOptInfo *old_rel,
                                                          ListCell *other_rels);
-static bool is_inside_IN(PlannerInfo *root, RelOptInfo *rel);
+static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
 
 
 /*
@@ -86,15 +86,16 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels)
                                                                                                 other_rels);
 
                        /*
-                        * An exception occurs when there is a clauseless join inside an
-                        * IN (sub-SELECT) construct.  Here, the members of the subselect
-                        * all have join clauses (against the stuff outside the IN), but
-                        * they *must* be joined to each other before we can make use of
-                        * those join clauses.  So do the clauseless join bit.
+                        * An exception occurs when there is a clauseless join inside a
+                        * construct that restricts join order, i.e., an outer join RHS
+                        * or an IN (sub-SELECT) construct.  Here, the rel may well have
+                        * join clauses against stuff outside the OJ RHS or IN sub-SELECT,
+                        * but the clauseless join *must* be done before we can make use
+                        * of those join clauses.       So do the clauseless join bit.
                         *
                         * See also the last-ditch case below.
                         */
-                       if (new_rels == NIL && is_inside_IN(root, old_rel))
+                       if (new_rels == NIL && has_join_restriction(root, old_rel))
                                new_rels = make_rels_by_clauseless_joins(root,
                                                                                                                 old_rel,
                                                                                                                 other_rels);
@@ -169,8 +170,7 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels)
                                        {
                                                RelOptInfo *jrel;
 
-                                               jrel = make_join_rel(root, old_rel, new_rel,
-                                                                                        JOIN_INNER);
+                                               jrel = make_join_rel(root, old_rel, new_rel);
                                                /* Avoid making duplicate entries ... */
                                                if (jrel)
                                                        result_rels = list_append_unique_ptr(result_rels,
@@ -219,8 +219,8 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels)
                }
 
                /*----------
-                * When IN clauses are involved, there may be no legal way to make
-                * an N-way join for some values of N.  For example consider
+                * When OJs or IN clauses are involved, there may be no legal way
+                * to make an N-way join for some values of N.  For example consider
                 *
                 * SELECT ... FROM t1 WHERE
                 *       x IN (SELECT ... FROM t2,t3 WHERE ...) AND
@@ -231,11 +231,12 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels)
                 * to accept failure at level 4 and go on to discover a workable
                 * bushy plan at level 5.
                 *
-                * However, if there are no IN clauses then make_join_rel() should
+                * However, if there are no such clauses then make_join_rel() should
                 * never fail, and so the following sanity check is useful.
                 *----------
                 */
-               if (result_rels == NIL && root->in_info_list == NIL)
+               if (result_rels == NIL &&
+                       root->oj_info_list == NIL && root->in_info_list == NIL)
                        elog(ERROR, "failed to build any %d-way joins", level);
        }
 
@@ -273,7 +274,7 @@ make_rels_by_clause_joins(PlannerInfo *root,
                {
                        RelOptInfo *jrel;
 
-                       jrel = make_join_rel(root, old_rel, other_rel, JOIN_INNER);
+                       jrel = make_join_rel(root, old_rel, other_rel);
                        if (jrel)
                                result = lcons(jrel, result);
                }
@@ -312,7 +313,7 @@ make_rels_by_clauseless_joins(PlannerInfo *root,
                {
                        RelOptInfo *jrel;
 
-                       jrel = make_join_rel(root, old_rel, other_rel, JOIN_INNER);
+                       jrel = make_join_rel(root, old_rel, other_rel);
 
                        /*
                         * As long as given other_rels are distinct, don't need to test to
@@ -328,85 +329,31 @@ make_rels_by_clauseless_joins(PlannerInfo *root,
 
 
 /*
- * is_inside_IN
- *             Detect whether the specified relation is inside an IN (sub-SELECT).
- *
- * Note that we are actually only interested in rels that have been pulled up
- * out of an IN, so the routine name is a slight misnomer.
+ * has_join_restriction
+ *             Detect whether the specified relation has join-order restrictions
+ *             due to being inside an OJ RHS or an IN (sub-SELECT).
  */
 static bool
-is_inside_IN(PlannerInfo *root, RelOptInfo *rel)
+has_join_restriction(PlannerInfo *root, RelOptInfo *rel)
 {
        ListCell   *l;
 
-       foreach(l, root->in_info_list)
+       foreach(l, root->oj_info_list)
        {
-               InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
+               OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
 
-               if (bms_is_subset(rel->relids, ininfo->righthand))
+               if (bms_is_subset(rel->relids, ojinfo->min_righthand))
                        return true;
        }
-       return false;
-}
-
 
-/*
- * make_jointree_rel
- *             Find or build a RelOptInfo join rel representing a specific
- *             jointree item.  For JoinExprs, we only consider the construction
- *             path that corresponds exactly to what the user wrote.
- */
-RelOptInfo *
-make_jointree_rel(PlannerInfo *root, Node *jtnode)
-{
-       if (IsA(jtnode, RangeTblRef))
-       {
-               int                     varno = ((RangeTblRef *) jtnode)->rtindex;
-
-               return find_base_rel(root, varno);
-       }
-       else if (IsA(jtnode, FromExpr))
-       {
-               FromExpr   *f = (FromExpr *) jtnode;
-
-               /* Recurse back to multi-way-join planner */
-               return make_fromexpr_rel(root, f);
-       }
-       else if (IsA(jtnode, JoinExpr))
+       foreach(l, root->in_info_list)
        {
-               JoinExpr   *j = (JoinExpr *) jtnode;
-               RelOptInfo *rel,
-                                  *lrel,
-                                  *rrel;
-
-               /* Recurse */
-               lrel = make_jointree_rel(root, j->larg);
-               rrel = make_jointree_rel(root, j->rarg);
-
-               /* Make this join rel */
-               rel = make_join_rel(root, lrel, rrel, j->jointype);
-
-               if (rel == NULL)                /* oops */
-                       elog(ERROR, "invalid join order");
-
-               /*
-                * Since we are only going to consider this one way to do it, we're
-                * done generating Paths for this joinrel and can now select the
-                * cheapest.  In fact we *must* do so now, since next level up will
-                * need it!
-                */
-               set_cheapest(rel);
-
-#ifdef OPTIMIZER_DEBUG
-               debug_print_rel(root, rel);
-#endif
+               InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
 
-               return rel;
+               if (bms_is_subset(rel->relids, ininfo->righthand))
+                       return true;
        }
-       else
-               elog(ERROR, "unrecognized node type: %d",
-                        (int) nodeTag(jtnode));
-       return NULL;                            /* keep compiler quiet */
+       return false;
 }
 
 
@@ -418,16 +365,19 @@ make_jointree_rel(PlannerInfo *root, Node *jtnode)
  *        (The join rel may already contain paths generated from other
  *        pairs of rels that add up to the same set of base rels.)
  *
- * NB: will return NULL if attempted join is not valid.  This can only
- * happen when working with IN clauses that have been turned into joins.
+ * NB: will return NULL if attempted join is not valid.  This can happen
+ * when working with outer joins, or with IN clauses that have been turned
+ * into joins.
  */
 RelOptInfo *
-make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
-                         JoinType jointype)
+make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 {
        Relids          joinrelids;
+       JoinType        jointype;
+       bool            is_valid_inner;
        RelOptInfo *joinrel;
        List       *restrictlist;
+       ListCell   *l;
 
        /* We should never try to join two overlapping sets of rels. */
        Assert(!bms_overlap(rel1->relids, rel2->relids));
@@ -436,94 +386,176 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
        joinrelids = bms_union(rel1->relids, rel2->relids);
 
        /*
-        * If we are implementing IN clauses as joins, there are some joins that
-        * are illegal.  Check to see if the proposed join is trouble. We can skip
-        * the work if looking at an outer join, however, because only top-level
-        * joins might be affected.
+        * If we have any outer joins, the proposed join might be illegal; and
+        * in any case we have to determine its join type.  Scan the OJ list
+        * for conflicts.
         */
-       if (jointype == JOIN_INNER)
-       {
-               ListCell   *l;
+       jointype = JOIN_INNER;          /* default if no match to an OJ */
+       is_valid_inner = true;
 
-               foreach(l, root->in_info_list)
-               {
-                       InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
-
-                       /*
-                        * This IN clause is not relevant unless its RHS overlaps the
-                        * proposed join.  (Check this first as a fast path for dismissing
-                        * most irrelevant INs quickly.)
-                        */
-                       if (!bms_overlap(ininfo->righthand, joinrelids))
-                               continue;
+       foreach(l, root->oj_info_list)
+       {
+               OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
 
-                       /*
-                        * If we are still building the IN clause's RHS, then this IN
-                        * clause isn't relevant yet.
-                        */
-                       if (bms_is_subset(joinrelids, ininfo->righthand))
-                               continue;
+               /*
+                * This OJ is not relevant unless its RHS overlaps the proposed join.
+                * (Check this first as a fast path for dismissing most irrelevant OJs
+                * quickly.)
+                */
+               if (!bms_overlap(ojinfo->min_righthand, joinrelids))
+                       continue;
 
-                       /*
-                        * Cannot join if proposed join contains rels not in the RHS *and*
-                        * contains only part of the RHS.  We must build the complete RHS
-                        * (subselect's join) before it can be joined to rels outside the
-                        * subselect.
-                        */
-                       if (!bms_is_subset(ininfo->righthand, joinrelids))
-                       {
-                               bms_free(joinrelids);
-                               return NULL;
-                       }
+               /*
+                * Also, not relevant if proposed join is fully contained within RHS
+                * (ie, we're still building up the RHS).
+                */
+               if (bms_is_subset(joinrelids, ojinfo->min_righthand))
+                       continue;
 
-                       /*
-                        * At this point we are considering a join of the IN's RHS to some
-                        * other rel(s).
-                        *
-                        * If we already joined IN's RHS to any other rels in either input
-                        * path, then this join is not constrained (the necessary work was
-                        * done at the lower level where that join occurred).
-                        */
-                       if (bms_is_subset(ininfo->righthand, rel1->relids) &&
-                               !bms_equal(ininfo->righthand, rel1->relids))
-                               continue;
-                       if (bms_is_subset(ininfo->righthand, rel2->relids) &&
-                               !bms_equal(ininfo->righthand, rel2->relids))
-                               continue;
+               /*
+                * Also, not relevant if OJ is already done within either input.
+                */
+               if (bms_is_subset(ojinfo->min_lefthand, rel1->relids) &&
+                       bms_is_subset(ojinfo->min_righthand, rel1->relids))
+                       continue;
+               if (bms_is_subset(ojinfo->min_lefthand, rel2->relids) &&
+                       bms_is_subset(ojinfo->min_righthand, rel2->relids))
+                       continue;
 
-                       /*
-                        * JOIN_IN technique will work if outerrel includes LHS and
-                        * innerrel is exactly RHS; conversely JOIN_REVERSE_IN handles
-                        * RHS/LHS.
-                        *
-                        * JOIN_UNIQUE_OUTER will work if outerrel is exactly RHS;
-                        * conversely JOIN_UNIQUE_INNER will work if innerrel is exactly
-                        * RHS.
-                        *
-                        * But none of these will work if we already found another IN that
-                        * needs to trigger here.
-                        */
+               /*
+                * If one input contains min_lefthand and the other contains
+                * min_righthand, then we can perform the OJ at this join.
+                *
+                * Barf if we get matches to more than one OJ (is that possible?)
+                */
+               if (bms_is_subset(ojinfo->min_lefthand, rel1->relids) &&
+                       bms_is_subset(ojinfo->min_righthand, rel2->relids))
+               {
                        if (jointype != JOIN_INNER)
                        {
+                               /* invalid join path */
                                bms_free(joinrelids);
                                return NULL;
                        }
-                       if (bms_is_subset(ininfo->lefthand, rel1->relids) &&
-                               bms_equal(ininfo->righthand, rel2->relids))
-                               jointype = JOIN_IN;
-                       else if (bms_is_subset(ininfo->lefthand, rel2->relids) &&
-                                        bms_equal(ininfo->righthand, rel1->relids))
-                               jointype = JOIN_REVERSE_IN;
-                       else if (bms_equal(ininfo->righthand, rel1->relids))
-                               jointype = JOIN_UNIQUE_OUTER;
-                       else if (bms_equal(ininfo->righthand, rel2->relids))
-                               jointype = JOIN_UNIQUE_INNER;
-                       else
+                       jointype = ojinfo->is_full_join ? JOIN_FULL : JOIN_LEFT;
+               }
+               else if (bms_is_subset(ojinfo->min_lefthand, rel2->relids) &&
+                                bms_is_subset(ojinfo->min_righthand, rel1->relids))
+               {
+                       if (jointype != JOIN_INNER)
                        {
                                /* invalid join path */
                                bms_free(joinrelids);
                                return NULL;
                        }
+                       jointype = ojinfo->is_full_join ? JOIN_FULL : JOIN_RIGHT;
+               }
+               else
+               {
+                       /*----------
+                        * Otherwise, the proposed join overlaps the RHS but isn't
+                        * a valid implementation of this OJ.  It might still be
+                        * a valid implementation of some other OJ, however.  We have
+                        * to allow this to support the associative identity
+                        *      (a LJ b on Pab) LJ c ON Pbc = a LJ (b LJ c ON Pbc) on Pab
+                        * since joining B directly to C violates the lower OJ's RHS.
+                        * We assume that make_outerjoininfo() set things up correctly
+                        * so that we'll only match to the upper OJ if the transformation
+                        * is valid.  Set flag here to check at bottom of loop.
+                        *----------
+                        */
+                       is_valid_inner = false;
+               }
+       }
+
+       /* Fail if violated some OJ's RHS and didn't match to another OJ */
+       if (jointype == JOIN_INNER && !is_valid_inner)
+       {
+               /* invalid join path */
+               bms_free(joinrelids);
+               return NULL;
+       }
+
+       /*
+        * Similarly, if we are implementing IN clauses as joins, check for
+        * illegal join path and detect whether we need a non-default join type.
+        */
+       foreach(l, root->in_info_list)
+       {
+               InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
+
+               /*
+                * This IN clause is not relevant unless its RHS overlaps the
+                * proposed join.  (Check this first as a fast path for dismissing
+                * most irrelevant INs quickly.)
+                */
+               if (!bms_overlap(ininfo->righthand, joinrelids))
+                       continue;
+
+               /*
+                * If we are still building the IN clause's RHS, then this IN
+                * clause isn't relevant yet.
+                */
+               if (bms_is_subset(joinrelids, ininfo->righthand))
+                       continue;
+
+               /*
+                * Cannot join if proposed join contains rels not in the RHS *and*
+                * contains only part of the RHS.  We must build the complete RHS
+                * (subselect's join) before it can be joined to rels outside the
+                * subselect.
+                */
+               if (!bms_is_subset(ininfo->righthand, joinrelids))
+               {
+                       bms_free(joinrelids);
+                       return NULL;
+               }
+
+               /*
+                * At this point we are considering a join of the IN's RHS to some
+                * other rel(s).
+                *
+                * If we already joined IN's RHS to any other rels in either input
+                * path, then this join is not constrained (the necessary work was
+                * done at the lower level where that join occurred).
+                */
+               if (bms_is_subset(ininfo->righthand, rel1->relids) &&
+                       !bms_equal(ininfo->righthand, rel1->relids))
+                       continue;
+               if (bms_is_subset(ininfo->righthand, rel2->relids) &&
+                       !bms_equal(ininfo->righthand, rel2->relids))
+                       continue;
+
+               /*
+                * JOIN_IN technique will work if outerrel includes LHS and innerrel
+                * is exactly RHS; conversely JOIN_REVERSE_IN handles RHS/LHS.
+                *
+                * JOIN_UNIQUE_OUTER will work if outerrel is exactly RHS; conversely
+                * JOIN_UNIQUE_INNER will work if innerrel is exactly RHS.
+                *
+                * But none of these will work if we already found an OJ or another IN
+                * that needs to trigger here.
+                */
+               if (jointype != JOIN_INNER)
+               {
+                       bms_free(joinrelids);
+                       return NULL;
+               }
+               if (bms_is_subset(ininfo->lefthand, rel1->relids) &&
+                       bms_equal(ininfo->righthand, rel2->relids))
+                       jointype = JOIN_IN;
+               else if (bms_is_subset(ininfo->lefthand, rel2->relids) &&
+                                bms_equal(ininfo->righthand, rel1->relids))
+                       jointype = JOIN_REVERSE_IN;
+               else if (bms_equal(ininfo->righthand, rel1->relids))
+                       jointype = JOIN_UNIQUE_OUTER;
+               else if (bms_equal(ininfo->righthand, rel2->relids))
+                       jointype = JOIN_UNIQUE_INNER;
+               else
+               {
+                       /* invalid join path */
+                       bms_free(joinrelids);
+                       return NULL;
                }
        }
 
index 72d9c7402ebef0f4a57c75a2ad67198cd764b803..4b132d65611d3bc5c3808296e2ae925258116fe9 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.112 2005/11/22 18:17:12 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.113 2005/12/20 02:30:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "utils/syscache.h"
 
 
-static void mark_baserels_for_outer_join(PlannerInfo *root, Relids rels,
-                                                        Relids outerrels);
+/* These parameters are set by GUC */
+int                    from_collapse_limit;
+int                    join_collapse_limit;
+
+
+static void add_vars_to_targetlist(PlannerInfo *root, List *vars,
+                                          Relids where_needed);
+static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
+                                                                bool below_outer_join, Relids *qualscope);
+static OuterJoinInfo *make_outerjoininfo(PlannerInfo *root,
+                                                                                Relids left_rels, Relids right_rels,
+                                                                                bool is_full_join, Node *clause);
 static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                                                bool is_pushed_down,
                                                bool is_deduced,
                                                bool below_outer_join,
-                                               Relids outerjoin_nonnullable,
-                                               Relids qualscope);
-static void add_vars_to_targetlist(PlannerInfo *root, List *vars,
-                                          Relids where_needed);
+                                               Relids qualscope,
+                                               Relids ojscope,
+                                               Relids outerjoin_nonnullable);
 static bool qual_is_redundant(PlannerInfo *root, RestrictInfo *restrictinfo,
                                  List *restrictlist);
 static void check_mergejoinable(RestrictInfo *restrictinfo);
@@ -162,66 +171,117 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed)
 
 /*****************************************************************************
  *
- *       QUALIFICATIONS
+ *       JOIN TREE PROCESSING
  *
  *****************************************************************************/
 
-
 /*
- * distribute_quals_to_rels
+ * deconstruct_jointree
  *       Recursively scan the query's join tree for WHERE and JOIN/ON qual
  *       clauses, and add these to the appropriate restrictinfo and joininfo
- *       lists belonging to base RelOptInfos.  Also, base RelOptInfos are marked
- *       with outerjoinset information, to aid in proper positioning of qual
- *       clauses that appear above outer joins.
+ *       lists belonging to base RelOptInfos.  Also, add OuterJoinInfo nodes
+ *       to root->oj_info_list for any outer joins appearing in the query tree.
+ *       Return a "joinlist" data structure showing the join order decisions
+ *       that need to be made by make_one_rel().
  *
- * 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.
+ * The "joinlist" result is a list of items that are either RangeTblRef
+ * jointree nodes or sub-joinlists.  All the items at the same level of
+ * joinlist must be joined in an order to be determined by make_one_rel()
+ * (note that legal orders may be constrained by OuterJoinInfo nodes).
+ * A sub-joinlist represents a subproblem to be planned separately. Currently
+ * sub-joinlists arise only from FULL OUTER JOIN or when collapsing of
+ * subproblems is stopped by join_collapse_limit or from_collapse_limit.
  *
  * 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)
  * of an outer join since the qual might eliminate matching rows and cause a
- * NULL row to be incorrectly emitted by the join.     Therefore, rels appearing
- * within the nullable side(s) of an outer join are marked with
- *             outerjoinset = set of Relids used at the outer join node.
- * This set will be added to the set of rels referenced by quals using such
- * a rel, thereby forcing them up the join tree to the right level.
+ * NULL row to be incorrectly emitted by the join.  Therefore, we artificially
+ * OR the minimum-relids of such an outer join into the required_relids of
+ * clauses appearing above it.  This forces those clauses to be delayed until
+ * application of the outer join (or maybe even higher in the join tree).
+ */
+List *
+deconstruct_jointree(PlannerInfo *root)
+{
+       Relids          qualscope;
+
+       /* Start recursion at top of jointree */
+       Assert(root->parse->jointree != NULL &&
+                  IsA(root->parse->jointree, FromExpr));
+
+       return deconstruct_recurse(root, (Node *) root->parse->jointree, false,
+                                                          &qualscope);
+}
+
+/*
+ * deconstruct_recurse
+ *       One recursion level of deconstruct_jointree processing.
  *
- * To ease the calculation of these values, distribute_quals_to_rels() returns
- * the set of base Relids involved in its own level of join.  This is just an
- * internal convenience; no outside callers pay attention to the result.
+ * Inputs:
+ *     jtnode is the jointree node to examine
+ *     below_outer_join is TRUE if this node is within the nullable side of a
+ *             higher-level outer join
+ * Outputs:
+ *     *qualscope gets the set of base Relids syntactically included in this
+ *             jointree node (do not modify or free this, as it may also be pointed
+ *             to by RestrictInfo nodes)
+ *     Return value is the appropriate joinlist for this jointree node
+ *
+ * In addition, entries will be added to root->oj_info_list for outer joins.
  */
-Relids
-distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
-                                                bool below_outer_join)
+static List *
+deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
+                                       Relids *qualscope)
 {
-       Relids          result = NULL;
+       List       *joinlist;
 
        if (jtnode == NULL)
-               return result;
+       {
+               *qualscope = NULL;
+               return NIL;
+       }
        if (IsA(jtnode, RangeTblRef))
        {
                int                     varno = ((RangeTblRef *) jtnode)->rtindex;
 
                /* No quals to deal with, just return correct result */
-               result = bms_make_singleton(varno);
+               *qualscope = bms_make_singleton(varno);
+               joinlist = list_make1(jtnode);
        }
        else if (IsA(jtnode, FromExpr))
        {
                FromExpr   *f = (FromExpr *) jtnode;
+               int                     remaining;
                ListCell   *l;
 
                /*
-                * First, recurse to handle child joins.
+                * First, recurse to handle child joins.  We collapse subproblems
+                * into a single joinlist whenever the resulting joinlist wouldn't
+                * exceed from_collapse_limit members.  Also, always collapse
+                * one-element subproblems, since that won't lengthen the joinlist
+                * anyway.
                 */
+               *qualscope = NULL;
+               joinlist = NIL;
+               remaining = list_length(f->fromlist);
                foreach(l, f->fromlist)
                {
-                       result = bms_add_members(result,
-                                                                        distribute_quals_to_rels(root,
-                                                                                                                         lfirst(l),
-                                                                                                                 below_outer_join));
+                       Relids  sub_qualscope;
+                       List   *sub_joinlist;
+                       int             sub_members;
+
+                       sub_joinlist = deconstruct_recurse(root, lfirst(l),
+                                                                                          below_outer_join,
+                                                                                          &sub_qualscope);
+                       *qualscope = bms_add_members(*qualscope, sub_qualscope);
+                       sub_members = list_length(sub_joinlist);
+                       remaining--;
+                       if (sub_members <= 1 ||
+                               list_length(joinlist) + sub_members + remaining <= from_collapse_limit)
+                               joinlist = list_concat(joinlist, sub_joinlist);
+                       else
+                               joinlist = lappend(joinlist, sub_joinlist);
                }
 
                /*
@@ -231,7 +291,7 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
                foreach(l, (List *) f->quals)
                        distribute_qual_to_rels(root, (Node *) lfirst(l),
                                                                        true, false, below_outer_join,
-                                                                       NULL, result);
+                                                                       *qualscope, NULL, NULL);
        }
        else if (IsA(jtnode, JoinExpr))
        {
@@ -239,7 +299,10 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
                Relids          leftids,
                                        rightids,
                                        nonnullable_rels,
-                                       nullable_rels;
+                                       ojscope;
+               List       *leftjoinlist,
+                                  *rightjoinlist;
+               OuterJoinInfo *ojinfo;
                ListCell   *qual;
 
                /*
@@ -249,55 +312,55 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
                 * Then we place our own join quals, which are restricted by lower
                 * outer joins in any case, and are forced to this level if this is an
                 * outer join and they mention the outer side.  Finally, if this is an
-                * outer join, we mark baserels contained within the inner side(s)
-                * with our own rel set; this will prevent quals above us in the join
-                * tree that use those rels from being pushed down below this level.
-                * (It's okay for upper quals to be pushed down to the outer side,
-                * however.)
+                * outer join, we create an oj_info_list entry for the join.  This
+                * will prevent quals above us in the join tree that use those rels
+                * from being pushed down below this level.  (It's okay for upper
+                * quals to be pushed down to the outer side, however.)
                 */
                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);
+                               leftjoinlist = deconstruct_recurse(root, j->larg,
+                                                                                                  below_outer_join,
+                                                                                                  &leftids);
+                               rightjoinlist = deconstruct_recurse(root, j->rarg,
+                                                                                                       below_outer_join,
+                                                                                                       &rightids);
+                               *qualscope = 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);
+                               leftjoinlist = deconstruct_recurse(root, j->larg,
+                                                                                                  below_outer_join,
+                                                                                                  &leftids);
+                               rightjoinlist = deconstruct_recurse(root, j->rarg,
+                                                                                                       true,
+                                                                                                       &rightids);
+                               *qualscope = 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);
+                               leftjoinlist = deconstruct_recurse(root, j->larg,
+                                                                                                  true,
+                                                                                                  &leftids);
+                               rightjoinlist = deconstruct_recurse(root, j->rarg,
+                                                                                                       true,
+                                                                                                       &rightids);
+                               *qualscope = bms_union(leftids, rightids);
                                /* each side is both outer and inner */
-                               nonnullable_rels = result;
-                               nullable_rels = result;
+                               nonnullable_rels = *qualscope;
                                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;
+                               /* notice we switch leftids and rightids */
+                               leftjoinlist = deconstruct_recurse(root, j->larg,
+                                                                                                  true,
+                                                                                                  &rightids);
+                               rightjoinlist = deconstruct_recurse(root, j->rarg,
+                                                                                                       below_outer_join,
+                                                                                                       &leftids);
+                               *qualscope = bms_union(leftids, rightids);
+                               nonnullable_rels = leftids;
                                break;
                        case JOIN_UNION:
 
@@ -309,73 +372,184 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                                 errmsg("UNION JOIN is not implemented")));
                                nonnullable_rels = NULL;                /* keep compiler quiet */
-                               nullable_rels = NULL;
+                               leftjoinlist = rightjoinlist = NIL;
                                break;
                        default:
                                elog(ERROR, "unrecognized join type: %d",
                                         (int) j->jointype);
                                nonnullable_rels = NULL;                /* keep compiler quiet */
-                               nullable_rels = NULL;
+                               leftjoinlist = rightjoinlist = NIL;
                                break;
                }
 
+               /*
+                * For an OJ, form the OuterJoinInfo now, because we need the OJ's
+                * semantic scope (ojscope) to pass to distribute_qual_to_rels.
+                */
+               if (j->jointype != JOIN_INNER)
+               {
+                       ojinfo = make_outerjoininfo(root, leftids, rightids,
+                                                                               (j->jointype == JOIN_FULL), j->quals);
+                       ojscope = bms_union(ojinfo->min_lefthand, ojinfo->min_righthand);
+               }
+               else
+               {
+                       ojinfo = NULL;
+                       ojscope = NULL;
+               }
+
+               /* Process the qual clauses */
                foreach(qual, (List *) j->quals)
                        distribute_qual_to_rels(root, (Node *) lfirst(qual),
                                                                        false, false, below_outer_join,
-                                                                       nonnullable_rels, result);
+                                                                       *qualscope, ojscope, nonnullable_rels);
+
+               /* Now we can add the OuterJoinInfo to oj_info_list */
+               if (ojinfo)
+                       root->oj_info_list = lappend(root->oj_info_list, ojinfo);
 
-               if (nullable_rels != NULL)
-                       mark_baserels_for_outer_join(root, nullable_rels, result);
+               /*
+                * Finally, compute the output joinlist.  We fold subproblems together
+                * except at a FULL JOIN or where join_collapse_limit would be
+                * exceeded.
+                */
+               if (j->jointype != JOIN_FULL &&
+                       (list_length(leftjoinlist) + list_length(rightjoinlist) <=
+                        join_collapse_limit))
+                       joinlist = list_concat(leftjoinlist, rightjoinlist);
+               else                                    /* force the join order at this node */
+                       joinlist = list_make1(list_make2(leftjoinlist, rightjoinlist));
        }
        else
+       {
                elog(ERROR, "unrecognized node type: %d",
                         (int) nodeTag(jtnode));
-       return result;
+               joinlist = NIL;                 /* keep compiler quiet */
+       }
+       return joinlist;
 }
 
 /*
- * mark_baserels_for_outer_join
- *       Mark all base rels listed in 'rels' as having the given outerjoinset.
+ * make_outerjoininfo
+ *       Build an OuterJoinInfo for the current outer join
+ *
+ * Inputs:
+ *     left_rels: the base Relids syntactically on outer side of join
+ *     right_rels: the base Relids syntactically on inner side of join
+ *     is_full_join: what it says
+ *     clause: the outer join's join condition
+ *
+ * If the join is a RIGHT JOIN, left_rels and right_rels are switched by
+ * the caller, so that left_rels is always the nonnullable side.  Hence
+ * we need only distinguish the LEFT and FULL cases.
+ *
+ * The node should eventually be put into root->oj_info_list, but we
+ * do not do that here.
  */
-static void
-mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels)
+static OuterJoinInfo *
+make_outerjoininfo(PlannerInfo *root,
+                                  Relids left_rels, Relids right_rels,
+                                  bool is_full_join, Node *clause)
 {
-       Relids          tmprelids;
-       int                     relno;
+       OuterJoinInfo *ojinfo = makeNode(OuterJoinInfo);
+       Relids          clause_relids;
+       Relids          strict_relids;
+       ListCell   *l;
+
+       /* If it's a full join, no need to be very smart */
+       ojinfo->is_full_join = is_full_join;
+       if (is_full_join)
+       {
+               ojinfo->min_lefthand = left_rels;
+               ojinfo->min_righthand = right_rels;
+               ojinfo->lhs_strict = false;                     /* don't care about this */
+               return ojinfo;
+       }
+
+       /*
+        * Retrieve all relids mentioned within the join clause.
+        */
+       clause_relids = pull_varnos(clause);
+
+       /*
+        * For which relids is the clause strict, ie, it cannot succeed if the
+        * rel's columns are all NULL?
+        */
+       strict_relids = find_nonnullable_rels(clause);
 
-       tmprelids = bms_copy(rels);
-       while ((relno = bms_first_member(tmprelids)) >= 0)
+       /* Remember whether the clause is strict for any LHS relations */
+       ojinfo->lhs_strict = bms_overlap(strict_relids, left_rels);
+
+       /*
+        * Required LHS is basically the LHS rels mentioned in the clause...
+        * but if there aren't any, punt and make it the full LHS, to avoid
+        * having an empty min_lefthand which will confuse later processing.
+        * (We don't try to be smart about such cases, just correct.)
+        * We may have to add more rels based on lower outer joins; see below.
+        */
+       ojinfo->min_lefthand = bms_intersect(clause_relids, left_rels);
+       if (bms_is_empty(ojinfo->min_lefthand))
+               ojinfo->min_lefthand = bms_copy(left_rels);
+
+       /*
+        * Required RHS is normally the full set of RHS rels.  Sometimes we
+        * can exclude some, see below.
+        */
+       ojinfo->min_righthand = bms_copy(right_rels);
+
+       foreach(l, root->oj_info_list)
        {
-               RelOptInfo *rel = find_base_rel(root, relno);
+               OuterJoinInfo *otherinfo = (OuterJoinInfo *) lfirst(l);
+
+               /* ignore full joins --- other mechanisms preserve their ordering */
+               if (otherinfo->is_full_join)
+                       continue;
 
                /*
-                * Since we do this bottom-up, any outer-rels previously marked should
-                * be within the new outer join set.
+                * For a lower OJ in our LHS, if our join condition uses the lower
+                * join's RHS and is not strict for that rel, we must preserve the
+                * ordering of the two OJs, so add lower OJ's full required relset to
+                * min_lefthand.
                 */
-               Assert(bms_is_subset(rel->outerjoinset, outerrels));
-
+               if (bms_overlap(ojinfo->min_lefthand, otherinfo->min_righthand) &&
+                       !bms_overlap(strict_relids, otherinfo->min_righthand))
+               {
+                       ojinfo->min_lefthand = bms_add_members(ojinfo->min_lefthand,
+                                                                                                  otherinfo->min_lefthand);
+                       ojinfo->min_lefthand = bms_add_members(ojinfo->min_lefthand,
+                                                                                                  otherinfo->min_righthand);
+               }
                /*
-                * Presently the executor cannot support FOR UPDATE/SHARE marking of
-                * rels appearing on the nullable side of an outer join. (It's
-                * somewhat unclear what that would mean, anyway: what should we mark
-                * when a result row is generated from no element of the nullable
-                * relation?)  So, complain if target rel is FOR UPDATE/SHARE. It's
-                * sufficient to make this check once per rel, so do it only if rel
-                * wasn't already known nullable.
+                * For a lower OJ in our RHS, if our join condition does not use the
+                * lower join's RHS and the lower OJ's join condition is strict, we
+                * can interchange the ordering of the two OJs, so exclude the lower
+                * RHS from our min_righthand.
                 */
-               if (rel->outerjoinset == NULL)
+               if (bms_overlap(ojinfo->min_righthand, otherinfo->min_righthand) &&
+                       !bms_overlap(clause_relids, otherinfo->min_righthand) &&
+                       otherinfo->lhs_strict)
                {
-                       if (list_member_int(root->parse->rowMarks, relno))
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("SELECT FOR UPDATE/SHARE cannot be applied to the nullable side of an outer join")));
+                       ojinfo->min_righthand = bms_del_members(ojinfo->min_righthand,
+                                                                                                       otherinfo->min_righthand);
                }
-
-               rel->outerjoinset = outerrels;
        }
-       bms_free(tmprelids);
+
+       /* Neither set should be empty, else we might get confused later */
+       Assert(!bms_is_empty(ojinfo->min_lefthand));
+       Assert(!bms_is_empty(ojinfo->min_righthand));
+       /* Shouldn't overlap either */
+       Assert(!bms_overlap(ojinfo->min_lefthand, ojinfo->min_righthand));
+
+       return ojinfo;
 }
 
+
+/*****************************************************************************
+ *
+ *       QUALIFICATIONS
+ *
+ *****************************************************************************/
+
 /*
  * distribute_qual_to_rels
  *       Add clause information to either the baserestrictinfo or joininfo list
@@ -392,21 +566,26 @@ mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels)
  * '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.
+ * 'qualscope': set of baserels the qual's syntactic scope covers
+ * 'ojscope': NULL if not an outer-join qual, else the minimum set of baserels
+ *             needed to form this 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
+ *             (for FULL JOIN this includes both sides of the join, and must in fact
+ *             equal qualscope)
  *
- * 'qualscope' identifies what level of JOIN the qual came from.  For a top
- * level qual (WHERE qual), qualscope lists all baserel ids and in addition
- * 'is_pushed_down' will be TRUE.
+ * 'qualscope' identifies what level of JOIN the qual came from syntactically.
+ * 'ojscope' is needed if we decide to force the qual up to the outer-join
+ * level, which will be ojscope not necessarily qualscope.
  */
 static void
 distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                                                bool is_pushed_down,
                                                bool is_deduced,
                                                bool below_outer_join,
-                                               Relids outerjoin_nonnullable,
-                                               Relids qualscope)
+                                               Relids qualscope,
+                                               Relids ojscope,
+                                               Relids outerjoin_nonnullable)
 {
        Relids          relids;
        bool            outerjoin_delayed;
@@ -427,16 +606,20 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
         */
        if (!bms_is_subset(relids, qualscope))
                elog(ERROR, "JOIN qualification may not refer to other relations");
+       if (ojscope && !bms_is_subset(relids, ojscope))
+               elog(ERROR, "JOIN qualification may not refer to other relations");
 
        /*
         * If the clause is variable-free, we force it to be evaluated at its
         * original syntactic level.  Note that this should not happen for
         * top-level clauses, because query_planner() special-cases them.  But it
         * will happen for variable-free JOIN/ON clauses.  We don't have to be
-        * real smart about such a case, we just have to be correct.
+        * real smart about such a case, we just have to be correct.  Also note
+        * that for an outer-join clause, we must force it to the OJ's semantic
+        * level, not the syntactic scope.
         */
        if (bms_is_empty(relids))
-               relids = qualscope;
+               relids = ojscope ? ojscope : qualscope;
 
        /*
         * Check to see if clause application must be delayed by outer-join
@@ -451,6 +634,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                 * be delayed by outer-join rules.
                 */
                Assert(bms_equal(relids, qualscope));
+               Assert(!ojscope);
                /* Needn't feed it back for more deductions */
                outerjoin_delayed = false;
                maybe_equijoin = false;
@@ -471,7 +655,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                 * result, so we treat it the same as an ordinary inner-join qual,
                 * except for not setting maybe_equijoin (see below).
                 */
-               relids = qualscope;
+               Assert(ojscope);
+               relids = ojscope;
                outerjoin_delayed = true;
 
                /*
@@ -493,28 +678,27 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
                 * we have all the rels it mentions, and (2) we are at or above any
                 * outer joins that can null any of these rels and are below the
                 * syntactic location of the given qual. To enforce the latter, scan
-                * the base rels listed in relids, and merge their outer-join sets
+                * the oj_info_list and merge the required-relid sets of any such OJs
                 * into the clause's own reference list.  At the time we are called,
-                * the outerjoinset of each baserel will show exactly those outer
-                * joins that are below the qual in the join tree.
+                * the oj_info_list contains only outer joins below this qual.
                 */
                Relids          addrelids = NULL;
-               Relids          tmprelids;
-               int                     relno;
+               ListCell   *l;
 
                outerjoin_delayed = false;
-               tmprelids = bms_copy(relids);
-               while ((relno = bms_first_member(tmprelids)) >= 0)
+               foreach(l, root->oj_info_list)
                {
-                       RelOptInfo *rel = find_base_rel(root, relno);
+                       OuterJoinInfo *ojinfo = (OuterJoinInfo *) lfirst(l);
 
-                       if (rel->outerjoinset != NULL)
+                       if (bms_overlap(relids, ojinfo->min_righthand) ||
+                               (ojinfo->is_full_join &&
+                                bms_overlap(relids, ojinfo->min_lefthand)))
                        {
-                               addrelids = bms_add_members(addrelids, rel->outerjoinset);
+                               addrelids = bms_add_members(addrelids, ojinfo->min_lefthand);
+                               addrelids = bms_add_members(addrelids, ojinfo->min_righthand);
                                outerjoin_delayed = true;
                        }
                }
-               bms_free(tmprelids);
 
                if (bms_is_subset(addrelids, relids))
                {
@@ -553,9 +737,11 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
         * its original syntactic level.  This allows us to distinguish original
         * JOIN/ON quals from higher-level quals pushed down to the same joinrel.
         * A qual originating from WHERE is always considered "pushed down".
+        * Note that for an outer-join qual, we have to compare to ojscope not
+        * qualscope.
         */
        if (!is_pushed_down)
-               is_pushed_down = !bms_equal(relids, qualscope);
+               is_pushed_down = !bms_equal(relids, ojscope ? ojscope : qualscope);
 
        /*
         * Build the RestrictInfo node itself.
@@ -864,7 +1050,7 @@ process_implied_equality(PlannerInfo *root,
         * taken for an original JOIN/ON clause.
         */
        distribute_qual_to_rels(root, (Node *) clause,
-                                                       true, true, false, NULL, relids);
+                                                       true, true, false, relids, NULL, NULL);
 }
 
 /*
index 06d351bf59d1fc41f1b410d8999dfcca510844b5..3729fd2b199f6ffdad3fdfc183faaa1323534f8b 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.90 2005/11/22 18:17:13 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.91 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -83,6 +83,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
 {
        Query      *parse = root->parse;
        List       *constant_quals;
+       List       *joinlist;
        RelOptInfo *final_rel;
        Path       *cheapestpath;
        Path       *sortedpath;
@@ -134,6 +135,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
        root->left_join_clauses = NIL;
        root->right_join_clauses = NIL;
        root->full_join_clauses = NIL;
+       root->oj_info_list = NIL;
 
        /*
         * Construct RelOptInfo nodes for all base relations in query.
@@ -144,7 +146,8 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
         * Examine the targetlist and qualifications, adding entries to baserel
         * targetlists for all referenced Vars.  Restrict and join clauses are
         * added to appropriate lists belonging to the mentioned relations.  We
-        * also build lists of equijoined keys for pathkey construction.
+        * also build lists of equijoined keys for pathkey construction, and
+        * form a target joinlist for make_one_rel() to work from.
         *
         * Note: all subplan nodes will have "flat" (var-only) tlists. This
         * implies that all expression evaluations are done at the root of the
@@ -154,7 +157,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, false);
+       joinlist = deconstruct_jointree(root);
 
        /*
         * Use the completed lists of equijoined keys to deduce any implied but
@@ -175,7 +178,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
        /*
         * Ready to do the primary planning.
         */
-       final_rel = make_one_rel(root);
+       final_rel = make_one_rel(root, joinlist);
 
        if (!final_rel || !final_rel->cheapest_total_path)
                elog(ERROR, "failed to construct the join relation");
index 9f6d0957b1e333ac745b1d268e9d1431c36a32fa..0dd9e1e8d2aac77b4925de5681d927219e276962 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.195 2005/11/22 18:17:13 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.196 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -352,17 +352,6 @@ subquery_planner(Query *parse, double tuple_fraction,
        if (root->hasOuterJoins)
                reduce_outer_joins(root);
 
-       /*
-        * See if we can simplify the jointree; opportunities for this may come
-        * from having pulled up subqueries, or from flattening explicit JOIN
-        * syntax.      We must do this after flattening JOIN alias variables, since
-        * eliminating explicit JOIN nodes from the jointree will cause
-        * get_relids_for_join() to fail.  But it should happen after
-        * reduce_outer_joins, anyway.
-        */
-       parse->jointree = (FromExpr *)
-               simplify_jointree(root, (Node *) parse->jointree);
-
        /*
         * Do the main planning.  If we have an inherited target relation, that
         * needs special processing, else go straight to grouping_planner.
@@ -567,6 +556,8 @@ inheritance_planner(PlannerInfo *root, List *inheritlist)
                        adjust_inherited_attrs((Node *) root->in_info_list,
                                                                   parentRTindex, parentOID,
                                                                   childRTindex, childOID);
+               /* There shouldn't be any OJ info to translate, though */
+               Assert(subroot.oj_info_list == NIL);
 
                /* Generate plan */
                subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ );
index cc3d904eca5d0cef810342a88017f7ee7a875f10..a25787685b1a774ec446e9d68a8feec69adf9b22 100644 (file)
@@ -8,7 +8,6 @@
  *             pull_up_subqueries
  *             do expression preprocessing (including flattening JOIN alias vars)
  *             reduce_outer_joins
- *             simplify_jointree
  *
  *
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
@@ -16,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.32 2005/11/22 18:17:14 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.33 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "utils/lsyscache.h"
 
 
-/* These parameters are set by GUC */
-int                    from_collapse_limit;
-int                    join_collapse_limit;
-
-
 typedef struct reduce_outer_joins_state
 {
        Relids          relids;                 /* base relids within this subtree */
@@ -52,7 +46,6 @@ static void reduce_outer_joins_pass2(Node *jtnode,
                                                 reduce_outer_joins_state *state,
                                                 PlannerInfo *root,
                                                 Relids nonnullable_rels);
-static Relids find_nonnullable_rels(Node *node, bool top_level);
 static void fix_in_clause_relids(List *in_info_list, int varno,
                                         Relids subrelids);
 static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
@@ -334,6 +327,13 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode, bool below_outer_join)
                        root->in_info_list = list_concat(root->in_info_list,
                                                                                         subroot->in_info_list);
 
+                       /*
+                        * We don't have to do the equivalent bookkeeping for outer-join
+                        * info, because that hasn't been set up yet.
+                        */
+                       Assert(root->oj_info_list == NIL);
+                       Assert(subroot->oj_info_list == NIL);
+
                        /*
                         * Miscellaneous housekeeping.
                         */
@@ -695,7 +695,7 @@ reduce_outer_joins_pass2(Node *jtnode,
                Relids          pass_nonnullable;
 
                /* Scan quals to see if we can add any nonnullability constraints */
-               pass_nonnullable = find_nonnullable_rels(f->quals, true);
+               pass_nonnullable = find_nonnullable_rels(f->quals);
                pass_nonnullable = bms_add_members(pass_nonnullable,
                                                                                   nonnullable_rels);
                /* And recurse --- but only into interesting subtrees */
@@ -772,7 +772,7 @@ reduce_outer_joins_pass2(Node *jtnode,
                         */
                        if (jointype != JOIN_FULL)
                        {
-                               local_nonnullable = find_nonnullable_rels(j->quals, true);
+                               local_nonnullable = find_nonnullable_rels(j->quals);
                                local_nonnullable = bms_add_members(local_nonnullable,
                                                                                                        nonnullable_rels);
                        }
@@ -805,256 +805,6 @@ reduce_outer_joins_pass2(Node *jtnode,
                         (int) nodeTag(jtnode));
 }
 
-/*
- * find_nonnullable_rels
- *             Determine which base rels are forced nonnullable by given quals
- *
- * We don't use expression_tree_walker here because we don't want to
- * descend through very many kinds of nodes; only the ones we can be sure
- * are strict. We can descend through the top level of implicit AND'ing,
- * but not through any explicit ANDs (or ORs) below that, since those are not
- * strict constructs.  The List case handles the top-level implicit AND list
- * as well as lists of arguments to strict operators/functions.
- */
-static Relids
-find_nonnullable_rels(Node *node, bool top_level)
-{
-       Relids          result = NULL;
-
-       if (node == NULL)
-               return NULL;
-       if (IsA(node, Var))
-       {
-               Var                *var = (Var *) node;
-
-               if (var->varlevelsup == 0)
-                       result = bms_make_singleton(var->varno);
-       }
-       else if (IsA(node, List))
-       {
-               ListCell   *l;
-
-               foreach(l, (List *) node)
-               {
-                       result = bms_join(result, find_nonnullable_rels(lfirst(l),
-                                                                                                                       top_level));
-               }
-       }
-       else if (IsA(node, FuncExpr))
-       {
-               FuncExpr   *expr = (FuncExpr *) node;
-
-               if (func_strict(expr->funcid))
-                       result = find_nonnullable_rels((Node *) expr->args, false);
-       }
-       else if (IsA(node, OpExpr))
-       {
-               OpExpr     *expr = (OpExpr *) node;
-
-               if (op_strict(expr->opno))
-                       result = find_nonnullable_rels((Node *) expr->args, false);
-       }
-       else if (IsA(node, BoolExpr))
-       {
-               BoolExpr   *expr = (BoolExpr *) node;
-
-               /* NOT is strict, others are not */
-               if (expr->boolop == NOT_EXPR)
-                       result = find_nonnullable_rels((Node *) expr->args, false);
-       }
-       else if (IsA(node, RelabelType))
-       {
-               RelabelType *expr = (RelabelType *) node;
-
-               result = find_nonnullable_rels((Node *) expr->arg, top_level);
-       }
-       else if (IsA(node, ConvertRowtypeExpr))
-       {
-               /* not clear this is useful, but it can't hurt */
-               ConvertRowtypeExpr *expr = (ConvertRowtypeExpr *) node;
-
-               result = find_nonnullable_rels((Node *) expr->arg, top_level);
-       }
-       else if (IsA(node, NullTest))
-       {
-               NullTest   *expr = (NullTest *) node;
-
-               /*
-                * IS NOT NULL can be considered strict, but only at top level; else
-                * we might have something like NOT (x IS NOT NULL).
-                */
-               if (top_level && expr->nulltesttype == IS_NOT_NULL)
-                       result = find_nonnullable_rels((Node *) expr->arg, false);
-       }
-       else if (IsA(node, BooleanTest))
-       {
-               BooleanTest *expr = (BooleanTest *) node;
-
-               /*
-                * Appropriate boolean tests are strict at top level.
-                */
-               if (top_level &&
-                       (expr->booltesttype == IS_TRUE ||
-                        expr->booltesttype == IS_FALSE ||
-                        expr->booltesttype == IS_NOT_UNKNOWN))
-                       result = find_nonnullable_rels((Node *) expr->arg, false);
-       }
-       return result;
-}
-
-/*
- * simplify_jointree
- *             Attempt to simplify a query's jointree.
- *
- * If we succeed in pulling up a subquery then we might form a jointree
- * in which a FromExpr is a direct child of another FromExpr.  In that
- * case we can consider collapsing the two FromExprs into one. This is
- * an optional conversion, since the planner will work correctly either
- * way.  But we may find a better plan (at the cost of more planning time)
- * if we merge the two nodes, creating a single join search space out of
- * two.  To allow the user to trade off planning time against plan quality,
- * we provide a control parameter from_collapse_limit that limits the size
- * of the join search space that can be created this way.
- *
- * We also consider flattening explicit inner JOINs into FromExprs (which
- * will in turn allow them to be merged into parent FromExprs).  The tradeoffs
- * here are the same as for flattening FromExprs, but we use a different
- * control parameter so that the user can use explicit JOINs to control the
- * join order even when they are inner JOINs.
- *
- * NOTE: don't try to do this in the same jointree scan that does subquery
- * pullup!     Since we're changing the jointree structure here, that wouldn't
- * work reliably --- see comments for pull_up_subqueries().
- */
-Node *
-simplify_jointree(PlannerInfo *root, Node *jtnode)
-{
-       if (jtnode == NULL)
-               return NULL;
-       if (IsA(jtnode, RangeTblRef))
-       {
-               /* nothing to do here... */
-       }
-       else if (IsA(jtnode, FromExpr))
-       {
-               FromExpr   *f = (FromExpr *) jtnode;
-               List       *newlist = NIL;
-               int                     children_remaining;
-               ListCell   *l;
-
-               children_remaining = list_length(f->fromlist);
-               foreach(l, f->fromlist)
-               {
-                       Node       *child = (Node *) lfirst(l);
-
-                       children_remaining--;
-                       /* Recursively simplify this child... */
-                       child = simplify_jointree(root, child);
-                       /* Now, is it a FromExpr? */
-                       if (child && IsA(child, FromExpr))
-                       {
-                               /*
-                                * Yes, so do we want to merge it into parent?  Always do so
-                                * if child has just one element (since that doesn't make the
-                                * parent's list any longer).  Otherwise merge if the
-                                * resulting join list would be no longer than
-                                * from_collapse_limit.
-                                */
-                               FromExpr   *subf = (FromExpr *) child;
-                               int                     childlen = list_length(subf->fromlist);
-                               int                     myothers = list_length(newlist) + children_remaining;
-
-                               if (childlen <= 1 ||
-                                       (childlen + myothers) <= from_collapse_limit)
-                               {
-                                       newlist = list_concat(newlist, subf->fromlist);
-
-                                       /*
-                                        * By now, the quals have been converted to implicit-AND
-                                        * lists, so we just need to join the lists.  NOTE: we put
-                                        * the pulled-up quals first.
-                                        */
-                                       f->quals = (Node *) list_concat((List *) subf->quals,
-                                                                                                       (List *) f->quals);
-                               }
-                               else
-                                       newlist = lappend(newlist, child);
-                       }
-                       else
-                               newlist = lappend(newlist, child);
-               }
-               f->fromlist = newlist;
-       }
-       else if (IsA(jtnode, JoinExpr))
-       {
-               JoinExpr   *j = (JoinExpr *) jtnode;
-
-               /* Recursively simplify the children... */
-               j->larg = simplify_jointree(root, j->larg);
-               j->rarg = simplify_jointree(root, j->rarg);
-
-               /*
-                * If it is an outer join, we must not flatten it.      An inner join is
-                * semantically equivalent to a FromExpr; we convert it to one,
-                * allowing it to be flattened into its parent, if the resulting
-                * FromExpr would have no more than join_collapse_limit members.
-                */
-               if (j->jointype == JOIN_INNER && join_collapse_limit > 1)
-               {
-                       int                     leftlen,
-                                               rightlen;
-
-                       if (j->larg && IsA(j->larg, FromExpr))
-                               leftlen = list_length(((FromExpr *) j->larg)->fromlist);
-                       else
-                               leftlen = 1;
-                       if (j->rarg && IsA(j->rarg, FromExpr))
-                               rightlen = list_length(((FromExpr *) j->rarg)->fromlist);
-                       else
-                               rightlen = 1;
-                       if ((leftlen + rightlen) <= join_collapse_limit)
-                       {
-                               FromExpr   *f = makeNode(FromExpr);
-
-                               f->fromlist = NIL;
-                               f->quals = NULL;
-
-                               if (j->larg && IsA(j->larg, FromExpr))
-                               {
-                                       FromExpr   *subf = (FromExpr *) j->larg;
-
-                                       f->fromlist = subf->fromlist;
-                                       f->quals = subf->quals;
-                               }
-                               else
-                                       f->fromlist = list_make1(j->larg);
-
-                               if (j->rarg && IsA(j->rarg, FromExpr))
-                               {
-                                       FromExpr   *subf = (FromExpr *) j->rarg;
-
-                                       f->fromlist = list_concat(f->fromlist,
-                                                                                         subf->fromlist);
-                                       f->quals = (Node *) list_concat((List *) f->quals,
-                                                                                                       (List *) subf->quals);
-                               }
-                               else
-                                       f->fromlist = lappend(f->fromlist, j->rarg);
-
-                               /* pulled-up quals first */
-                               f->quals = (Node *) list_concat((List *) f->quals,
-                                                                                               (List *) j->quals);
-
-                               return (Node *) f;
-                       }
-               }
-       }
-       else
-               elog(ERROR, "unrecognized node type: %d",
-                        (int) nodeTag(jtnode));
-       return jtnode;
-}
-
 /*
  * fix_in_clause_relids: update RT-index sets of InClauseInfo nodes
  *
@@ -1128,9 +878,6 @@ get_relids_in_jointree(Node *jtnode)
 
 /*
  * get_relids_for_join: get set of base RT indexes making up a join
- *
- * NB: this will not work reliably after simplify_jointree() is run,
- * since that may eliminate join nodes from the jointree.
  */
 Relids
 get_relids_for_join(PlannerInfo *root, int joinrelid)
index 23b229815a4e5d50cffefe566bf8a0dd4ba539fe..2cdb3b357391accb1c8ec94f106b739b067474cc 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.203 2005/11/22 18:17:14 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.204 2005/12/20 02:30:36 tgl Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -69,6 +69,7 @@ static bool contain_subplans_walker(Node *node, void *context);
 static bool contain_mutable_functions_walker(Node *node, void *context);
 static bool contain_volatile_functions_walker(Node *node, void *context);
 static bool contain_nonstrict_functions_walker(Node *node, void *context);
+static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
 static bool set_coercionform_dontcare_walker(Node *node, void *context);
 static Node *eval_const_expressions_mutator(Node *node,
                                                           eval_const_expressions_context *context);
@@ -861,6 +862,131 @@ contain_nonstrict_functions_walker(Node *node, void *context)
 }
 
 
+/*
+ * find_nonnullable_rels
+ *             Determine which base rels are forced nonnullable by given clause.
+ *
+ * Returns the set of all Relids that are referenced in the clause in such
+ * a way that the clause cannot possibly return TRUE if any of these Relids
+ * is an all-NULL row.  (It is OK to err on the side of conservatism; hence
+ * the analysis here is simplistic.)
+ *
+ * The semantics here are subtly different from contain_nonstrict_functions:
+ * that function is concerned with NULL results from arbitrary expressions,
+ * but here we assume that the input is a Boolean expression, and wish to
+ * see if NULL inputs will provably cause a FALSE-or-NULL result.  We expect
+ * the expression to have been AND/OR flattened and converted to implicit-AND
+ * format.
+ *
+ * We don't use expression_tree_walker here because we don't want to
+ * descend through very many kinds of nodes; only the ones we can be sure
+ * are strict. We can descend through the top level of implicit AND'ing,
+ * but not through any explicit ANDs (or ORs) below that, since those are not
+ * strict constructs.  The List case handles the top-level implicit AND list
+ * as well as lists of arguments to strict operators/functions.
+ */
+Relids
+find_nonnullable_rels(Node *clause)
+{
+       return find_nonnullable_rels_walker(clause, true);
+}
+
+static Relids
+find_nonnullable_rels_walker(Node *node, bool top_level)
+{
+       Relids          result = NULL;
+
+       if (node == NULL)
+               return NULL;
+       if (IsA(node, Var))
+       {
+               Var                *var = (Var *) node;
+
+               if (var->varlevelsup == 0)
+                       result = bms_make_singleton(var->varno);
+       }
+       else if (IsA(node, List))
+       {
+               ListCell   *l;
+
+               foreach(l, (List *) node)
+               {
+                       result = bms_join(result,
+                                                         find_nonnullable_rels_walker(lfirst(l),
+                                                                                                                  top_level));
+               }
+       }
+       else if (IsA(node, FuncExpr))
+       {
+               FuncExpr   *expr = (FuncExpr *) node;
+
+               if (func_strict(expr->funcid))
+                       result = find_nonnullable_rels_walker((Node *) expr->args, false);
+       }
+       else if (IsA(node, OpExpr))
+       {
+               OpExpr     *expr = (OpExpr *) node;
+
+               if (op_strict(expr->opno))
+                       result = find_nonnullable_rels_walker((Node *) expr->args, false);
+       }
+       else if (IsA(node, ScalarArrayOpExpr))
+       {
+               /* Strict if it's "foo op ANY array" and op is strict */
+               ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
+
+               if (expr->useOr && op_strict(expr->opno))
+                       result = find_nonnullable_rels_walker((Node *) expr->args, false);
+       }
+       else if (IsA(node, BoolExpr))
+       {
+               BoolExpr   *expr = (BoolExpr *) node;
+
+               /* NOT is strict, others are not */
+               if (expr->boolop == NOT_EXPR)
+                       result = find_nonnullable_rels_walker((Node *) expr->args, false);
+       }
+       else if (IsA(node, RelabelType))
+       {
+               RelabelType *expr = (RelabelType *) node;
+
+               result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);
+       }
+       else if (IsA(node, ConvertRowtypeExpr))
+       {
+               /* not clear this is useful, but it can't hurt */
+               ConvertRowtypeExpr *expr = (ConvertRowtypeExpr *) node;
+
+               result = find_nonnullable_rels_walker((Node *) expr->arg, top_level);
+       }
+       else if (IsA(node, NullTest))
+       {
+               NullTest   *expr = (NullTest *) node;
+
+               /*
+                * IS NOT NULL can be considered strict, but only at top level; else
+                * we might have something like NOT (x IS NOT NULL).
+                */
+               if (top_level && expr->nulltesttype == IS_NOT_NULL)
+                       result = find_nonnullable_rels_walker((Node *) expr->arg, false);
+       }
+       else if (IsA(node, BooleanTest))
+       {
+               BooleanTest *expr = (BooleanTest *) node;
+
+               /*
+                * Appropriate boolean tests are strict at top level.
+                */
+               if (top_level &&
+                       (expr->booltesttype == IS_TRUE ||
+                        expr->booltesttype == IS_FALSE ||
+                        expr->booltesttype == IS_NOT_UNKNOWN))
+                       result = find_nonnullable_rels_walker((Node *) expr->arg, false);
+       }
+       return result;
+}
+
+
 /*****************************************************************************
  *             Check for "pseudo-constant" clauses
  *****************************************************************************/
@@ -2794,7 +2920,8 @@ expression_tree_walker(Node *node,
                case T_CaseTestExpr:
                case T_SetToDefault:
                case T_RangeTblRef:
-                       /* primitive node types with no subnodes */
+               case T_OuterJoinInfo:
+                       /* primitive node types with no expression subnodes */
                        break;
                case T_Aggref:
                        return walker(((Aggref *) node)->target, context);
@@ -3191,7 +3318,8 @@ expression_tree_mutator(Node *node,
                case T_CaseTestExpr:
                case T_SetToDefault:
                case T_RangeTblRef:
-                       /* primitive node types with no subnodes */
+               case T_OuterJoinInfo:
+                       /* primitive node types with no expression subnodes */
                        return (Node *) copyObject(node);
                case T_Aggref:
                        {
index ed8f4148e33e9264532b991b9e0278aa1e26cd0e..cef0c63a66ff26035130b215202bbd249be3102c 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.73 2005/11/22 18:17:15 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.74 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -133,7 +133,6 @@ make_reloptinfo(PlannerInfo *root, int relid, RelOptKind reloptkind)
        rel->baserestrictinfo = NIL;
        rel->baserestrictcost.startup = 0;
        rel->baserestrictcost.per_tuple = 0;
-       rel->outerjoinset = NULL;
        rel->joininfo = NIL;
        rel->index_outer_relids = NULL;
        rel->index_inner_paths = NIL;
@@ -369,7 +368,6 @@ build_join_rel(PlannerInfo *root,
        joinrel->baserestrictinfo = NIL;
        joinrel->baserestrictcost.startup = 0;
        joinrel->baserestrictcost.per_tuple = 0;
-       joinrel->outerjoinset = NULL;
        joinrel->joininfo = NIL;
        joinrel->index_outer_relids = NULL;
        joinrel->index_inner_paths = NIL;
index 01e66f69762e49d36d3f7d29cd80caea474b6e31..b7c4599a03045f8b45eae0390427ee53e5290694 100644 (file)
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.301 2005/11/22 18:17:26 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.302 2005/12/20 02:30:36 tgl Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -45,7 +45,7 @@
 #include "optimizer/cost.h"
 #include "optimizer/geqo.h"
 #include "optimizer/paths.h"
-#include "optimizer/prep.h"
+#include "optimizer/planmain.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "postmaster/autovacuum.h"
@@ -1010,7 +1010,7 @@ static struct config_int ConfigureNamesInt[] =
                {"join_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER,
                        gettext_noop("Sets the FROM-list size beyond which JOIN constructs are not "
                                                 "flattened."),
-                       gettext_noop("The planner will flatten explicit inner JOIN "
+                       gettext_noop("The planner will flatten explicit JOIN "
                        "constructs into lists of FROM items whenever a list of no more "
                                                 "than this many items would result.")
                },
index e9ec4b8ad65aa0d7234fb0addf4b579aec174dfb..0d6a4871ac2f32b01aef0fc800aabf1739a03fd4 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/nodes.h,v 1.178 2005/11/22 18:17:30 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.179 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -186,6 +186,7 @@ typedef enum NodeTag
        T_PathKeyItem,
        T_RestrictInfo,
        T_InnerIndexscanInfo,
+       T_OuterJoinInfo,
        T_InClauseInfo,
 
        /*
index 1cdd64b26ebb0130a5effb6366ddc3f763262812..40fda441b97cd5de2f55ae8ae3e32d9068f91dc6 100644 (file)
@@ -10,7 +10,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/primnodes.h,v 1.109 2005/10/15 02:49:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.110 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -845,12 +845,7 @@ typedef struct TargetEntry
  * or qualified join.  Also, FromExpr nodes can appear to denote an
  * ordinary cross-product join ("FROM foo, bar, baz WHERE ...").
  * FromExpr is like a JoinExpr of jointype JOIN_INNER, except that it
- * may have any number of child nodes, not just two.  Also, there is an
- * implementation-defined difference: the planner is allowed to join the
- * children of a FromExpr using whatever join order seems good to it.
- * At present, JoinExpr nodes are always joined in exactly the order
- * implied by the jointree structure (except the planner may choose to
- * swap inner and outer members of a join pair).
+ * may have any number of child nodes, not just two.
  *
  * NOTE: the top level of a Query's jointree is always a FromExpr.
  * Even if the jointree contains no rels, there will be a FromExpr.
index aa6217d0313a31f269e6a4a8706eb174ba7ed353..1d490fc17bf895821415d5a5dca3e55928206e6f 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.121 2005/11/26 22:14:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.122 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -97,6 +97,8 @@ typedef struct PlannerInfo
        List       *full_join_clauses;          /* list of RestrictInfos for full
                                                                                 * outer join clauses */
 
+       List       *oj_info_list;       /* list of OuterJoinInfos */
+
        List       *in_info_list;       /* list of InClauseInfos */
 
        List       *query_pathkeys; /* desired pathkeys for query_planner(), and
@@ -201,10 +203,6 @@ typedef struct PlannerInfo
  *                                     participates (only used for base rels)
  *             baserestrictcost - Estimated cost of evaluating the baserestrictinfo
  *                                     clauses at a single tuple (only used for base rels)
- *             outerjoinset - For a base rel: if the rel appears within the nullable
- *                                     side of an outer join, the set of all relids
- *                                     participating in the highest such outer join; else NULL.
- *                                     Otherwise, unused.
  *             joininfo  - List of RestrictInfo nodes, containing info about each
  *                                     join clause in which this relation participates
  *             index_outer_relids - only used for base rels; set of outer relids
@@ -228,10 +226,6 @@ typedef struct PlannerInfo
  * We store baserestrictcost in the RelOptInfo (for base relations) because
  * we know we will need it at least once (to price the sequential scan)
  * and may need it multiple times to price index scans.
- *
- * outerjoinset is used to ensure correct placement of WHERE clauses that
- * apply to outer-joined relations; we must not apply such WHERE clauses
- * until after the outer join is performed.
  *----------
  */
 typedef enum RelOptKind
@@ -277,7 +271,6 @@ typedef struct RelOptInfo
        List       *baserestrictinfo;           /* RestrictInfo structures (if base
                                                                                 * rel) */
        QualCost        baserestrictcost;               /* cost of evaluating the above */
-       Relids          outerjoinset;   /* set of base relids */
        List       *joininfo;           /* RestrictInfo structures for join clauses
                                                                 * involving this rel */
 
@@ -830,6 +823,40 @@ typedef struct InnerIndexscanInfo
        Path       *best_innerpath; /* best inner indexscan, or NULL if none */
 } InnerIndexscanInfo;
 
+/*
+ * Outer join info.
+ *
+ * One-sided outer joins constrain the order of joining partially but not
+ * completely.  We flatten such joins into the planner's top-level list of
+ * relations to join, but record information about each outer join in an
+ * OuterJoinInfo struct.  These structs are kept in the PlannerInfo node's
+ * oj_info_list.
+ *
+ * min_lefthand and min_righthand are the sets of base relids that must be
+ * available on each side when performing the outer join.  lhs_strict is
+ * true if the outer join's condition cannot succeed when the LHS variables
+ * are all NULL (this means that the outer join can commute with upper-level
+ * outer joins even if it appears in their RHS).  We don't bother to set
+ * lhs_strict for FULL JOINs, however.
+ *
+ * It is not valid for either min_lefthand or min_righthand to be empty sets;
+ * if they were, this would break the logic that enforces join order.
+ *
+ * Note: OuterJoinInfo directly represents only LEFT JOIN and FULL JOIN;
+ * RIGHT JOIN is handled by switching the inputs to make it a LEFT JOIN.
+ * We make an OuterJoinInfo for FULL JOINs even though there is no flexibility
+ * of planning for them, because this simplifies make_join_rel()'s API.
+ */
+
+typedef struct OuterJoinInfo
+{
+       NodeTag         type;
+       Relids          min_lefthand;   /* base relids in minimum LHS for join */
+       Relids          min_righthand;  /* base relids in minimum RHS for join */
+       bool            is_full_join;   /* it's a FULL OUTER JOIN */
+       bool            lhs_strict;             /* joinclause is strict for some LHS rel */
+} OuterJoinInfo;
+
 /*
  * IN clause info.
  *
index bf00f2cc97e730823ebd0e18ddfe22abc595ee00..0d3770dc5c4f438097e0a73e6f9dc9f6fb4a3c12 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/clauses.h,v 1.80 2005/10/15 02:49:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.81 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -57,6 +57,7 @@ extern bool contain_subplans(Node *clause);
 extern bool contain_mutable_functions(Node *clause);
 extern bool contain_volatile_functions(Node *clause);
 extern bool contain_nonstrict_functions(Node *clause);
+extern Relids find_nonnullable_rels(Node *clause);
 
 extern bool is_pseudo_constant_clause(Node *clause);
 extern bool is_pseudo_constant_clause_relids(Node *clause, Relids relids);
index eba65c699c057afa05400616a04598fbbdcf5109..afe3a70d71bd489647dcd5bbb7b9e5a1dc25b01e 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/paths.h,v 1.89 2005/11/25 19:47:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.90 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,8 +23,7 @@
 extern bool enable_geqo;
 extern int     geqo_threshold;
 
-extern RelOptInfo *make_one_rel(PlannerInfo *root);
-extern RelOptInfo *make_fromexpr_rel(PlannerInfo *root, FromExpr *from);
+extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist);
 
 #ifdef OPTIMIZER_DEBUG
 extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
@@ -88,10 +87,8 @@ extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
  *       routines to determine which relations to join
  */
 extern List *make_rels_by_joins(PlannerInfo *root, int level, List **joinrels);
-extern RelOptInfo *make_jointree_rel(PlannerInfo *root, Node *jtnode);
 extern RelOptInfo *make_join_rel(PlannerInfo *root,
-                         RelOptInfo *rel1, RelOptInfo *rel2,
-                         JoinType jointype);
+                         RelOptInfo *rel1, RelOptInfo *rel2);
 
 /*
  * pathkeys.c
index 0e78933f79284b3141b313453e63110a79dd992e..97d2287b5c8fde0236c66cacd4e6aa991b285ccc 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.90 2005/10/15 02:49:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.91 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -65,10 +65,12 @@ extern bool is_projection_capable_plan(Plan *plan);
 /*
  * prototypes for plan/initsplan.c
  */
+extern int     from_collapse_limit;
+extern int     join_collapse_limit;
+
 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,
-                                                bool below_outer_join);
+extern List *deconstruct_jointree(PlannerInfo *root);
 extern void process_implied_equality(PlannerInfo *root,
                                                 Node *item1, Node *item2,
                                                 Oid sortop1, Oid sortop2,
index c26e6491f34f7d596371016c669a7a3768454aee..ce89771b1799d088bd9aa11e68a7f562364e6dce 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/prep.h,v 1.52 2005/10/15 02:49:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.53 2005/12/20 02:30:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 /*
  * prototypes for prepjointree.c
  */
-extern int     from_collapse_limit;
-extern int     join_collapse_limit;
-
 extern Node *pull_up_IN_clauses(PlannerInfo *root, Node *node);
 extern Node *pull_up_subqueries(PlannerInfo *root, Node *jtnode,
                                   bool below_outer_join);
 extern void reduce_outer_joins(PlannerInfo *root);
-extern Node *simplify_jointree(PlannerInfo *root, Node *jtnode);
 extern Relids get_relids_in_jointree(Node *jtnode);
 extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);