Commit e3b98527 authored by Tom Lane's avatar Tom Lane

Teach planner how to rearrange join order for some classes of OUTER JOIN.

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.
parent 1a6aaaa6
<!-- <!--
$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"> <chapter Id="runtime-config">
<title>Server Configuration</title> <title>Server Configuration</title>
...@@ -2028,6 +2028,7 @@ SELECT * FROM parent WHERE key = 2400; ...@@ -2028,6 +2028,7 @@ SELECT * FROM parent WHERE key = 2400;
this many items. Smaller values reduce planning time but may this many items. Smaller values reduce planning time but may
yield inferior query plans. The default is 8. It is usually yield inferior query plans. The default is 8. It is usually
wise to keep this less than <xref linkend="guc-geqo-threshold">. wise to keep this less than <xref linkend="guc-geqo-threshold">.
For more information see <xref linkend="explicit-joins">.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -2039,48 +2040,24 @@ SELECT * FROM parent WHERE key = 2400; ...@@ -2039,48 +2040,24 @@ SELECT * FROM parent WHERE key = 2400;
</indexterm> </indexterm>
<listitem> <listitem>
<para> <para>
The planner will rewrite explicit inner <literal>JOIN</> The planner will rewrite explicit <literal>JOIN</>
constructs into lists of <literal>FROM</> items whenever a constructs (except <literal>FULL JOIN</>s) into lists of
list of no more than this many items in total would <literal>FROM</> items whenever a list of no more than this many items
result. Prior to <productname>PostgreSQL</> 7.4, joins would result. Smaller values reduce planning time but may
specified via the <literal>JOIN</literal> construct would yield inferior query plans.
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>
</para> </para>
<para> <para>
By default, this variable is set the same as By default, this variable is set the same as
<varname>from_collapse_limit</varname>, which is appropriate <varname>from_collapse_limit</varname>, which is appropriate
for most uses. Setting it to 1 prevents any reordering of 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 specified in the query will be the actual order in which the
relations are joined. The query planner does not always choose relations are joined. The query planner does not always choose
the optimal join order; advanced users may elect to the optimal join order; advanced users may elect to
temporarily set this variable to 1, and then specify the join temporarily set this variable to 1, and then specify the join
order they desire explicitly. Another consequence of setting order they desire explicitly.
this variable to 1 is that the query planner will behave more For more information see <xref linkend="explicit-joins">.
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).
</para> </para>
</listitem> </listitem>
</varlistentry> </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"> <chapter id="performance-tips">
...@@ -627,7 +627,7 @@ SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id; ...@@ -627,7 +627,7 @@ SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
</para> </para>
<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 than it does for plain (inner) joins. For example, consider
<programlisting> <programlisting>
SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id); 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); ...@@ -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. 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 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 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>
<para> <para>
Explicit inner join syntax (<literal>INNER JOIN</>, <literal>CROSS Explicit inner join syntax (<literal>INNER JOIN</>, <literal>CROSS
JOIN</>, or unadorned <literal>JOIN</>) is semantically the same as JOIN</>, or unadorned <literal>JOIN</>) is semantically the same as
listing the input relations in <literal>FROM</>, so it does not need to listing the input relations in <literal>FROM</>, so it does not
constrain the join order. But it is possible to instruct the constrain the join order.
<productname>PostgreSQL</productname> query planner to treat </para>
explicit inner <literal>JOIN</>s as constraining the join order anyway.
<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: For example, these three queries are logically equivalent:
<programlisting> <programlisting>
SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id; 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); ...@@ -660,7 +674,8 @@ SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
</para> </para>
<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. set the <xref linkend="guc-join-collapse-limit"> run-time parameter to 1.
(Other possible values are discussed below.) (Other possible values are discussed below.)
</para> </para>
...@@ -697,9 +712,9 @@ FROM x, y, ...@@ -697,9 +712,9 @@ FROM x, y,
WHERE somethingelse; WHERE somethingelse;
</programlisting> </programlisting>
This situation might arise from use of a view that contains a join; 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, the view's <literal>SELECT</> rule will be inserted in place of the view
yielding a query much like the above. Normally, the planner will try reference, yielding a query much like the above. Normally, the planner
to collapse the subquery into the parent, yielding will try to collapse the subquery into the parent, yielding
<programlisting> <programlisting>
SELECT * FROM x, y, a, b, c WHERE something AND somethingelse; SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;
</programlisting> </programlisting>
...@@ -722,12 +737,12 @@ SELECT * FROM x, y, a, b, c WHERE something AND somethingelse; ...@@ -722,12 +737,12 @@ SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;
linkend="guc-join-collapse-limit"> linkend="guc-join-collapse-limit">
are similarly named because they do almost the same thing: one controls are similarly named because they do almost the same thing: one controls
when the planner will <quote>flatten out</> subselects, and the 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 you would either set <varname>join_collapse_limit</> equal to
<varname>from_collapse_limit</> (so that explicit joins and subselects <varname>from_collapse_limit</> (so that explicit joins and subselects
act similarly) or set <varname>join_collapse_limit</> to 1 (if you want 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 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. time and run time.
</para> </para>
</sect1> </sect1>
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * 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) ...@@ -1277,6 +1277,22 @@ _copyRestrictInfo(RestrictInfo *from)
return newnode; 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 * _copyInClauseInfo
*/ */
...@@ -2906,6 +2922,9 @@ copyObject(void *from) ...@@ -2906,6 +2922,9 @@ copyObject(void *from)
case T_RestrictInfo: case T_RestrictInfo:
retval = _copyRestrictInfo(from); retval = _copyRestrictInfo(from);
break; break;
case T_OuterJoinInfo:
retval = _copyOuterJoinInfo(from);
break;
case T_InClauseInfo: case T_InClauseInfo:
retval = _copyInClauseInfo(from); retval = _copyInClauseInfo(from);
break; break;
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * 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) ...@@ -613,6 +613,17 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b)
return true; 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 static bool
_equalInClauseInfo(InClauseInfo *a, InClauseInfo *b) _equalInClauseInfo(InClauseInfo *a, InClauseInfo *b)
{ {
...@@ -1954,6 +1965,9 @@ equal(void *a, void *b) ...@@ -1954,6 +1965,9 @@ equal(void *a, void *b)
case T_RestrictInfo: case T_RestrictInfo:
retval = _equalRestrictInfo(a, b); retval = _equalRestrictInfo(a, b);
break; break;
case T_OuterJoinInfo:
retval = _equalOuterJoinInfo(a, b);
break;
case T_InClauseInfo: case T_InClauseInfo:
retval = _equalInClauseInfo(a, b); retval = _equalInClauseInfo(a, b);
break; break;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* Every node type that can appear in stored rules' parsetrees *must* * Every node type that can appear in stored rules' parsetrees *must*
...@@ -1167,6 +1167,7 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node) ...@@ -1167,6 +1167,7 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
WRITE_NODE_FIELD(left_join_clauses); WRITE_NODE_FIELD(left_join_clauses);
WRITE_NODE_FIELD(right_join_clauses); WRITE_NODE_FIELD(right_join_clauses);
WRITE_NODE_FIELD(full_join_clauses); WRITE_NODE_FIELD(full_join_clauses);
WRITE_NODE_FIELD(oj_info_list);
WRITE_NODE_FIELD(in_info_list); WRITE_NODE_FIELD(in_info_list);
WRITE_NODE_FIELD(query_pathkeys); WRITE_NODE_FIELD(query_pathkeys);
WRITE_NODE_FIELD(group_pathkeys); WRITE_NODE_FIELD(group_pathkeys);
...@@ -1201,7 +1202,6 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node) ...@@ -1201,7 +1202,6 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node)
WRITE_FLOAT_FIELD(tuples, "%.0f"); WRITE_FLOAT_FIELD(tuples, "%.0f");
WRITE_NODE_FIELD(subplan); WRITE_NODE_FIELD(subplan);
WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(baserestrictinfo);
WRITE_BITMAPSET_FIELD(outerjoinset);
WRITE_NODE_FIELD(joininfo); WRITE_NODE_FIELD(joininfo);
WRITE_BITMAPSET_FIELD(index_outer_relids); WRITE_BITMAPSET_FIELD(index_outer_relids);
WRITE_NODE_FIELD(index_inner_paths); WRITE_NODE_FIELD(index_inner_paths);
...@@ -1265,6 +1265,17 @@ _outInnerIndexscanInfo(StringInfo str, InnerIndexscanInfo *node) ...@@ -1265,6 +1265,17 @@ _outInnerIndexscanInfo(StringInfo str, InnerIndexscanInfo *node)
WRITE_NODE_FIELD(best_innerpath); 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 static void
_outInClauseInfo(StringInfo str, InClauseInfo *node) _outInClauseInfo(StringInfo str, InClauseInfo *node)
{ {
...@@ -2019,6 +2030,9 @@ _outNode(StringInfo str, void *obj) ...@@ -2019,6 +2030,9 @@ _outNode(StringInfo str, void *obj)
case T_InnerIndexscanInfo: case T_InnerIndexscanInfo:
_outInnerIndexscanInfo(str, obj); _outInnerIndexscanInfo(str, obj);
break; break;
case T_OuterJoinInfo:
_outOuterJoinInfo(str, obj);
break;
case T_InClauseInfo: case T_InClauseInfo:
_outInClauseInfo(str, obj); _outInClauseInfo(str, obj);
break; break;
......
...@@ -40,10 +40,11 @@ is derived from the cheapest Path for the RelOptInfo that includes all the ...@@ -40,10 +40,11 @@ is derived from the cheapest Path for the RelOptInfo that includes all the
base rels of the query. base rels of the query.
Possible Paths for a primitive table relation include plain old sequential 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 scan, plus index scans for any indexes that exist on the table, plus bitmap
base relation just has one Path, a "SubqueryScan" path (which links to the index scans using one or more indexes. A subquery base relation just has
subplan that was built by a recursive invocation of the planner). Likewise one Path, a "SubqueryScan" path (which links to the subplan that was built
a function-RTE base relation has only one possible Path. 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. 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 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. ...@@ -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 Otherwise we have to figure out how to join the base relations into a
single join relation. single join relation.
2) If the query's FROM clause contains explicit JOIN clauses, we join 2) Normally, any explicit JOIN clauses are "flattened" so that we just
those pairs of relations in exactly the tree structure indicated by the have a list of relations to join. However, FULL OUTER JOIN clauses are
JOIN clauses. (This is absolutely necessary when dealing with outer JOINs. never flattened, and other kinds of JOIN might not be either, if the
For inner JOINs we have more flexibility in theory, but don't currently flattening process is stopped by join_collapse_limit or from_collapse_limit
exploit it in practice.) For each such join pair, we generate a Path restrictions. Therefore, we end up with a planning problem that contains
for each feasible join method, and select the cheapest Path. Note that both lists of relations to be joined in any order, and JOIN nodes that
the JOIN clause structure determines the join Path structure, but it force a particular join order. For each un-flattened JOIN node, we join
doesn't constrain the join implementation method at each join (nestloop, exactly that pair of relations (after recursively planning their inputs,
merge, hash), nor does it say which rel is considered outer or inner at if the inputs aren't single base relations). We generate a Path for each
each join. We consider all these possibilities in building Paths. 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 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. that are either base rels or joinrels constructed per un-flattened JOIN
We can join these rels together in any order the planner sees fit. 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: The standard (non-GEQO) planner does this as follows:
Consider joining each RelOptInfo to each other RelOptInfo specified in its 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 ...@@ -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, 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 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 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 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 for it or the cheapest path with the desired ordering (if that's cheaper
than applying a sort to the cheapest other path). 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 Pulling up subqueries
--------------------- ---------------------
...@@ -180,13 +251,13 @@ of the join tree. Each FROM-list is planned using the dynamic-programming ...@@ -180,13 +251,13 @@ of the join tree. Each FROM-list is planned using the dynamic-programming
search method described above. search method described above.
If pulling up a subquery produces a FROM-list as a direct child of another 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 FROM-list, then we can merge the two FROM-lists together. Once that's
two FROM-lists together. Once that's done, the subquery is an absolutely done, the subquery is an absolutely integral part of the outer query and
integral part of the outer query and will not constrain the join tree will not constrain the join tree search space at all. However, that could
search space at all. However, that could result in unpleasant growth of result in unpleasant growth of planning time, since the dynamic-programming
planning time, since the dynamic-programming search has runtime exponential search has runtime exponential in the number of FROM-items considered.
in the number of FROM-items considered. Therefore, we don't merge Therefore, we don't merge FROM-lists if the result would have too many
FROM-lists if the result would have too many FROM-items in one list. FROM-items in one list.
Optimizer Functions Optimizer Functions
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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) ...@@ -216,12 +216,11 @@ gimme_tree(Gene *tour, int num_gene, GeqoEvalData *evaldata)
/* /*
* Construct a RelOptInfo representing the join of these two input * Construct a RelOptInfo representing the join of these two input
* relations. These are always inner joins. Note that we expect * relations. Note that we expect the joinrel not to exist in
* the joinrel not to exist in root->join_rel_list yet, and so the * root->join_rel_list yet, and so the paths constructed for it
* paths constructed for it will only include the ones we want. * will only include the ones we want.
*/ */
joinrel = make_join_rel(evaldata->root, outer_rel, inner_rel, joinrel = make_join_rel(evaldata->root, outer_rel, inner_rel);
JOIN_INNER);
/* Can't pop stack here if join order is not valid */ /* Can't pop stack here if join order is not valid */
if (!joinrel) if (!joinrel)
...@@ -262,6 +261,20 @@ desirable_join(PlannerInfo *root, ...@@ -262,6 +261,20 @@ desirable_join(PlannerInfo *root,
if (have_relevant_joinclause(outer_rel, inner_rel)) if (have_relevant_joinclause(outer_rel, inner_rel))
return true; 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 * 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 * to improve the odds that we will find a valid solution in a case where
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -51,6 +51,7 @@ static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte); Index rti, RangeTblEntry *rte);
static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte); RangeTblEntry *rte);
static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
static RelOptInfo *make_one_rel_by_joins(PlannerInfo *root, int levels_needed, static RelOptInfo *make_one_rel_by_joins(PlannerInfo *root, int levels_needed,
List *initial_rels); List *initial_rels);
static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery,
...@@ -73,7 +74,7 @@ static void recurse_push_qual(Node *setOp, 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. * single rel that represents the join of all base rels in the query.
*/ */
RelOptInfo * RelOptInfo *
make_one_rel(PlannerInfo *root) make_one_rel(PlannerInfo *root, List *joinlist)
{ {
RelOptInfo *rel; RelOptInfo *rel;
...@@ -85,10 +86,7 @@ make_one_rel(PlannerInfo *root) ...@@ -85,10 +86,7 @@ make_one_rel(PlannerInfo *root)
/* /*
* Generate access paths for the entire join tree. * Generate access paths for the entire join tree.
*/ */
Assert(root->parse->jointree != NULL && rel = make_rel_from_joinlist(root, joinlist);
IsA(root->parse->jointree, FromExpr));
rel = make_fromexpr_rel(root, root->parse->jointree);
/* /*
* The result should join all and only the query's base rels. * 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) ...@@ -528,43 +526,65 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
} }
/* /*
* make_fromexpr_rel * make_rel_from_joinlist
* Build access paths for a FromExpr jointree node. * 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 * static RelOptInfo *
make_fromexpr_rel(PlannerInfo *root, FromExpr *from) make_rel_from_joinlist(PlannerInfo *root, List *joinlist)
{ {
int levels_needed; int levels_needed;
List *initial_rels = NIL; List *initial_rels;
ListCell *jt; 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 * dynamic-programming algorithm we must employ to consider all ways of
* joining the child nodes. * joining the child nodes.
*/ */
levels_needed = list_length(from->fromlist); levels_needed = list_length(joinlist);
if (levels_needed <= 0) if (levels_needed <= 0)
return NULL; /* nothing to do? */ 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 * 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, initial_rels = lappend(initial_rels, thisrel);
make_jointree_rel(root, jtnode));
} }
if (levels_needed == 1) if (levels_needed == 1)
{ {
/* /*
* Single jointree node, so we're done. * Single joinlist node, so we're done.
*/ */
return (RelOptInfo *) linitial(initial_rels); return (RelOptInfo *) linitial(initial_rels);
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -25,7 +25,7 @@ static List *make_rels_by_clause_joins(PlannerInfo *root,
static List *make_rels_by_clauseless_joins(PlannerInfo *root, static List *make_rels_by_clauseless_joins(PlannerInfo *root,
RelOptInfo *old_rel, RelOptInfo *old_rel,
ListCell *other_rels); 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) ...@@ -86,15 +86,16 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels)
other_rels); other_rels);
/* /*
* An exception occurs when there is a clauseless join inside an * An exception occurs when there is a clauseless join inside a
* IN (sub-SELECT) construct. Here, the members of the subselect * construct that restricts join order, i.e., an outer join RHS
* all have join clauses (against the stuff outside the IN), but * or an IN (sub-SELECT) construct. Here, the rel may well have
* they *must* be joined to each other before we can make use of * join clauses against stuff outside the OJ RHS or IN sub-SELECT,
* those join clauses. So do the clauseless join bit. * 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. * 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, new_rels = make_rels_by_clauseless_joins(root,
old_rel, old_rel,
other_rels); other_rels);
...@@ -169,8 +170,7 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels) ...@@ -169,8 +170,7 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels)
{ {
RelOptInfo *jrel; RelOptInfo *jrel;
jrel = make_join_rel(root, old_rel, new_rel, jrel = make_join_rel(root, old_rel, new_rel);
JOIN_INNER);
/* Avoid making duplicate entries ... */ /* Avoid making duplicate entries ... */
if (jrel) if (jrel)
result_rels = list_append_unique_ptr(result_rels, result_rels = list_append_unique_ptr(result_rels,
...@@ -219,8 +219,8 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels) ...@@ -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 * When OJs or IN clauses are involved, there may be no legal way
* an N-way join for some values of N. For example consider * to make an N-way join for some values of N. For example consider
* *
* SELECT ... FROM t1 WHERE * SELECT ... FROM t1 WHERE
* x IN (SELECT ... FROM t2,t3 WHERE ...) AND * x IN (SELECT ... FROM t2,t3 WHERE ...) AND
...@@ -231,11 +231,12 @@ make_rels_by_joins(PlannerInfo *root, int level, List **joinrels) ...@@ -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 * to accept failure at level 4 and go on to discover a workable
* bushy plan at level 5. * 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. * 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); elog(ERROR, "failed to build any %d-way joins", level);
} }
...@@ -273,7 +274,7 @@ make_rels_by_clause_joins(PlannerInfo *root, ...@@ -273,7 +274,7 @@ make_rels_by_clause_joins(PlannerInfo *root,
{ {
RelOptInfo *jrel; RelOptInfo *jrel;
jrel = make_join_rel(root, old_rel, other_rel, JOIN_INNER); jrel = make_join_rel(root, old_rel, other_rel);
if (jrel) if (jrel)
result = lcons(jrel, result); result = lcons(jrel, result);
} }
...@@ -312,7 +313,7 @@ make_rels_by_clauseless_joins(PlannerInfo *root, ...@@ -312,7 +313,7 @@ make_rels_by_clauseless_joins(PlannerInfo *root,
{ {
RelOptInfo *jrel; 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 * 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, ...@@ -328,85 +329,31 @@ make_rels_by_clauseless_joins(PlannerInfo *root,
/* /*
* is_inside_IN * has_join_restriction
* Detect whether the specified relation is inside an IN (sub-SELECT). * Detect whether the specified relation has join-order restrictions
* * due to being inside an OJ RHS or 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.
*/ */
static bool static bool
is_inside_IN(PlannerInfo *root, RelOptInfo *rel) has_join_restriction(PlannerInfo *root, RelOptInfo *rel)
{ {
ListCell *l; 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 true;
} }
return false;
}
/* foreach(l, root->in_info_list)
* 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))
{ {
JoinExpr *j = (JoinExpr *) jtnode; InClauseInfo *ininfo = (InClauseInfo *) lfirst(l);
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
return rel; if (bms_is_subset(rel->relids, ininfo->righthand))
return true;
} }
else return false;
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(jtnode));
return NULL; /* keep compiler quiet */
} }
...@@ -418,16 +365,19 @@ make_jointree_rel(PlannerInfo *root, Node *jtnode) ...@@ -418,16 +365,19 @@ make_jointree_rel(PlannerInfo *root, Node *jtnode)
* (The join rel may already contain paths generated from other * (The join rel may already contain paths generated from other
* pairs of rels that add up to the same set of base rels.) * 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 * NB: will return NULL if attempted join is not valid. This can happen
* happen when working with IN clauses that have been turned into joins. * when working with outer joins, or with IN clauses that have been turned
* into joins.
*/ */
RelOptInfo * RelOptInfo *
make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
JoinType jointype)
{ {
Relids joinrelids; Relids joinrelids;
JoinType jointype;
bool is_valid_inner;
RelOptInfo *joinrel; RelOptInfo *joinrel;
List *restrictlist; List *restrictlist;
ListCell *l;
/* We should never try to join two overlapping sets of rels. */ /* We should never try to join two overlapping sets of rels. */
Assert(!bms_overlap(rel1->relids, rel2->relids)); Assert(!bms_overlap(rel1->relids, rel2->relids));
...@@ -436,94 +386,176 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, ...@@ -436,94 +386,176 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
joinrelids = bms_union(rel1->relids, rel2->relids); joinrelids = bms_union(rel1->relids, rel2->relids);
/* /*
* If we are implementing IN clauses as joins, there are some joins that * If we have any outer joins, the proposed join might be illegal; and
* are illegal. Check to see if the proposed join is trouble. We can skip * in any case we have to determine its join type. Scan the OJ list
* the work if looking at an outer join, however, because only top-level * for conflicts.
* joins might be affected.
*/ */
if (jointype == JOIN_INNER) jointype = JOIN_INNER; /* default if no match to an OJ */
{ is_valid_inner = true;
ListCell *l;
foreach(l, root->in_info_list) foreach(l, root->oj_info_list)
{ {
InClauseInfo *ininfo = (InClauseInfo *) lfirst(l); OuterJoinInfo *ojinfo = (OuterJoinInfo *) 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 * This OJ is not relevant unless its RHS overlaps the proposed join.
* clause isn't relevant yet. * (Check this first as a fast path for dismissing most irrelevant OJs
*/ * quickly.)
if (bms_is_subset(joinrelids, ininfo->righthand)) */
continue; if (!bms_overlap(ojinfo->min_righthand, joinrelids))
continue;
/* /*
* Cannot join if proposed join contains rels not in the RHS *and* * Also, not relevant if proposed join is fully contained within RHS
* contains only part of the RHS. We must build the complete RHS * (ie, we're still building up the RHS).
* (subselect's join) before it can be joined to rels outside the */
* subselect. if (bms_is_subset(joinrelids, ojinfo->min_righthand))
*/ continue;
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 * Also, not relevant if OJ is already done within either input.
* other rel(s). */
* if (bms_is_subset(ojinfo->min_lefthand, rel1->relids) &&
* If we already joined IN's RHS to any other rels in either input bms_is_subset(ojinfo->min_righthand, rel1->relids))
* path, then this join is not constrained (the necessary work was continue;
* done at the lower level where that join occurred). if (bms_is_subset(ojinfo->min_lefthand, rel2->relids) &&
*/ bms_is_subset(ojinfo->min_righthand, rel2->relids))
if (bms_is_subset(ininfo->righthand, rel1->relids) && continue;
!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 * If one input contains min_lefthand and the other contains
* innerrel is exactly RHS; conversely JOIN_REVERSE_IN handles * min_righthand, then we can perform the OJ at this join.
* RHS/LHS. *
* * Barf if we get matches to more than one OJ (is that possible?)
* JOIN_UNIQUE_OUTER will work if outerrel is exactly RHS; */
* conversely JOIN_UNIQUE_INNER will work if innerrel is exactly if (bms_is_subset(ojinfo->min_lefthand, rel1->relids) &&
* RHS. bms_is_subset(ojinfo->min_righthand, rel2->relids))
* {
* But none of these will work if we already found another IN that
* needs to trigger here.
*/
if (jointype != JOIN_INNER) if (jointype != JOIN_INNER)
{ {
/* invalid join path */
bms_free(joinrelids); bms_free(joinrelids);
return NULL; return NULL;
} }
if (bms_is_subset(ininfo->lefthand, rel1->relids) && jointype = ojinfo->is_full_join ? JOIN_FULL : JOIN_LEFT;
bms_equal(ininfo->righthand, rel2->relids)) }
jointype = JOIN_IN; else if (bms_is_subset(ojinfo->min_lefthand, rel2->relids) &&
else if (bms_is_subset(ininfo->lefthand, rel2->relids) && bms_is_subset(ojinfo->min_righthand, rel1->relids))
bms_equal(ininfo->righthand, rel1->relids)) {
jointype = JOIN_REVERSE_IN; if (jointype != JOIN_INNER)
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 */ /* invalid join path */
bms_free(joinrelids); bms_free(joinrelids);
return NULL; 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;
} }
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -34,16 +34,25 @@ ...@@ -34,16 +34,25 @@
#include "utils/syscache.h" #include "utils/syscache.h"
static void mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, /* These parameters are set by GUC */
Relids outerrels); 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, static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_pushed_down, bool is_pushed_down,
bool is_deduced, bool is_deduced,
bool below_outer_join, bool below_outer_join,
Relids outerjoin_nonnullable, Relids qualscope,
Relids qualscope); Relids ojscope,
static void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids outerjoin_nonnullable);
Relids where_needed);
static bool qual_is_redundant(PlannerInfo *root, RestrictInfo *restrictinfo, static bool qual_is_redundant(PlannerInfo *root, RestrictInfo *restrictinfo,
List *restrictlist); List *restrictlist);
static void check_mergejoinable(RestrictInfo *restrictinfo); static void check_mergejoinable(RestrictInfo *restrictinfo);
...@@ -162,66 +171,117 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed) ...@@ -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 * Recursively scan the query's join tree for WHERE and JOIN/ON qual
* clauses, and add these to the appropriate restrictinfo and joininfo * clauses, and add these to the appropriate restrictinfo and joininfo
* lists belonging to base RelOptInfos. Also, base RelOptInfos are marked * lists belonging to base RelOptInfos. Also, add OuterJoinInfo nodes
* with outerjoinset information, to aid in proper positioning of qual * to root->oj_info_list for any outer joins appearing in the query tree.
* clauses that appear above outer joins. * 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 * The "joinlist" result is a list of items that are either RangeTblRef
* is TRUE if this node is within the nullable side of a higher-level outer * jointree nodes or sub-joinlists. All the items at the same level of
* join. * 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 * 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 * 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) * 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 * 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 * NULL row to be incorrectly emitted by the join. Therefore, we artificially
* within the nullable side(s) of an outer join are marked with * OR the minimum-relids of such an outer join into the required_relids of
* outerjoinset = set of Relids used at the outer join node. * clauses appearing above it. This forces those clauses to be delayed until
* This set will be added to the set of rels referenced by quals using such * application of the outer join (or maybe even higher in the join tree).
* a rel, thereby forcing them up the join tree to the right level. */
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 * Inputs:
* the set of base Relids involved in its own level of join. This is just an * jtnode is the jointree node to examine
* internal convenience; no outside callers pay attention to the result. * 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 static List *
distribute_quals_to_rels(PlannerInfo *root, Node *jtnode, deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
bool below_outer_join) Relids *qualscope)
{ {
Relids result = NULL; List *joinlist;
if (jtnode == NULL) if (jtnode == NULL)
return result; {
*qualscope = NULL;
return NIL;
}
if (IsA(jtnode, RangeTblRef)) if (IsA(jtnode, RangeTblRef))
{ {
int varno = ((RangeTblRef *) jtnode)->rtindex; int varno = ((RangeTblRef *) jtnode)->rtindex;
/* No quals to deal with, just return correct result */ /* 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)) else if (IsA(jtnode, FromExpr))
{ {
FromExpr *f = (FromExpr *) jtnode; FromExpr *f = (FromExpr *) jtnode;
int remaining;
ListCell *l; 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) foreach(l, f->fromlist)
{ {
result = bms_add_members(result, Relids sub_qualscope;
distribute_quals_to_rels(root, List *sub_joinlist;
lfirst(l), int sub_members;
below_outer_join));
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, ...@@ -231,7 +291,7 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
foreach(l, (List *) f->quals) foreach(l, (List *) f->quals)
distribute_qual_to_rels(root, (Node *) lfirst(l), distribute_qual_to_rels(root, (Node *) lfirst(l),
true, false, below_outer_join, true, false, below_outer_join,
NULL, result); *qualscope, NULL, NULL);
} }
else if (IsA(jtnode, JoinExpr)) else if (IsA(jtnode, JoinExpr))
{ {
...@@ -239,7 +299,10 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode, ...@@ -239,7 +299,10 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
Relids leftids, Relids leftids,
rightids, rightids,
nonnullable_rels, nonnullable_rels,
nullable_rels; ojscope;
List *leftjoinlist,
*rightjoinlist;
OuterJoinInfo *ojinfo;
ListCell *qual; ListCell *qual;
/* /*
...@@ -249,55 +312,55 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode, ...@@ -249,55 +312,55 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
* Then we place our own join quals, which are restricted by lower * 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 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 and they mention the outer side. Finally, if this is an
* outer join, we mark baserels contained within the inner side(s) * outer join, we create an oj_info_list entry for the join. This
* with our own rel set; this will prevent quals above us in the join * will prevent quals above us in the join tree that use those rels
* tree that use those rels from being pushed down below this level. * from being pushed down below this level. (It's okay for upper
* (It's okay for upper quals to be pushed down to the outer side, * quals to be pushed down to the outer side, however.)
* however.)
*/ */
switch (j->jointype) switch (j->jointype)
{ {
case JOIN_INNER: case JOIN_INNER:
leftids = distribute_quals_to_rels(root, j->larg, leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join); below_outer_join,
rightids = distribute_quals_to_rels(root, j->rarg, &leftids);
below_outer_join); rightjoinlist = deconstruct_recurse(root, j->rarg,
below_outer_join,
result = bms_union(leftids, rightids); &rightids);
*qualscope = bms_union(leftids, rightids);
/* Inner join adds no restrictions for quals */ /* Inner join adds no restrictions for quals */
nonnullable_rels = NULL; nonnullable_rels = NULL;
nullable_rels = NULL;
break; break;
case JOIN_LEFT: case JOIN_LEFT:
leftids = distribute_quals_to_rels(root, j->larg, leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join); below_outer_join,
rightids = distribute_quals_to_rels(root, j->rarg, &leftids);
true); rightjoinlist = deconstruct_recurse(root, j->rarg,
true,
result = bms_union(leftids, rightids); &rightids);
*qualscope = bms_union(leftids, rightids);
nonnullable_rels = leftids; nonnullable_rels = leftids;
nullable_rels = rightids;
break; break;
case JOIN_FULL: case JOIN_FULL:
leftids = distribute_quals_to_rels(root, j->larg, leftjoinlist = deconstruct_recurse(root, j->larg,
true); true,
rightids = distribute_quals_to_rels(root, j->rarg, &leftids);
true); rightjoinlist = deconstruct_recurse(root, j->rarg,
true,
result = bms_union(leftids, rightids); &rightids);
*qualscope = bms_union(leftids, rightids);
/* each side is both outer and inner */ /* each side is both outer and inner */
nonnullable_rels = result; nonnullable_rels = *qualscope;
nullable_rels = result;
break; break;
case JOIN_RIGHT: case JOIN_RIGHT:
leftids = distribute_quals_to_rels(root, j->larg, /* notice we switch leftids and rightids */
true); leftjoinlist = deconstruct_recurse(root, j->larg,
rightids = distribute_quals_to_rels(root, j->rarg, true,
below_outer_join); &rightids);
rightjoinlist = deconstruct_recurse(root, j->rarg,
result = bms_union(leftids, rightids); below_outer_join,
nonnullable_rels = rightids; &leftids);
nullable_rels = leftids; *qualscope = bms_union(leftids, rightids);
nonnullable_rels = leftids;
break; break;
case JOIN_UNION: case JOIN_UNION:
...@@ -309,73 +372,184 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode, ...@@ -309,73 +372,184 @@ distribute_quals_to_rels(PlannerInfo *root, Node *jtnode,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("UNION JOIN is not implemented"))); errmsg("UNION JOIN is not implemented")));
nonnullable_rels = NULL; /* keep compiler quiet */ nonnullable_rels = NULL; /* keep compiler quiet */
nullable_rels = NULL; leftjoinlist = rightjoinlist = NIL;
break; break;
default: default:
elog(ERROR, "unrecognized join type: %d", elog(ERROR, "unrecognized join type: %d",
(int) j->jointype); (int) j->jointype);
nonnullable_rels = NULL; /* keep compiler quiet */ nonnullable_rels = NULL; /* keep compiler quiet */
nullable_rels = NULL; leftjoinlist = rightjoinlist = NIL;
break; 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) foreach(qual, (List *) j->quals)
distribute_qual_to_rels(root, (Node *) lfirst(qual), distribute_qual_to_rels(root, (Node *) lfirst(qual),
false, false, below_outer_join, 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 else
{
elog(ERROR, "unrecognized node type: %d", elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(jtnode)); (int) nodeTag(jtnode));
return result; joinlist = NIL; /* keep compiler quiet */
}
return joinlist;
} }
/* /*
* mark_baserels_for_outer_join * make_outerjoininfo
* Mark all base rels listed in 'rels' as having the given outerjoinset. * 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 static OuterJoinInfo *
mark_baserels_for_outer_join(PlannerInfo *root, Relids rels, Relids outerrels) make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
bool is_full_join, Node *clause)
{ {
Relids tmprelids; OuterJoinInfo *ojinfo = makeNode(OuterJoinInfo);
int relno; 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); /* Remember whether the clause is strict for any LHS relations */
while ((relno = bms_first_member(tmprelids)) >= 0) 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 * For a lower OJ in our LHS, if our join condition uses the lower
* be within the new outer join set. * 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 * For a lower OJ in our RHS, if our join condition does not use the
* rels appearing on the nullable side of an outer join. (It's * lower join's RHS and the lower OJ's join condition is strict, we
* somewhat unclear what that would mean, anyway: what should we mark * can interchange the ordering of the two OJs, so exclude the lower
* when a result row is generated from no element of the nullable * RHS from our min_righthand.
* 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.
*/ */
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)) ojinfo->min_righthand = bms_del_members(ojinfo->min_righthand,
ereport(ERROR, otherinfo->min_righthand);
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to the nullable side of an outer join")));
} }
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 * distribute_qual_to_rels
* Add clause information to either the baserestrictinfo or joininfo list * 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) ...@@ -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 * '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 * 'below_outer_join': TRUE if the qual is from a JOIN/ON that is below the
* nullable side of a higher-level outer join. * 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 * 'outerjoin_nonnullable': NULL if not an outer-join qual, else the set of
* baserels appearing on the outer (nonnullable) side of the join * 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 * 'qualscope' identifies what level of JOIN the qual came from syntactically.
* level qual (WHERE qual), qualscope lists all baserel ids and in addition * 'ojscope' is needed if we decide to force the qual up to the outer-join
* 'is_pushed_down' will be TRUE. * level, which will be ojscope not necessarily qualscope.
*/ */
static void static void
distribute_qual_to_rels(PlannerInfo *root, Node *clause, distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_pushed_down, bool is_pushed_down,
bool is_deduced, bool is_deduced,
bool below_outer_join, bool below_outer_join,
Relids outerjoin_nonnullable, Relids qualscope,
Relids qualscope) Relids ojscope,
Relids outerjoin_nonnullable)
{ {
Relids relids; Relids relids;
bool outerjoin_delayed; bool outerjoin_delayed;
...@@ -427,16 +606,20 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -427,16 +606,20 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
*/ */
if (!bms_is_subset(relids, qualscope)) if (!bms_is_subset(relids, qualscope))
elog(ERROR, "JOIN qualification may not refer to other relations"); 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 * If the clause is variable-free, we force it to be evaluated at its
* original syntactic level. Note that this should not happen for * original syntactic level. Note that this should not happen for
* top-level clauses, because query_planner() special-cases them. But it * 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 * 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)) if (bms_is_empty(relids))
relids = qualscope; relids = ojscope ? ojscope : qualscope;
/* /*
* Check to see if clause application must be delayed by outer-join * Check to see if clause application must be delayed by outer-join
...@@ -451,6 +634,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -451,6 +634,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
* be delayed by outer-join rules. * be delayed by outer-join rules.
*/ */
Assert(bms_equal(relids, qualscope)); Assert(bms_equal(relids, qualscope));
Assert(!ojscope);
/* Needn't feed it back for more deductions */ /* Needn't feed it back for more deductions */
outerjoin_delayed = false; outerjoin_delayed = false;
maybe_equijoin = false; maybe_equijoin = false;
...@@ -471,7 +655,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -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, * result, so we treat it the same as an ordinary inner-join qual,
* except for not setting maybe_equijoin (see below). * except for not setting maybe_equijoin (see below).
*/ */
relids = qualscope; Assert(ojscope);
relids = ojscope;
outerjoin_delayed = true; outerjoin_delayed = true;
/* /*
...@@ -493,28 +678,27 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -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 * 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 * outer joins that can null any of these rels and are below the
* syntactic location of the given qual. To enforce the latter, scan * 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, * into the clause's own reference list. At the time we are called,
* the outerjoinset of each baserel will show exactly those outer * the oj_info_list contains only outer joins below this qual.
* joins that are below the qual in the join tree.
*/ */
Relids addrelids = NULL; Relids addrelids = NULL;
Relids tmprelids; ListCell *l;
int relno;
outerjoin_delayed = false; outerjoin_delayed = false;
tmprelids = bms_copy(relids); foreach(l, root->oj_info_list)
while ((relno = bms_first_member(tmprelids)) >= 0)
{ {
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; outerjoin_delayed = true;
} }
} }
bms_free(tmprelids);
if (bms_is_subset(addrelids, relids)) if (bms_is_subset(addrelids, relids))
{ {
...@@ -553,9 +737,11 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, ...@@ -553,9 +737,11 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
* its original syntactic level. This allows us to distinguish original * its original syntactic level. This allows us to distinguish original
* JOIN/ON quals from higher-level quals pushed down to the same joinrel. * JOIN/ON quals from higher-level quals pushed down to the same joinrel.
* A qual originating from WHERE is always considered "pushed down". * 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) 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. * Build the RestrictInfo node itself.
...@@ -864,7 +1050,7 @@ process_implied_equality(PlannerInfo *root, ...@@ -864,7 +1050,7 @@ process_implied_equality(PlannerInfo *root,
* taken for an original JOIN/ON clause. * taken for an original JOIN/ON clause.
*/ */
distribute_qual_to_rels(root, (Node *) clause, distribute_qual_to_rels(root, (Node *) clause,
true, true, false, NULL, relids); true, true, false, relids, NULL, NULL);
} }
/* /*
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -83,6 +83,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
{ {
Query *parse = root->parse; Query *parse = root->parse;
List *constant_quals; List *constant_quals;
List *joinlist;
RelOptInfo *final_rel; RelOptInfo *final_rel;
Path *cheapestpath; Path *cheapestpath;
Path *sortedpath; Path *sortedpath;
...@@ -134,6 +135,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, ...@@ -134,6 +135,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
root->left_join_clauses = NIL; root->left_join_clauses = NIL;
root->right_join_clauses = NIL; root->right_join_clauses = NIL;
root->full_join_clauses = NIL; root->full_join_clauses = NIL;
root->oj_info_list = NIL;
/* /*
* Construct RelOptInfo nodes for all base relations in query. * Construct RelOptInfo nodes for all base relations in query.
...@@ -144,7 +146,8 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, ...@@ -144,7 +146,8 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
* Examine the targetlist and qualifications, adding entries to baserel * Examine the targetlist and qualifications, adding entries to baserel
* targetlists for all referenced Vars. Restrict and join clauses are * targetlists for all referenced Vars. Restrict and join clauses are
* added to appropriate lists belonging to the mentioned relations. We * 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 * Note: all subplan nodes will have "flat" (var-only) tlists. This
* implies that all expression evaluations are done at the root of the * 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, ...@@ -154,7 +157,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
*/ */
build_base_rel_tlists(root, tlist); 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 * 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, ...@@ -175,7 +178,7 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
/* /*
* Ready to do the primary planning. * 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) if (!final_rel || !final_rel->cheapest_total_path)
elog(ERROR, "failed to construct the join relation"); elog(ERROR, "failed to construct the join relation");
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -352,17 +352,6 @@ subquery_planner(Query *parse, double tuple_fraction,
if (root->hasOuterJoins) if (root->hasOuterJoins)
reduce_outer_joins(root); 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 * Do the main planning. If we have an inherited target relation, that
* needs special processing, else go straight to grouping_planner. * needs special processing, else go straight to grouping_planner.
...@@ -567,6 +556,8 @@ inheritance_planner(PlannerInfo *root, List *inheritlist) ...@@ -567,6 +556,8 @@ inheritance_planner(PlannerInfo *root, List *inheritlist)
adjust_inherited_attrs((Node *) root->in_info_list, adjust_inherited_attrs((Node *) root->in_info_list,
parentRTindex, parentOID, parentRTindex, parentOID,
childRTindex, childOID); childRTindex, childOID);
/* There shouldn't be any OJ info to translate, though */
Assert(subroot.oj_info_list == NIL);
/* Generate plan */ /* Generate plan */
subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ ); subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ );
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
* pull_up_subqueries * pull_up_subqueries
* do expression preprocessing (including flattening JOIN alias vars) * do expression preprocessing (including flattening JOIN alias vars)
* reduce_outer_joins * reduce_outer_joins
* simplify_jointree
* *
* *
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
...@@ -16,7 +15,7 @@ ...@@ -16,7 +15,7 @@
* *
* *
* IDENTIFICATION * 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 $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -31,11 +30,6 @@ ...@@ -31,11 +30,6 @@
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
/* These parameters are set by GUC */
int from_collapse_limit;
int join_collapse_limit;
typedef struct reduce_outer_joins_state typedef struct reduce_outer_joins_state
{ {
Relids relids; /* base relids within this subtree */ Relids relids; /* base relids within this subtree */
...@@ -52,7 +46,6 @@ static void reduce_outer_joins_pass2(Node *jtnode, ...@@ -52,7 +46,6 @@ static void reduce_outer_joins_pass2(Node *jtnode,
reduce_outer_joins_state *state, reduce_outer_joins_state *state,
PlannerInfo *root, PlannerInfo *root,
Relids nonnullable_rels); 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, static void fix_in_clause_relids(List *in_info_list, int varno,
Relids subrelids); Relids subrelids);
static Node *find_jointree_node_for_rel(Node *jtnode, int relid); 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) ...@@ -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, root->in_info_list = list_concat(root->in_info_list,
subroot->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. * Miscellaneous housekeeping.
*/ */
...@@ -695,7 +695,7 @@ reduce_outer_joins_pass2(Node *jtnode, ...@@ -695,7 +695,7 @@ reduce_outer_joins_pass2(Node *jtnode,
Relids pass_nonnullable; Relids pass_nonnullable;
/* Scan quals to see if we can add any nonnullability constraints */ /* 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, pass_nonnullable = bms_add_members(pass_nonnullable,
nonnullable_rels); nonnullable_rels);
/* And recurse --- but only into interesting subtrees */ /* And recurse --- but only into interesting subtrees */
...@@ -772,7 +772,7 @@ reduce_outer_joins_pass2(Node *jtnode, ...@@ -772,7 +772,7 @@ reduce_outer_joins_pass2(Node *jtnode,
*/ */
if (jointype != JOIN_FULL) 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, local_nonnullable = bms_add_members(local_nonnullable,
nonnullable_rels); nonnullable_rels);
} }
...@@ -805,256 +805,6 @@ reduce_outer_joins_pass2(Node *jtnode, ...@@ -805,256 +805,6 @@ reduce_outer_joins_pass2(Node *jtnode,
(int) nodeTag(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 * fix_in_clause_relids: update RT-index sets of InClauseInfo nodes
* *
...@@ -1128,9 +878,6 @@ get_relids_in_jointree(Node *jtnode) ...@@ -1128,9 +878,6 @@ get_relids_in_jointree(Node *jtnode)
/* /*
* get_relids_for_join: get set of base RT indexes making up a join * 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 Relids
get_relids_for_join(PlannerInfo *root, int joinrelid) get_relids_for_join(PlannerInfo *root, int joinrelid)
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -69,6 +69,7 @@ static bool contain_subplans_walker(Node *node, void *context); ...@@ -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_mutable_functions_walker(Node *node, void *context);
static bool contain_volatile_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 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 bool set_coercionform_dontcare_walker(Node *node, void *context);
static Node *eval_const_expressions_mutator(Node *node, static Node *eval_const_expressions_mutator(Node *node,
eval_const_expressions_context *context); eval_const_expressions_context *context);
...@@ -861,6 +862,131 @@ contain_nonstrict_functions_walker(Node *node, void *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 * Check for "pseudo-constant" clauses
*****************************************************************************/ *****************************************************************************/
...@@ -2794,7 +2920,8 @@ expression_tree_walker(Node *node, ...@@ -2794,7 +2920,8 @@ expression_tree_walker(Node *node,
case T_CaseTestExpr: case T_CaseTestExpr:
case T_SetToDefault: case T_SetToDefault:
case T_RangeTblRef: case T_RangeTblRef:
/* primitive node types with no subnodes */ case T_OuterJoinInfo:
/* primitive node types with no expression subnodes */
break; break;
case T_Aggref: case T_Aggref:
return walker(((Aggref *) node)->target, context); return walker(((Aggref *) node)->target, context);
...@@ -3191,7 +3318,8 @@ expression_tree_mutator(Node *node, ...@@ -3191,7 +3318,8 @@ expression_tree_mutator(Node *node,
case T_CaseTestExpr: case T_CaseTestExpr:
case T_SetToDefault: case T_SetToDefault:
case T_RangeTblRef: case T_RangeTblRef:
/* primitive node types with no subnodes */ case T_OuterJoinInfo:
/* primitive node types with no expression subnodes */
return (Node *) copyObject(node); return (Node *) copyObject(node);
case T_Aggref: case T_Aggref:
{ {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -133,7 +133,6 @@ make_reloptinfo(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->baserestrictinfo = NIL; rel->baserestrictinfo = NIL;
rel->baserestrictcost.startup = 0; rel->baserestrictcost.startup = 0;
rel->baserestrictcost.per_tuple = 0; rel->baserestrictcost.per_tuple = 0;
rel->outerjoinset = NULL;
rel->joininfo = NIL; rel->joininfo = NIL;
rel->index_outer_relids = NULL; rel->index_outer_relids = NULL;
rel->index_inner_paths = NIL; rel->index_inner_paths = NIL;
...@@ -369,7 +368,6 @@ build_join_rel(PlannerInfo *root, ...@@ -369,7 +368,6 @@ build_join_rel(PlannerInfo *root,
joinrel->baserestrictinfo = NIL; joinrel->baserestrictinfo = NIL;
joinrel->baserestrictcost.startup = 0; joinrel->baserestrictcost.startup = 0;
joinrel->baserestrictcost.per_tuple = 0; joinrel->baserestrictcost.per_tuple = 0;
joinrel->outerjoinset = NULL;
joinrel->joininfo = NIL; joinrel->joininfo = NIL;
joinrel->index_outer_relids = NULL; joinrel->index_outer_relids = NULL;
joinrel->index_inner_paths = NIL; joinrel->index_inner_paths = NIL;
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* Written by Peter Eisentraut <peter_e@gmx.net>. * Written by Peter Eisentraut <peter_e@gmx.net>.
* *
* IDENTIFICATION * 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 @@ ...@@ -45,7 +45,7 @@
#include "optimizer/cost.h" #include "optimizer/cost.h"
#include "optimizer/geqo.h" #include "optimizer/geqo.h"
#include "optimizer/paths.h" #include "optimizer/paths.h"
#include "optimizer/prep.h" #include "optimizer/planmain.h"
#include "parser/parse_expr.h" #include "parser/parse_expr.h"
#include "parser/parse_relation.h" #include "parser/parse_relation.h"
#include "postmaster/autovacuum.h" #include "postmaster/autovacuum.h"
...@@ -1010,7 +1010,7 @@ static struct config_int ConfigureNamesInt[] = ...@@ -1010,7 +1010,7 @@ static struct config_int ConfigureNamesInt[] =
{"join_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER, {"join_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER,
gettext_noop("Sets the FROM-list size beyond which JOIN constructs are not " gettext_noop("Sets the FROM-list size beyond which JOIN constructs are not "
"flattened."), "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 " "constructs into lists of FROM items whenever a list of no more "
"than this many items would result.") "than this many items would result.")
}, },
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -186,6 +186,7 @@ typedef enum NodeTag
T_PathKeyItem, T_PathKeyItem,
T_RestrictInfo, T_RestrictInfo,
T_InnerIndexscanInfo, T_InnerIndexscanInfo,
T_OuterJoinInfo,
T_InClauseInfo, T_InClauseInfo,
/* /*
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -845,12 +845,7 @@ typedef struct TargetEntry
* or qualified join. Also, FromExpr nodes can appear to denote an * or qualified join. Also, FromExpr nodes can appear to denote an
* ordinary cross-product join ("FROM foo, bar, baz WHERE ..."). * ordinary cross-product join ("FROM foo, bar, baz WHERE ...").
* FromExpr is like a JoinExpr of jointype JOIN_INNER, except that it * 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 * may have any number of child nodes, not just two.
* 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).
* *
* NOTE: the top level of a Query's jointree is always a FromExpr. * 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. * Even if the jointree contains no rels, there will be a FromExpr.
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -97,6 +97,8 @@ typedef struct PlannerInfo
List *full_join_clauses; /* list of RestrictInfos for full List *full_join_clauses; /* list of RestrictInfos for full
* outer join clauses */ * outer join clauses */
List *oj_info_list; /* list of OuterJoinInfos */
List *in_info_list; /* list of InClauseInfos */ List *in_info_list; /* list of InClauseInfos */
List *query_pathkeys; /* desired pathkeys for query_planner(), and List *query_pathkeys; /* desired pathkeys for query_planner(), and
...@@ -201,10 +203,6 @@ typedef struct PlannerInfo ...@@ -201,10 +203,6 @@ typedef struct PlannerInfo
* participates (only used for base rels) * participates (only used for base rels)
* baserestrictcost - Estimated cost of evaluating the baserestrictinfo * baserestrictcost - Estimated cost of evaluating the baserestrictinfo
* clauses at a single tuple (only used for base rels) * 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 * joininfo - List of RestrictInfo nodes, containing info about each
* join clause in which this relation participates * join clause in which this relation participates
* index_outer_relids - only used for base rels; set of outer relids * index_outer_relids - only used for base rels; set of outer relids
...@@ -228,10 +226,6 @@ typedef struct PlannerInfo ...@@ -228,10 +226,6 @@ typedef struct PlannerInfo
* We store baserestrictcost in the RelOptInfo (for base relations) because * We store baserestrictcost in the RelOptInfo (for base relations) because
* we know we will need it at least once (to price the sequential scan) * we know we will need it at least once (to price the sequential scan)
* and may need it multiple times to price index scans. * 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 typedef enum RelOptKind
...@@ -277,7 +271,6 @@ typedef struct RelOptInfo ...@@ -277,7 +271,6 @@ typedef struct RelOptInfo
List *baserestrictinfo; /* RestrictInfo structures (if base List *baserestrictinfo; /* RestrictInfo structures (if base
* rel) */ * rel) */
QualCost baserestrictcost; /* cost of evaluating the above */ QualCost baserestrictcost; /* cost of evaluating the above */
Relids outerjoinset; /* set of base relids */
List *joininfo; /* RestrictInfo structures for join clauses List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */ * involving this rel */
...@@ -830,6 +823,40 @@ typedef struct InnerIndexscanInfo ...@@ -830,6 +823,40 @@ typedef struct InnerIndexscanInfo
Path *best_innerpath; /* best inner indexscan, or NULL if none */ Path *best_innerpath; /* best inner indexscan, or NULL if none */
} InnerIndexscanInfo; } 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. * IN clause info.
* *
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -57,6 +57,7 @@ extern bool contain_subplans(Node *clause);
extern bool contain_mutable_functions(Node *clause); extern bool contain_mutable_functions(Node *clause);
extern bool contain_volatile_functions(Node *clause); extern bool contain_volatile_functions(Node *clause);
extern bool contain_nonstrict_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(Node *clause);
extern bool is_pseudo_constant_clause_relids(Node *clause, Relids relids); extern bool is_pseudo_constant_clause_relids(Node *clause, Relids relids);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -23,8 +23,7 @@
extern bool enable_geqo; extern bool enable_geqo;
extern int geqo_threshold; extern int geqo_threshold;
extern RelOptInfo *make_one_rel(PlannerInfo *root); extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist);
extern RelOptInfo *make_fromexpr_rel(PlannerInfo *root, FromExpr *from);
#ifdef OPTIMIZER_DEBUG #ifdef OPTIMIZER_DEBUG
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel); extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
...@@ -88,10 +87,8 @@ extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel, ...@@ -88,10 +87,8 @@ extern void add_paths_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
* routines to determine which relations to join * routines to determine which relations to join
*/ */
extern List *make_rels_by_joins(PlannerInfo *root, int level, List **joinrels); 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, extern RelOptInfo *make_join_rel(PlannerInfo *root,
RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *rel1, RelOptInfo *rel2);
JoinType jointype);
/* /*
* pathkeys.c * pathkeys.c
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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); ...@@ -65,10 +65,12 @@ extern bool is_projection_capable_plan(Plan *plan);
/* /*
* prototypes for plan/initsplan.c * 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 add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
extern Relids distribute_quals_to_rels(PlannerInfo *root, Node *jtnode, extern List *deconstruct_jointree(PlannerInfo *root);
bool below_outer_join);
extern void process_implied_equality(PlannerInfo *root, extern void process_implied_equality(PlannerInfo *root,
Node *item1, Node *item2, Node *item1, Node *item2,
Oid sortop1, Oid sortop2, Oid sortop1, Oid sortop2,
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -21,14 +21,10 @@ ...@@ -21,14 +21,10 @@
/* /*
* prototypes for prepjointree.c * 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_IN_clauses(PlannerInfo *root, Node *node);
extern Node *pull_up_subqueries(PlannerInfo *root, Node *jtnode, extern Node *pull_up_subqueries(PlannerInfo *root, Node *jtnode,
bool below_outer_join); bool below_outer_join);
extern void reduce_outer_joins(PlannerInfo *root); 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_in_jointree(Node *jtnode);
extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid); extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment