1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-13 16:22:44 +03:00

First cut at full support for OUTER JOINs. There are still a few loose

ends to clean up (see my message of same date to pghackers), but mostly
it works.  INITDB REQUIRED!
This commit is contained in:
Tom Lane
2000-09-12 21:07:18 +00:00
parent b5c0ab278b
commit ed5003c584
93 changed files with 6386 additions and 4262 deletions

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.62 2000/05/31 00:28:22 petere Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.63 2000/09/12 21:06:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -26,7 +26,9 @@ int geqo_rels = DEFAULT_GEQO_RELS;
static void set_base_rel_pathlist(Query *root);
static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed);
static List *build_jointree_rels(Query *root);
static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed,
List *initial_rels);
#ifdef OPTIMIZER_DEBUG
static void debug_print_rel(Query *root, RelOptInfo *rel);
@@ -43,27 +45,38 @@ RelOptInfo *
make_one_rel(Query *root)
{
int levels_needed;
List *initial_rels;
/*
* Set the number of join (not nesting) levels yet to be processed.
* Count the number of top-level jointree nodes. This is the depth
* of the dynamic-programming algorithm we must employ to consider
* all ways of joining the top-level nodes. Currently, we build
* JoinExpr joins in exactly the order implied by the join expression,
* so no dynamic-programming search is needed within a JoinExpr.
*/
levels_needed = length(root->base_rel_list);
levels_needed = length(root->jointree);
if (levels_needed <= 0)
return NULL;
return NULL; /* nothing to do? */
/*
* Generate access paths for the base rels.
*/
set_base_rel_pathlist(root);
/*
* Construct a list of rels corresponding to the toplevel jointree nodes.
* This may contain both base rels and rels constructed according to
* explicit JOIN directives.
*/
initial_rels = build_jointree_rels(root);
if (levels_needed == 1)
{
/*
* Single relation, no more processing is required.
* Single jointree node, so we're done.
*/
return (RelOptInfo *) lfirst(root->base_rel_list);
return (RelOptInfo *) lfirst(initial_rels);
}
else
{
@@ -71,7 +84,7 @@ make_one_rel(Query *root)
/*
* Generate join tree.
*/
return make_one_rel_by_joins(root, levels_needed);
return make_one_rel_by_joins(root, levels_needed, initial_rels);
}
}
@@ -125,20 +138,47 @@ set_base_rel_pathlist(Query *root)
}
}
/*
* build_jointree_rels
* Construct a RelOptInfo for each item in the query's jointree.
*
* At present, we handle explicit joins in the FROM clause exactly as
* specified, with no search for other join orders. Only the cross-product
* joins at the top level are involved in the dynamic-programming search.
*/
static List *
build_jointree_rels(Query *root)
{
List *rels = NIL;
List *jt;
foreach(jt, root->jointree)
{
Node *jtnode = (Node *) lfirst(jt);
rels = lappend(rels, make_rel_from_jointree(root, jtnode));
}
return rels;
}
/*
* make_one_rel_by_joins
* Find all possible joinpaths for a query by successively finding ways
* to join component relations into join relations.
*
* 'levels_needed' is the number of iterations needed, ie, the number of
* base relations present in the query
* independent jointree items in the query. This is > 1.
*
* 'initial_rels' is a list of RelOptInfo nodes for each independent
* jointree item. These are the components to be joined together.
*
* Returns the final level of join relations, i.e., the relation that is
* the result of joining all the original relations together.
*/
static RelOptInfo *
make_one_rel_by_joins(Query *root, int levels_needed)
make_one_rel_by_joins(Query *root, int levels_needed, List *initial_rels)
{
List **joinitems;
int lev;
RelOptInfo *rel;
@@ -152,34 +192,35 @@ make_one_rel_by_joins(Query *root, int levels_needed)
/*
* We employ a simple "dynamic programming" algorithm: we first find
* all ways to build joins of two base relations, then all ways to
* build joins of three base relations (from two-base-rel joins and
* other base rels), then four-base-rel joins, and so on until we have
* considered all ways to join all N relations into one rel.
* all ways to build joins of two jointree items, then all ways to
* build joins of three items (from two-item joins and single items),
* then four-item joins, and so on until we have considered all ways
* to join all the items into one rel.
*
* joinitems[j] is a list of all the j-item rels. Initially we set
* joinitems[1] to represent all the single-jointree-item relations.
*/
joinitems = (List **) palloc((levels_needed+1) * sizeof(List *));
MemSet(joinitems, 0, (levels_needed+1) * sizeof(List *));
joinitems[1] = initial_rels;
for (lev = 2; lev <= levels_needed; lev++)
{
List *first_old_rel = root->join_rel_list;
List *x;
/*
* Determine all possible pairs of relations to be joined at this
* level, and build paths for making each one from every available
* pair of lower-level relations. Results are prepended to
* root->join_rel_list.
* pair of lower-level relations.
*/
make_rels_by_joins(root, lev);
joinitems[lev] = make_rels_by_joins(root, lev, joinitems);
/*
* The relations created at the current level will appear at the
* front of root->join_rel_list.
* Do cleanup work on each just-processed rel.
*/
foreach(x, root->join_rel_list)
foreach(x, joinitems[lev])
{
if (x == first_old_rel)
break; /* no more rels added at this level */
rel = (RelOptInfo *) lfirst(x);
#ifdef NOT_USED
@@ -202,14 +243,12 @@ make_one_rel_by_joins(Query *root, int levels_needed)
}
/*
* Now, the front of the join_rel_list should be the single rel
* We should have a single rel at the final level,
* representing the join of all the base rels.
*/
Assert(length(root->join_rel_list) > 0);
rel = (RelOptInfo *) lfirst(root->join_rel_list);
Assert(length(rel->relids) == levels_needed);
Assert(length(root->join_rel_list) == 1 ||
length(((RelOptInfo *) lsecond(root->join_rel_list))->relids) < levels_needed);
Assert(length(joinitems[levels_needed]) == 1);
rel = (RelOptInfo *) lfirst(joinitems[levels_needed]);
Assert(length(rel->relids) == length(root->base_rel_list));
return rel;
}

View File

@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.94 2000/08/24 03:29:04 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.95 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1482,7 +1482,9 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
{
List *clausegroup = lfirst(i);
IndexPath *pathnode = makeNode(IndexPath);
List *indexquals;
List *indexquals = NIL;
bool alljoinquals = true;
List *temp;
/* XXX this code ought to be merged with create_index_path? */
@@ -1496,7 +1498,16 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
*/
pathnode->path.pathkeys = NIL;
indexquals = get_actual_clauses(clausegroup);
/* extract bare indexqual clauses, check whether all from JOIN/ON */
foreach(temp, clausegroup)
{
RestrictInfo *clause = (RestrictInfo *) lfirst(temp);
indexquals = lappend(indexquals, clause->clause);
if (! clause->isjoinqual)
alljoinquals = false;
}
/* expand special operators to indexquals the executor can handle */
indexquals = expand_indexqual_conditions(indexquals);
@@ -1514,6 +1525,8 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
/* joinrelids saves the rels needed on the outer side of the join */
pathnode->joinrelids = lfirst(outerrelids_list);
pathnode->alljoinquals = alljoinquals;
/*
* We must compute the estimated number of output rows for the
* indexscan. This is less than rel->rows because of the

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.55 2000/05/30 00:49:47 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.56 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -25,27 +25,32 @@
#include "utils/lsyscache.h"
static void sort_inner_and_outer(Query *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, List *mergeclause_list);
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, List *mergeclause_list,
JoinType jointype);
static void match_unsorted_outer(Query *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, List *mergeclause_list);
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, List *mergeclause_list,
JoinType jointype);
#ifdef NOT_USED
static void match_unsorted_inner(Query *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, List *mergeclause_list);
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, List *mergeclause_list,
JoinType jointype);
#endif
static void hash_inner_and_outer(Query *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist);
static Path *best_innerjoin(List *join_paths, List *outer_relid);
RelOptInfo *outerrel, RelOptInfo *innerrel,
List *restrictlist, JoinType jointype);
static Path *best_innerjoin(List *join_paths, List *outer_relid,
JoinType jointype);
static Selectivity estimate_disbursion(Query *root, Var *var);
static List *select_mergejoin_clauses(RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist);
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist,
JoinType jointype);
/*
@@ -64,6 +69,7 @@ add_paths_to_joinrel(Query *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
List *restrictlist)
{
List *mergeclause_list = NIL;
@@ -75,14 +81,15 @@ add_paths_to_joinrel(Query *root,
mergeclause_list = select_mergejoin_clauses(joinrel,
outerrel,
innerrel,
restrictlist);
restrictlist,
jointype);
/*
* 1. Consider mergejoin paths where both relations must be explicitly
* sorted.
*/
sort_inner_and_outer(root, joinrel, outerrel, innerrel,
restrictlist, mergeclause_list);
restrictlist, mergeclause_list, jointype);
/*
* 2. Consider paths where the outer relation need not be explicitly
@@ -90,7 +97,7 @@ add_paths_to_joinrel(Query *root,
* path is already ordered.
*/
match_unsorted_outer(root, joinrel, outerrel, innerrel,
restrictlist, mergeclause_list);
restrictlist, mergeclause_list, jointype);
#ifdef NOT_USED
@@ -107,7 +114,7 @@ add_paths_to_joinrel(Query *root,
* other order.
*/
match_unsorted_inner(root, joinrel, outerrel, innerrel,
restrictlist, mergeclause_list);
restrictlist, mergeclause_list, jointype);
#endif
/*
@@ -116,7 +123,7 @@ add_paths_to_joinrel(Query *root,
*/
if (enable_hashjoin)
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
restrictlist);
restrictlist, jointype);
}
/*
@@ -131,6 +138,7 @@ add_paths_to_joinrel(Query *root,
* clauses that apply to this join
* 'mergeclause_list' is a list of RestrictInfo nodes for available
* mergejoin clauses in this join
* 'jointype' is the type of join to do
*/
static void
sort_inner_and_outer(Query *root,
@@ -138,7 +146,8 @@ sort_inner_and_outer(Query *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist,
List *mergeclause_list)
List *mergeclause_list,
JoinType jointype)
{
List *i;
@@ -187,10 +196,10 @@ sort_inner_and_outer(Query *root,
*/
outerkeys = make_pathkeys_for_mergeclauses(root,
curclause_list,
outerrel->targetlist);
outerrel);
innerkeys = make_pathkeys_for_mergeclauses(root,
curclause_list,
innerrel->targetlist);
innerrel);
/* Build pathkeys representing output sort order. */
merge_pathkeys = build_join_pathkeys(outerkeys,
joinrel->targetlist,
@@ -204,6 +213,7 @@ sort_inner_and_outer(Query *root,
*/
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
jointype,
outerrel->cheapest_total_path,
innerrel->cheapest_total_path,
restrictlist,
@@ -243,6 +253,7 @@ sort_inner_and_outer(Query *root,
* clauses that apply to this join
* 'mergeclause_list' is a list of RestrictInfo nodes for available
* mergejoin clauses in this join
* 'jointype' is the type of join to do
*/
static void
match_unsorted_outer(Query *root,
@@ -250,16 +261,33 @@ match_unsorted_outer(Query *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist,
List *mergeclause_list)
List *mergeclause_list,
JoinType jointype)
{
bool nestjoinOK;
Path *bestinnerjoin;
List *i;
/*
* Nestloop only supports inner and left joins.
*/
switch (jointype)
{
case JOIN_INNER:
case JOIN_LEFT:
nestjoinOK = true;
break;
default:
nestjoinOK = false;
break;
}
/*
* Get the best innerjoin indexpath (if any) for this outer rel. It's
* the same for all outer paths.
*/
bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids);
bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids,
jointype);
foreach(i, outerrel->pathlist)
{
@@ -282,31 +310,38 @@ match_unsorted_outer(Query *root,
joinrel->targetlist,
root->equi_key_list);
/*
* Always consider a nestloop join with this outer and cheapest-
* total-cost inner. Consider nestloops using the cheapest-
* startup-cost inner as well, and the best innerjoin indexpath.
*/
add_path(joinrel, (Path *)
create_nestloop_path(joinrel,
outerpath,
innerrel->cheapest_total_path,
restrictlist,
merge_pathkeys));
if (innerrel->cheapest_startup_path != innerrel->cheapest_total_path)
if (nestjoinOK)
{
/*
* Always consider a nestloop join with this outer and cheapest-
* total-cost inner. Consider nestloops using the cheapest-
* startup-cost inner as well, and the best innerjoin indexpath.
*/
add_path(joinrel, (Path *)
create_nestloop_path(joinrel,
jointype,
outerpath,
innerrel->cheapest_startup_path,
restrictlist,
merge_pathkeys));
if (bestinnerjoin != NULL)
add_path(joinrel, (Path *)
create_nestloop_path(joinrel,
outerpath,
bestinnerjoin,
innerrel->cheapest_total_path,
restrictlist,
merge_pathkeys));
if (innerrel->cheapest_startup_path !=
innerrel->cheapest_total_path)
add_path(joinrel, (Path *)
create_nestloop_path(joinrel,
jointype,
outerpath,
innerrel->cheapest_startup_path,
restrictlist,
merge_pathkeys));
if (bestinnerjoin != NULL)
add_path(joinrel, (Path *)
create_nestloop_path(joinrel,
jointype,
outerpath,
bestinnerjoin,
restrictlist,
merge_pathkeys));
}
/* Look for useful mergeclauses (if any) */
mergeclauses = find_mergeclauses_for_pathkeys(outerpath->pathkeys,
@@ -319,7 +354,7 @@ match_unsorted_outer(Query *root,
/* Compute the required ordering of the inner path */
innersortkeys = make_pathkeys_for_mergeclauses(root,
mergeclauses,
innerrel->targetlist);
innerrel);
/*
* Generate a mergejoin on the basis of sorting the cheapest
@@ -328,6 +363,7 @@ match_unsorted_outer(Query *root,
*/
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
jointype,
outerpath,
innerrel->cheapest_total_path,
restrictlist,
@@ -373,6 +409,7 @@ match_unsorted_outer(Query *root,
newclauses = mergeclauses;
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
jointype,
outerpath,
innerpath,
restrictlist,
@@ -409,6 +446,7 @@ match_unsorted_outer(Query *root,
}
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
jointype,
outerpath,
innerpath,
restrictlist,
@@ -437,6 +475,7 @@ match_unsorted_outer(Query *root,
* clauses that apply to this join
* 'mergeclause_list' is a list of RestrictInfo nodes for available
* mergejoin clauses in this join
* 'jointype' is the type of join to do
*/
static void
match_unsorted_inner(Query *root,
@@ -444,7 +483,8 @@ match_unsorted_inner(Query *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist,
List *mergeclause_list)
List *mergeclause_list,
JoinType jointype)
{
List *i;
@@ -466,7 +506,7 @@ match_unsorted_inner(Query *root,
/* Compute the required ordering of the outer path */
outersortkeys = make_pathkeys_for_mergeclauses(root,
mergeclauses,
outerrel->targetlist);
outerrel);
/*
* Generate a mergejoin on the basis of sorting the cheapest
@@ -478,6 +518,7 @@ match_unsorted_inner(Query *root,
root->equi_key_list);
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
jointype,
outerrel->cheapest_total_path,
innerpath,
restrictlist,
@@ -506,6 +547,7 @@ match_unsorted_inner(Query *root,
root->equi_key_list);
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
jointype,
totalouterpath,
innerpath,
restrictlist,
@@ -524,6 +566,7 @@ match_unsorted_inner(Query *root,
root->equi_key_list);
add_path(joinrel, (Path *)
create_mergejoin_path(joinrel,
jointype,
startupouterpath,
innerpath,
restrictlist,
@@ -547,18 +590,36 @@ match_unsorted_inner(Query *root,
* 'innerrel' is the inner join relation
* 'restrictlist' contains all of the RestrictInfo nodes for restriction
* clauses that apply to this join
* 'jointype' is the type of join to do
*/
static void
hash_inner_and_outer(Query *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist)
List *restrictlist,
JoinType jointype)
{
Relids outerrelids = outerrel->relids;
Relids innerrelids = innerrel->relids;
bool isouterjoin;
List *i;
/*
* Hashjoin only supports inner and left joins.
*/
switch (jointype)
{
case JOIN_INNER:
isouterjoin = false;
break;
case JOIN_LEFT:
isouterjoin = true;
break;
default:
return;
}
/*
* Scan the join's restrictinfo list to find hashjoinable clauses that
* are usable with this pair of sub-relations. Since we currently
@@ -581,6 +642,13 @@ hash_inner_and_outer(Query *root,
if (restrictinfo->hashjoinoperator == InvalidOid)
continue; /* not hashjoinable */
/*
* If processing an outer join, only use explicit join clauses for
* hashing. For inner joins we need not be so picky.
*/
if (isouterjoin && !restrictinfo->isjoinqual)
continue;
clause = restrictinfo->clause;
/* these must be OK, since check_hashjoinable accepted the clause */
left = get_leftop(clause);
@@ -609,6 +677,7 @@ hash_inner_and_outer(Query *root,
*/
add_path(joinrel, (Path *)
create_hashjoin_path(joinrel,
jointype,
outerrel->cheapest_total_path,
innerrel->cheapest_total_path,
restrictlist,
@@ -617,6 +686,7 @@ hash_inner_and_outer(Query *root,
if (outerrel->cheapest_startup_path != outerrel->cheapest_total_path)
add_path(joinrel, (Path *)
create_hashjoin_path(joinrel,
jointype,
outerrel->cheapest_startup_path,
innerrel->cheapest_total_path,
restrictlist,
@@ -641,26 +711,49 @@ hash_inner_and_outer(Query *root,
* usable path.
*/
static Path *
best_innerjoin(List *join_paths, Relids outer_relids)
best_innerjoin(List *join_paths, Relids outer_relids, JoinType jointype)
{
Path *cheapest = (Path *) NULL;
bool isouterjoin;
List *join_path;
/*
* Nestloop only supports inner and left joins.
*/
switch (jointype)
{
case JOIN_INNER:
isouterjoin = false;
break;
case JOIN_LEFT:
isouterjoin = true;
break;
default:
return NULL;
}
foreach(join_path, join_paths)
{
Path *path = (Path *) lfirst(join_path);
IndexPath *path = (IndexPath *) lfirst(join_path);
Assert(IsA(path, IndexPath));
/*
* If processing an outer join, only use explicit join clauses in the
* inner indexscan. For inner joins we need not be so picky.
*/
if (isouterjoin && !path->alljoinquals)
continue;
/*
* path->joinrelids is the set of base rels that must be part of
* outer_relids in order to use this inner path, because those
* rels are used in the index join quals of this inner path.
*/
if (is_subseti(((IndexPath *) path)->joinrelids, outer_relids) &&
if (is_subseti(path->joinrelids, outer_relids) &&
(cheapest == NULL ||
compare_path_costs(path, cheapest, TOTAL_COST) < 0))
cheapest = path;
compare_path_costs((Path *) path, cheapest, TOTAL_COST) < 0))
cheapest = (Path *) path;
}
return cheapest;
}
@@ -684,6 +777,9 @@ estimate_disbursion(Query *root, Var *var)
relid = getrelid(var->varno, root->rtable);
if (relid == InvalidOid)
return 0.1;
return (Selectivity) get_attdisbursion(relid, var->varattno, 0.1);
}
@@ -707,11 +803,13 @@ static List *
select_mergejoin_clauses(RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
List *restrictlist)
List *restrictlist,
JoinType jointype)
{
List *result_list = NIL;
Relids outerrelids = outerrel->relids;
Relids innerrelids = innerrel->relids;
bool isouterjoin = IS_OUTER_JOIN(jointype);
List *i;
foreach(i, restrictlist)
@@ -721,6 +819,37 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
Var *left,
*right;
/*
* If processing an outer join, only use explicit join clauses in the
* merge. For inner joins we need not be so picky.
*
* Furthermore, if it is a right/full join then *all* the explicit
* join clauses must be mergejoinable, else the executor will fail.
* If we are asked for a right join then just return NIL to indicate
* no mergejoin is possible (we can handle it as a left join instead).
* If we are asked for a full join then emit an error, because there
* is no fallback.
*/
if (isouterjoin)
{
if (!restrictinfo->isjoinqual)
continue;
switch (jointype)
{
case JOIN_RIGHT:
if (restrictinfo->mergejoinoperator == InvalidOid)
return NIL; /* not mergejoinable */
break;
case JOIN_FULL:
if (restrictinfo->mergejoinoperator == InvalidOid)
elog(ERROR, "FULL JOIN is only supported with mergejoinable join conditions");
break;
default:
/* otherwise, it's OK to have nonmergeable join quals */
break;
}
}
if (restrictinfo->mergejoinoperator == InvalidOid)
continue; /* not mergejoinable */

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.46 2000/05/30 00:49:47 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.47 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -19,62 +19,52 @@
static RelOptInfo *make_join_rel(Query *root, RelOptInfo *rel1,
RelOptInfo *rel2);
RelOptInfo *rel2, JoinType jointype);
/*
* make_rels_by_joins
* Consider ways to produce join relations containing exactly 'level'
* base relations. (This is one step of the dynamic-programming method
* jointree items. (This is one step of the dynamic-programming method
* embodied in make_one_rel_by_joins.) Join rel nodes for each feasible
* combination of base rels are created and added to the front of the
* query's join_rel_list. Implementation paths are created for each
* such joinrel, too.
* combination of lower-level rels are created and returned in a list.
* Implementation paths are created for each such joinrel, too.
*
* Returns nothing, but adds entries to root->join_rel_list.
* level: level of rels we want to make this time.
* joinrels[j], 1 <= j < level, is a list of rels containing j items.
*/
void
make_rels_by_joins(Query *root, int level)
List *
make_rels_by_joins(Query *root, int level, List **joinrels)
{
List *first_old_rel = root->join_rel_list;
List *result_rels = NIL;
List *new_rels;
List *nr;
List *r;
int k;
/*
* First, consider left-sided and right-sided plans, in which rels of
* exactly level-1 member relations are joined against base relations.
* We prefer to join using join clauses, but if we find a rel of
* level-1 members that has no join clauses, we will generate
* Cartesian-product joins against all base rels not already contained
* in it.
* exactly level-1 member relations are joined against initial relations.
* We prefer to join using join clauses, but if we find a rel of level-1
* members that has no join clauses, we will generate Cartesian-product
* joins against all initial rels not already contained in it.
*
* In the first pass (level == 2), we try to join each base rel to each
* base rel that appears later in base_rel_list. (The mirror-image
* In the first pass (level == 2), we try to join each initial rel to each
* initial rel that appears later in joinrels[1]. (The mirror-image
* joins are handled automatically by make_join_rel.) In later
* passes, we try to join rels of size level-1 from join_rel_list to
* each base rel in base_rel_list.
*
* We assume that the rels already present in join_rel_list appear in
* decreasing order of level (number of members). This should be true
* since we always add new higher-level rels to the front of the list.
* passes, we try to join rels of size level-1 from joinrels[level-1]
* to each initial rel in joinrels[1].
*/
if (level == 2)
r = root->base_rel_list;/* level-1 is base rels */
else
r = root->join_rel_list;
for (; r != NIL; r = lnext(r))
foreach(r, joinrels[level-1])
{
RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
int old_level = length(old_rel->relids);
List *other_rels;
if (old_level != level - 1)
break;
if (level == 2)
other_rels = lnext(r); /* only consider remaining base
other_rels = lnext(r); /* only consider remaining initial
* rels */
else
other_rels = root->base_rel_list; /* consider all base rels */
other_rels = joinrels[1]; /* consider all initial rels */
if (old_rel->joininfo != NIL)
{
@@ -87,9 +77,9 @@ make_rels_by_joins(Query *root, int level)
* have those other rels collected into a join rel. See also
* the last-ditch case below.
*/
make_rels_by_clause_joins(root,
old_rel,
other_rels);
new_rels = make_rels_by_clause_joins(root,
old_rel,
other_rels);
}
else
{
@@ -98,64 +88,90 @@ make_rels_by_joins(Query *root, int level)
* Oops, we have a relation that is not joined to any other
* relation. Cartesian product time.
*/
make_rels_by_clauseless_joins(root,
old_rel,
other_rels);
new_rels = make_rels_by_clauseless_joins(root,
old_rel,
other_rels);
}
/*
* At levels above 2 we will generate the same joined relation
* in multiple ways --- for example (a join b) join c is the same
* RelOptInfo as (b join c) join a, though the second case will
* add a different set of Paths to it. To avoid making extra work
* for subsequent passes, do not enter the same RelOptInfo into our
* output list multiple times.
*/
foreach(nr, new_rels)
{
RelOptInfo *jrel = (RelOptInfo *) lfirst(nr);
if (!ptrMember(jrel, result_rels))
result_rels = lcons(jrel, result_rels);
}
}
/*
* Now, consider "bushy plans" in which relations of k base rels are
* joined to relations of level-k base rels, for 2 <= k <= level-2.
* The previous loop left r pointing to the first rel of level
* level-2.
* Now, consider "bushy plans" in which relations of k initial rels are
* joined to relations of level-k initial rels, for 2 <= k <= level-2.
*
* We only consider bushy-plan joins for pairs of rels where there is a
* suitable join clause, in order to avoid unreasonable growth of
* planning time.
*/
for (; r != NIL; r = lnext(r))
for (k = 2; ; k++)
{
RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
int old_level = length(old_rel->relids);
List *r2;
int other_level = level - k;
/*
* We can quit once past the halfway point (make_join_rel took
* care of making the opposite-direction joins)
* Since make_join_rel(x, y) handles both x,y and y,x cases,
* we only need to go as far as the halfway point.
*/
if (old_level * 2 < level)
if (k > other_level)
break;
if (old_rel->joininfo == NIL)
continue; /* we ignore clauseless joins here */
foreach(r2, lnext(r))
foreach(r, joinrels[k])
{
RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
int new_level = length(new_rel->relids);
RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
List *other_rels;
List *r2;
if (old_level + new_level > level)
continue; /* scan down to new_rels of right size */
if (old_level + new_level < level)
break; /* no more new_rels of right size */
if (nonoverlap_setsi(old_rel->relids, new_rel->relids))
if (old_rel->joininfo == NIL)
continue; /* we ignore clauseless joins here */
if (k == other_level)
other_rels = lnext(r); /* only consider remaining rels */
else
other_rels = joinrels[other_level];
foreach(r2, other_rels)
{
List *i;
RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
/*
* OK, we can build a rel of the right level from this
* pair of rels. Do so if there is at least one usable
* join clause.
*/
foreach(i, old_rel->joininfo)
if (nonoverlap_setsi(old_rel->relids, new_rel->relids))
{
JoinInfo *joininfo = (JoinInfo *) lfirst(i);
List *i;
if (is_subseti(joininfo->unjoined_relids, new_rel->relids))
/*
* OK, we can build a rel of the right level from this
* pair of rels. Do so if there is at least one usable
* join clause.
*/
foreach(i, old_rel->joininfo)
{
make_join_rel(root, old_rel, new_rel);
break;
JoinInfo *joininfo = (JoinInfo *) lfirst(i);
if (is_subseti(joininfo->unjoined_relids,
new_rel->relids))
{
RelOptInfo *jrel;
jrel = make_join_rel(root, old_rel, new_rel,
JOIN_INNER);
/* Avoid making duplicate entries ... */
if (!ptrMember(jrel, result_rels))
result_rels = lcons(jrel, result_rels);
break; /* need not consider more joininfos */
}
}
}
}
@@ -174,39 +190,41 @@ make_rels_by_joins(Query *root, int level)
* no choice but to make cartesian joins. We consider only left-sided
* and right-sided cartesian joins in this case (no bushy).
*/
if (root->join_rel_list == first_old_rel)
if (result_rels == NIL)
{
/* This loop is just like the first one, except we always call
* make_rels_by_clauseless_joins().
*/
if (level == 2)
r = root->base_rel_list; /* level-1 is base rels */
else
r = root->join_rel_list;
for (; r != NIL; r = lnext(r))
foreach(r, joinrels[level-1])
{
RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
int old_level = length(old_rel->relids);
List *other_rels;
if (old_level != level - 1)
break;
if (level == 2)
other_rels = lnext(r); /* only consider remaining base
other_rels = lnext(r); /* only consider remaining initial
* rels */
else
other_rels = root->base_rel_list; /* consider all base rels */
other_rels = joinrels[1]; /* consider all initial rels */
make_rels_by_clauseless_joins(root,
old_rel,
other_rels);
new_rels = make_rels_by_clauseless_joins(root,
old_rel,
other_rels);
foreach(nr, new_rels)
{
RelOptInfo *jrel = (RelOptInfo *) lfirst(nr);
if (!ptrMember(jrel, result_rels))
result_rels = lcons(jrel, result_rels);
}
}
if (root->join_rel_list == first_old_rel)
if (result_rels == NIL)
elog(ERROR, "make_rels_by_joins: failed to build any %d-way joins",
level);
}
return result_rels;
}
/*
@@ -214,28 +232,23 @@ make_rels_by_joins(Query *root, int level)
* Build joins between the given relation 'old_rel' and other relations
* that are mentioned within old_rel's joininfo nodes (i.e., relations
* that participate in join clauses that 'old_rel' also participates in).
* The join rel nodes are added to root->join_rel_list.
* The join rel nodes are returned in a list.
*
* 'old_rel' is the relation entry for the relation to be joined
* 'other_rels': other rels to be considered for joining
*
* Currently, this is only used with base rels in other_rels, but it would
* work for joining to joinrels too, if the caller ensures there is no
* Currently, this is only used with initial rels in other_rels, but it
* will work for joining to joinrels too, if the caller ensures there is no
* membership overlap between old_rel and the rels in other_rels. (We need
* no extra test for overlap for base rels, since the is_subset test can
* no extra test for overlap for initial rels, since the is_subset test can
* only succeed when other_rel is not already part of old_rel.)
*
* Returns NULL if no suitable joins were found, else the last suitable
* joinrel processed. (The only caller who checks the return value is
* geqo_eval.c, and it sets things up so there can be no more than one
* "suitable" joinrel; so we don't bother with returning a list.)
*/
RelOptInfo *
List *
make_rels_by_clause_joins(Query *root,
RelOptInfo *old_rel,
List *other_rels)
{
RelOptInfo *result = NULL;
List *result = NIL;
List *i,
*j;
@@ -249,7 +262,9 @@ make_rels_by_clause_joins(Query *root,
RelOptInfo *other_rel = (RelOptInfo *) lfirst(j);
if (is_subseti(unjoined_relids, other_rel->relids))
result = make_join_rel(root, old_rel, other_rel);
result = lcons(make_join_rel(root, old_rel, other_rel,
JOIN_INNER),
result);
}
}
@@ -261,24 +276,20 @@ make_rels_by_clause_joins(Query *root,
* Given a relation 'old_rel' and a list of other relations
* 'other_rels', create a join relation between 'old_rel' and each
* member of 'other_rels' that isn't already included in 'old_rel'.
* The join rel nodes are returned in a list.
*
* 'old_rel' is the relation entry for the relation to be joined
* 'other_rels': other rels to be considered for joining
*
* Currently, this is only used with base rels in other_rels, but it would
* Currently, this is only used with initial rels in other_rels, but it would
* work for joining to joinrels too.
*
* Returns NULL if no suitable joins were found, else the last suitable
* joinrel processed. (The only caller who checks the return value is
* geqo_eval.c, and it sets things up so there can be no more than one
* "suitable" joinrel; so we don't bother with returning a list.)
*/
RelOptInfo *
List *
make_rels_by_clauseless_joins(Query *root,
RelOptInfo *old_rel,
List *other_rels)
{
RelOptInfo *result = NULL;
List *result = NIL;
List *i;
foreach(i, other_rels)
@@ -286,13 +297,61 @@ make_rels_by_clauseless_joins(Query *root,
RelOptInfo *other_rel = (RelOptInfo *) lfirst(i);
if (nonoverlap_setsi(other_rel->relids, old_rel->relids))
result = make_join_rel(root, old_rel, other_rel);
result = lcons(make_join_rel(root, old_rel, other_rel,
JOIN_INNER),
result);
}
return result;
}
/*
* make_rel_from_jointree
* Find or build a RelOptInfojoin rel representing a specific
* jointree item. For JoinExprs, we only consider the construction
* path that corresponds exactly to what the user wrote.
*/
RelOptInfo *
make_rel_from_jointree(Query *root, Node *jtnode)
{
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
return get_base_rel(root, varno);
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
RelOptInfo *rel,
*lrel,
*rrel;
/* Recurse */
lrel = make_rel_from_jointree(root, j->larg);
rrel = make_rel_from_jointree(root, j->rarg);
/* Make this join rel */
rel = make_join_rel(root, lrel, rrel, j->jointype);
/*
* 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);
return rel;
}
else
elog(ERROR, "make_rel_from_jointree: unexpected node type %d",
nodeTag(jtnode));
return NULL; /* keep compiler quiet */
}
/*
* make_join_rel
* Find or create a join RelOptInfo that represents the join of
@@ -300,10 +359,10 @@ make_rels_by_clauseless_joins(Query *root,
* created with the two rels as outer and inner rel.
* (The join rel may already contain paths generated from other
* pairs of rels that add up to the same set of base rels.)
* The join rel is stored in the query's join_rel_list.
*/
static RelOptInfo *
make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2)
make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2,
JoinType jointype)
{
RelOptInfo *joinrel;
List *restrictlist;
@@ -315,10 +374,39 @@ make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2)
joinrel = get_join_rel(root, rel1, rel2, &restrictlist);
/*
* We consider paths using each rel as both outer and inner.
* Consider paths using each rel as both outer and inner.
*/
add_paths_to_joinrel(root, joinrel, rel1, rel2, restrictlist);
add_paths_to_joinrel(root, joinrel, rel2, rel1, restrictlist);
switch (jointype)
{
case JOIN_INNER:
add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_INNER,
restrictlist);
add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_INNER,
restrictlist);
break;
case JOIN_LEFT:
add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_LEFT,
restrictlist);
add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT,
restrictlist);
break;
case JOIN_FULL:
add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_FULL,
restrictlist);
add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_FULL,
restrictlist);
break;
case JOIN_RIGHT:
add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_RIGHT,
restrictlist);
add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_LEFT,
restrictlist);
break;
default:
elog(ERROR, "make_join_rel: unsupported join type %d",
(int) jointype);
break;
}
return joinrel;
}

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.40 2000/05/30 00:49:47 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.41 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -101,6 +101,7 @@ create_or_index_paths(Query *root,
/* This isn't a nestloop innerjoin, so: */
pathnode->joinrelids = NIL; /* no join clauses here */
pathnode->alljoinquals = false;
pathnode->rows = rel->rows;
best_or_subclause_indices(root,

View File

@@ -11,7 +11,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.24 2000/08/08 15:41:31 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.25 2000/09/12 21:06:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -694,8 +694,8 @@ find_mergeclauses_for_pathkeys(List *pathkeys, List *restrictinfos)
*
* 'mergeclauses' is a list of RestrictInfos for mergejoin clauses
* that will be used in a merge join.
* 'tlist' is a relation target list for either the inner or outer
* side of the proposed join rel. (Not actually needed anymore)
* 'rel' is the relation the pathkeys will apply to (ie, either the inner
* or outer side of the proposed join rel).
*
* Returns a pathkeys list that can be applied to the indicated relation.
*
@@ -706,7 +706,7 @@ find_mergeclauses_for_pathkeys(List *pathkeys, List *restrictinfos)
List *
make_pathkeys_for_mergeclauses(Query *root,
List *mergeclauses,
List *tlist)
RelOptInfo *rel)
{
List *pathkeys = NIL;
List *i;
@@ -722,30 +722,37 @@ make_pathkeys_for_mergeclauses(Query *root,
Assert(restrictinfo->mergejoinoperator != InvalidOid);
/*
* Find the key and sortop needed for this mergeclause.
*
* Both sides of the mergeclause should appear in one of the query's
* pathkey equivalence classes, so it doesn't matter which one we
* use here.
* Which key and sortop is needed for this relation?
*/
key = (Node *) get_leftop(restrictinfo->clause);
sortop = restrictinfo->left_sortop;
if (!IsA(key, Var) ||
!intMember(((Var *) key)->varno, rel->relids))
{
key = (Node *) get_rightop(restrictinfo->clause);
sortop = restrictinfo->right_sortop;
if (!IsA(key, Var) ||
!intMember(((Var *) key)->varno, rel->relids))
elog(ERROR, "make_pathkeys_for_mergeclauses: can't identify which side of mergeclause to use");
}
/*
* Find pathkey sublist for this sort item. We expect to find the
* canonical set including the mergeclause's left and right sides;
* if we get back just the one item, something is rotten.
* Find or create canonical pathkey sublist for this sort item.
*/
item = makePathKeyItem(key, sortop);
pathkey = make_canonical_pathkey(root, item);
Assert(length(pathkey) > 1);
/*
* Since the item we just made is not in the returned canonical
* set, we can free it --- this saves a useful amount of storage
* in a big join tree.
* Most of the time we will get back a canonical pathkey set
* including both the mergeclause's left and right sides (the only
* case where we don't is if the mergeclause appeared in an OUTER
* JOIN, which causes us not to generate an equijoin set from it).
* Therefore, most of the time the item we just made is not part
* of the returned structure, and we can free it. This check
* saves a useful amount of storage in a big join tree.
*/
pfree(item);
if (item != (PathKeyItem *) lfirst(pathkey))
pfree(item);
pathkeys = lappend(pathkeys, pathkey);
}