<!--
-$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>
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>
</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>
<!--
-$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">
</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);
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;
</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>
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>
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>
* 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 $
*
*-------------------------------------------------------------------------
*/
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
*/
case T_RestrictInfo:
retval = _copyRestrictInfo(from);
break;
+ case T_OuterJoinInfo:
+ retval = _copyOuterJoinInfo(from);
+ break;
case T_InClauseInfo:
retval = _copyInClauseInfo(from);
break;
* 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 $
*
*-------------------------------------------------------------------------
*/
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)
{
case T_RestrictInfo:
retval = _equalRestrictInfo(a, b);
break;
+ case T_OuterJoinInfo:
+ retval = _equalOuterJoinInfo(a, b);
+ break;
case T_InClauseInfo:
retval = _equalInClauseInfo(a, b);
break;
*
*
* 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*
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);
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);
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)
{
case T_InnerIndexscanInfo:
_outInnerIndexscanInfo(str, obj);
break;
+ case T_OuterJoinInfo:
+ _outOuterJoinInfo(str, obj);
+ break;
case T_InClauseInfo:
_outInClauseInfo(str, obj);
break;
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
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
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
---------------------
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
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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)
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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
* 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;
/*
* 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.
}
/*
- * 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);
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
/*
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);
{
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,
}
/*----------
- * 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
* 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);
}
{
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);
}
{
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
/*
- * 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;
}
* (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));
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;
}
}
*
*
* 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);
/*****************************************************************************
*
- * 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);
}
/*
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))
{
Relids leftids,
rightids,
nonnullable_rels,
- nullable_rels;
+ ojscope;
+ List *leftjoinlist,
+ *rightjoinlist;
+ OuterJoinInfo *ojinfo;
ListCell *qual;
/*
* 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:
(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
* '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;
*/
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
* 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;
* 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;
/*
* 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))
{
* 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.
* 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);
}
/*
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
{
Query *parse = root->parse;
List *constant_quals;
+ List *joinlist;
RelOptInfo *final_rel;
Path *cheapestpath;
Path *sortedpath;
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.
* 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
*/
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
/*
* 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");
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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.
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 */ );
* 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
*
*
* 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 */
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);
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.
*/
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 */
*/
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);
}
(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
*
/*
* 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)
*
*
* 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
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);
}
+/*
+ * 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
*****************************************************************************/
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);
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:
{
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
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;
* 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 $
*
*--------------------------------------------------------------------
*/
#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"
{"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.")
},
* 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 $
*
*-------------------------------------------------------------------------
*/
T_PathKeyItem,
T_RestrictInfo,
T_InnerIndexscanInfo,
+ T_OuterJoinInfo,
T_InClauseInfo,
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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.
* 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 $
*
*-------------------------------------------------------------------------
*/
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
* 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
* 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
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 */
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.
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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,
* 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);