1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-09 06:21:09 +03:00

Repair planning bugs caused by my misguided removal of restrictinfo link

fields in JoinPaths --- turns out that we do need that after all :-(.
Also, rearrange planner so that only one RelOptInfo is created for a
particular set of joined base relations, no matter how many different
subsets of relations it can be created from.  This saves memory and
processing time compared to the old method of making a bunch of RelOptInfos
and then removing the duplicates.  Clean up the jointree iteration logic;
not sure if it's better, but I sure find it more readable and plausible
now, particularly for the case of 'bushy plans'.
This commit is contained in:
Tom Lane
2000-02-07 04:41:04 +00:00
parent 2bda7a4406
commit d8733ce674
24 changed files with 1224 additions and 1106 deletions

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.58 2000/01/26 05:56:40 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.59 2000/02/07 04:41:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -76,101 +76,104 @@ set_cheapest(RelOptInfo *parent_rel, List *pathlist)
/*
* add_pathlist
* Construct an output path list by adding to old_paths each path in
* new_paths that is worth considering --- that is, it has either a
* better sort order (better pathkeys) or cheaper cost than any of the
* existing old paths.
*
* Unless parent_rel->pruneable is false, we also remove from the output
* pathlist any old paths that are dominated by added path(s) --- that is,
* some new path is both cheaper and at least as well ordered.
*
* Note: the list old_paths is destructively modified, and in fact is
* turned into the output list.
*
* 'parent_rel' is the relation entry to which these paths correspond.
* 'old_paths' is the list of previously accepted paths for parent_rel.
* 'new_paths' is a list of potential new paths.
*
* Returns the updated list of interesting pathnodes.
* Consider each path given in new_paths, and add it to the parent rel's
* pathlist if it seems worthy.
*/
List *
add_pathlist(RelOptInfo *parent_rel, List *old_paths, List *new_paths)
void
add_pathlist(RelOptInfo *parent_rel, List *new_paths)
{
List *p1;
foreach(p1, new_paths)
{
Path *new_path = (Path *) lfirst(p1);
bool accept_new = true; /* unless we find a superior old path */
List *p2_prev = NIL;
List *p2;
/*
* Loop to check proposed new path against old paths. Note it is
* possible for more than one old path to be tossed out because
* new_path dominates it.
*/
foreach(p2, old_paths)
add_path(parent_rel, new_path);
}
}
/*
* add_path
* Consider a potential implementation path for the specified parent rel,
* and add it to the rel's pathlist if it is worthy of consideration.
* A path is worthy if it has either a better sort order (better pathkeys)
* or cheaper cost than any of the existing old paths.
*
* Unless parent_rel->pruneable is false, we also remove from the rel's
* pathlist any old paths that are dominated by new_path --- that is,
* new_path is both cheaper and at least as well ordered.
*
* 'parent_rel' is the relation entry to which the path corresponds.
* 'new_path' is a potential path for parent_rel.
*
* Returns nothing, but modifies parent_rel->pathlist.
*/
void
add_path(RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
List *p1_prev = NIL;
List *p1;
/*
* Loop to check proposed new path against old paths. Note it is
* possible for more than one old path to be tossed out because
* new_path dominates it.
*/
foreach(p1, parent_rel->pathlist)
{
Path *old_path = (Path *) lfirst(p1);
bool remove_old = false; /* unless new proves superior */
switch (compare_pathkeys(new_path->pathkeys, old_path->pathkeys))
{
Path *old_path = (Path *) lfirst(p2);
bool remove_old = false; /* unless new proves superior */
switch (compare_pathkeys(new_path->pathkeys, old_path->pathkeys))
{
case PATHKEYS_EQUAL:
if (new_path->path_cost < old_path->path_cost)
remove_old = true; /* new dominates old */
else
accept_new = false; /* old equals or dominates new */
break;
case PATHKEYS_BETTER1:
if (new_path->path_cost <= old_path->path_cost)
remove_old = true; /* new dominates old */
break;
case PATHKEYS_BETTER2:
if (new_path->path_cost >= old_path->path_cost)
accept_new = false; /* old dominates new */
break;
case PATHKEYS_DIFFERENT:
/* keep both paths, since they have different ordering */
break;
}
/*
* Remove current element from old_list if dominated by new,
* unless xfunc told us not to remove any paths.
*/
if (remove_old && parent_rel->pruneable)
{
if (p2_prev)
lnext(p2_prev) = lnext(p2);
case PATHKEYS_EQUAL:
if (new_path->path_cost < old_path->path_cost)
remove_old = true; /* new dominates old */
else
old_paths = lnext(p2);
}
else
p2_prev = p2;
/*
* If we found an old path that dominates new_path, we can quit
* scanning old_paths; we will not add new_path, and we assume
* new_path cannot dominate any other elements of old_paths.
*/
if (! accept_new)
accept_new = false; /* old equals or dominates new */
break;
case PATHKEYS_BETTER1:
if (new_path->path_cost <= old_path->path_cost)
remove_old = true; /* new dominates old */
break;
case PATHKEYS_BETTER2:
if (new_path->path_cost >= old_path->path_cost)
accept_new = false; /* old dominates new */
break;
case PATHKEYS_DIFFERENT:
/* keep both paths, since they have different ordering */
break;
}
if (accept_new)
/*
* Remove current element from pathlist if dominated by new,
* unless xfunc told us not to remove any paths.
*/
if (remove_old && parent_rel->pruneable)
{
/* Accept the path. Note that it will now be eligible to be
* compared against the additional elements of new_paths...
*/
new_path->parent = parent_rel; /* not redundant, see prune.c */
old_paths = lcons(new_path, old_paths);
if (p1_prev)
lnext(p1_prev) = lnext(p1);
else
parent_rel->pathlist = lnext(p1);
}
else
p1_prev = p1;
/*
* If we found an old path that dominates new_path, we can quit
* scanning the pathlist; we will not add new_path, and we assume
* new_path cannot dominate any other elements of the pathlist.
*/
if (! accept_new)
break;
}
return old_paths;
if (accept_new)
{
/* Accept the path */
parent_rel->pathlist = lcons(new_path, parent_rel->pathlist);
}
}
@@ -271,6 +274,7 @@ create_tidscan_path(RelOptInfo *rel, List *tideval)
* 'joinrel' is the join relation.
* 'outer_path' is the outer path
* 'inner_path' is the inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
* 'pathkeys' are the path keys of the new join path
*
* Returns the resulting path node.
@@ -280,6 +284,7 @@ NestPath *
create_nestloop_path(RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
List *pathkeys)
{
NestPath *pathnode = makeNode(NestPath);
@@ -288,6 +293,7 @@ create_nestloop_path(RelOptInfo *joinrel,
pathnode->path.parent = joinrel;
pathnode->outerjoinpath = outer_path;
pathnode->innerjoinpath = inner_path;
pathnode->joinrestrictinfo = restrict_clauses;
pathnode->path.pathkeys = pathkeys;
pathnode->path.path_cost = cost_nestloop(outer_path,
@@ -305,6 +311,7 @@ create_nestloop_path(RelOptInfo *joinrel,
* 'joinrel' is the join relation
* 'outer_path' is the outer path
* 'inner_path' is the inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
* 'pathkeys' are the path keys of the new join path
* 'mergeclauses' are the applicable join/restriction clauses
* 'outersortkeys' are the sort varkeys for the outer relation
@@ -315,6 +322,7 @@ MergePath *
create_mergejoin_path(RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
List *mergeclauses,
List *outersortkeys,
@@ -337,6 +345,7 @@ create_mergejoin_path(RelOptInfo *joinrel,
pathnode->jpath.path.parent = joinrel;
pathnode->jpath.outerjoinpath = outer_path;
pathnode->jpath.innerjoinpath = inner_path;
pathnode->jpath.joinrestrictinfo = restrict_clauses;
pathnode->jpath.path.pathkeys = pathkeys;
pathnode->path_mergeclauses = mergeclauses;
pathnode->outersortkeys = outersortkeys;
@@ -356,6 +365,7 @@ create_mergejoin_path(RelOptInfo *joinrel,
* 'joinrel' is the join relation
* 'outer_path' is the cheapest outer path
* 'inner_path' is the cheapest inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
* 'hashclauses' is a list of the hash join clause (always a 1-element list)
* 'innerdisbursion' is an estimate of the disbursion of the inner hash key
*
@@ -364,6 +374,7 @@ HashPath *
create_hashjoin_path(RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
List *hashclauses,
Selectivity innerdisbursion)
{
@@ -373,6 +384,7 @@ create_hashjoin_path(RelOptInfo *joinrel,
pathnode->jpath.path.parent = joinrel;
pathnode->jpath.outerjoinpath = outer_path;
pathnode->jpath.innerjoinpath = inner_path;
pathnode->jpath.joinrestrictinfo = restrict_clauses;
/* A hashjoin never has pathkeys, since its ordering is unpredictable */
pathnode->jpath.path.pathkeys = NIL;
pathnode->path_hashclauses = hashclauses;

View File

@@ -1,109 +1,408 @@
/*-------------------------------------------------------------------------
*
* relnode.c
* Relation manipulation routines
* Relation-node lookup/construction routines
*
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.22 2000/02/06 03:27:33 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.23 2000/02/07 04:41:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "optimizer/cost.h"
#include "optimizer/internal.h"
#include "optimizer/joininfo.h"
#include "optimizer/pathnode.h"
#include "optimizer/plancat.h"
#include "optimizer/tlist.h"
static List *new_join_tlist(List *tlist, int first_resdomno);
static List *build_joinrel_restrictlist(RelOptInfo *joinrel,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel);
static void build_joinrel_joinlist(RelOptInfo *joinrel,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel);
static List *subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
List *joininfo_list);
static void subbuild_joinrel_joinlist(RelOptInfo *joinrel,
List *joininfo_list);
/*
* get_base_rel
* Returns relation entry corresponding to 'relid', creating a new one if
* necessary. This is for base relations.
*
* Returns relation entry corresponding to 'relid', creating a new one
* if necessary. This is for base relations.
*/
RelOptInfo *
get_base_rel(Query *root, int relid)
{
Relids relids = lconsi(relid, NIL);
List *baserels;
RelOptInfo *rel;
rel = rel_member(relids, root->base_rel_list);
if (rel == NULL)
foreach(baserels, root->base_rel_list)
{
rel = makeNode(RelOptInfo);
rel->relids = relids;
rel->rows = 0;
rel->width = 0;
rel->targetlist = NIL;
rel->pathlist = NIL;
rel->cheapestpath = (Path *) NULL;
rel->pruneable = true;
rel->indexed = false;
rel->pages = 0;
rel->tuples = 0;
rel->restrictinfo = NIL;
rel->joininfo = NIL;
rel->innerjoin = NIL;
rel = (RelOptInfo *) lfirst(baserels);
root->base_rel_list = lcons(rel, root->base_rel_list);
if (relid < 0)
{
/*
* If the relation is a materialized relation, assume
* constants for sizes.
*/
rel->pages = _NONAME_RELATION_PAGES_;
rel->tuples = _NONAME_RELATION_TUPLES_;
}
else
{
/*
* Otherwise, retrieve relation statistics from the
* system catalogs.
*/
relation_info(root, relid,
&rel->indexed, &rel->pages, &rel->tuples);
}
/* We know length(rel->relids) == 1 for all members of base_rel_list */
if (lfirsti(rel->relids) == relid)
return rel;
}
/* No existing RelOptInfo for this base rel, so make a new one */
rel = makeNode(RelOptInfo);
rel->relids = lconsi(relid, NIL);
rel->rows = 0;
rel->width = 0;
rel->targetlist = NIL;
rel->pathlist = NIL;
rel->cheapestpath = (Path *) NULL;
rel->pruneable = true;
rel->indexed = false;
rel->pages = 0;
rel->tuples = 0;
rel->baserestrictinfo = NIL;
rel->joininfo = NIL;
rel->innerjoin = NIL;
if (relid < 0)
{
/*
* If the relation is a materialized relation, assume
* constants for sizes.
*/
rel->pages = _NONAME_RELATION_PAGES_;
rel->tuples = _NONAME_RELATION_TUPLES_;
}
else
{
/*
* Otherwise, retrieve relation statistics from the
* system catalogs.
*/
relation_info(root, relid,
&rel->indexed, &rel->pages, &rel->tuples);
}
root->base_rel_list = lcons(rel, root->base_rel_list);
return rel;
}
/*
* get_join_rel
* Returns relation entry corresponding to 'relid' (a list of relids),
* or NULL.
*/
RelOptInfo *
get_join_rel(Query *root, Relids relid)
{
return rel_member(relid, root->join_rel_list);
}
/*
* rel_member
* Determines whether a relation of id 'relid' is contained within a list
* 'rels'.
*
* Returns the corresponding entry in 'rels' if it is there.
* find_join_rel
* Returns relation entry corresponding to 'relids' (a list of RT indexes),
* or NULL if none exists. This is for join relations.
*
* Note: there is probably no good reason for this to be called from
* anywhere except get_join_rel, but keep it as a separate routine
* just in case.
*/
RelOptInfo *
rel_member(Relids relids, List *rels)
static RelOptInfo *
find_join_rel(Query *root, Relids relids)
{
List *temp;
List *joinrels;
foreach(temp, rels)
foreach(joinrels, root->join_rel_list)
{
RelOptInfo *rel = (RelOptInfo *) lfirst(temp);
RelOptInfo *rel = (RelOptInfo *) lfirst(joinrels);
if (sameseti(rel->relids, relids))
return rel;
}
return NULL;
}
/*
* get_join_rel
* Returns relation entry corresponding to the union of two given rels,
* creating a new relation entry if none already exists.
*
* 'outer_rel' and 'inner_rel' are relation nodes for the relations to be
* joined
* 'restrictlist_ptr': result variable. If not NULL, *restrictlist_ptr
* receives the list of RestrictInfo nodes that apply to this
* particular pair of joinable relations.
*
* restrictlist_ptr makes the routine's API a little grotty, but it saves
* duplicated calculation of the restrictlist...
*/
RelOptInfo *
get_join_rel(Query *root,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel,
List **restrictlist_ptr)
{
List *joinrelids;
RelOptInfo *joinrel;
List *restrictlist;
List *new_outer_tlist;
List *new_inner_tlist;
/* We should never try to join two overlapping sets of rels. */
Assert(nonoverlap_setsi(outer_rel->relids, inner_rel->relids));
/*
* See if we already have a joinrel for this set of base rels.
*
* nconc(listCopy(x), y) is an idiom for making a new list without
* changing either input list.
*/
joinrelids = nconc(listCopy(outer_rel->relids), inner_rel->relids);
joinrel = find_join_rel(root, joinrelids);
if (joinrel)
{
/*
* Yes, so we only need to figure the restrictlist for this
* particular pair of component relations.
*/
if (restrictlist_ptr)
*restrictlist_ptr = build_joinrel_restrictlist(joinrel,
outer_rel,
inner_rel);
return joinrel;
}
/*
* Nope, so make one.
*/
joinrel = makeNode(RelOptInfo);
joinrel->relids = joinrelids;
joinrel->rows = 0;
joinrel->width = 0;
joinrel->targetlist = NIL;
joinrel->pathlist = NIL;
joinrel->cheapestpath = (Path *) NULL;
joinrel->pruneable = true;
joinrel->indexed = false;
joinrel->pages = 0;
joinrel->tuples = 0;
joinrel->baserestrictinfo = NIL;
joinrel->joininfo = NIL;
joinrel->innerjoin = NIL;
/*
* Create a new tlist by removing irrelevant elements from both tlists
* of the outer and inner join relations and then merging the results
* together.
*
* NOTE: the tlist order for a join rel will depend on which pair
* of outer and inner rels we first try to build it from. But the
* contents should be the same regardless.
*
* XXX someday: consider pruning vars from the join's targetlist
* if they are needed only to evaluate restriction clauses of this
* join, and will never be accessed at higher levels of the plantree.
*/
new_outer_tlist = new_join_tlist(outer_rel->targetlist, 1);
new_inner_tlist = new_join_tlist(inner_rel->targetlist,
length(new_outer_tlist) + 1);
joinrel->targetlist = nconc(new_outer_tlist, new_inner_tlist);
/*
* Construct restrict and join clause lists for the new joinrel.
* (The caller might or might not need the restrictlist, but
* I need it anyway for set_joinrel_size_estimates().)
*/
restrictlist = build_joinrel_restrictlist(joinrel, outer_rel, inner_rel);
if (restrictlist_ptr)
*restrictlist_ptr = restrictlist;
build_joinrel_joinlist(joinrel, outer_rel, inner_rel);
/*
* Set estimates of the joinrel's size.
*/
set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel,
restrictlist);
/*
* Add the joinrel to the front of the query's joinrel list.
* (allpaths.c depends on this ordering!)
*/
root->join_rel_list = lcons(joinrel, root->join_rel_list);
return joinrel;
}
/*
* new_join_tlist
* Builds a join relation's target list by keeping those elements that
* will be in the final target list and any other elements that are still
* needed for future joins. For a target list entry to still be needed
* for future joins, its 'joinlist' field must not be empty after removal
* of all relids in 'other_relids'.
*
* XXX the above comment refers to code that is long dead and gone;
* we don't keep track of joinlists for individual targetlist entries
* anymore. For now, all vars present in either input tlist will be
* emitted in the join's tlist.
*
* 'tlist' is the target list of one of the join relations
* 'first_resdomno' is the resdom number to use for the first created
* target list entry
*
* Returns the new target list.
*/
static List *
new_join_tlist(List *tlist,
int first_resdomno)
{
int resdomno = first_resdomno - 1;
List *t_list = NIL;
List *i;
foreach(i, tlist)
{
TargetEntry *xtl = lfirst(i);
resdomno += 1;
t_list = lappend(t_list,
create_tl_element(get_expr(xtl), resdomno));
}
return t_list;
}
/*
* build_joinrel_restrictlist
* build_joinrel_joinlist
* These routines build lists of restriction and join clauses for a
* join relation from the joininfo lists of the relations it joins.
*
* These routines are separate because the restriction list must be
* built afresh for each pair of input sub-relations we consider, whereas
* the join lists need only be computed once for any join RelOptInfo.
* The join lists are fully determined by the set of rels making up the
* joinrel, so we should get the same results (up to ordering) from any
* candidate pair of sub-relations. But the restriction list is whatever
* is not handled in the sub-relations, so it depends on which
* sub-relations are considered.
*
* If a join clause from an input relation refers to base rels still not
* present in the joinrel, then it is still a join clause for the joinrel;
* we put it into an appropriate JoinInfo list for the joinrel. Otherwise,
* the clause is now a restrict clause for the joined relation, and we
* return it to the caller of build_joinrel_restrictlist() to be stored in
* join paths made from this pair of sub-relations. (It will not need to
* be considered further up the join tree.)
*
* 'joinrel' is a join relation node
* 'outer_rel' and 'inner_rel' are a pair of relations that can be joined
* to form joinrel.
*
* build_joinrel_restrictlist() returns a list of relevant restrictinfos,
* whereas build_joinrel_joinlist() stores its results in the joinrel's
* joininfo lists. One or the other must accept each given clause!
*
* NB: Formerly, we made deep(!) copies of each input RestrictInfo to pass
* up to the join relation. I believe this is no longer necessary, because
* RestrictInfo nodes are no longer context-dependent. Instead, just include
* the original nodes in the lists made for the join relation.
*/
static List *
build_joinrel_restrictlist(RelOptInfo *joinrel,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel)
{
/*
* We must eliminate duplicates, since we will see the
* same clauses arriving from both input relations...
*/
return LispUnion(subbuild_joinrel_restrictlist(joinrel,
outer_rel->joininfo),
subbuild_joinrel_restrictlist(joinrel,
inner_rel->joininfo));
}
static void
build_joinrel_joinlist(RelOptInfo *joinrel,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel)
{
subbuild_joinrel_joinlist(joinrel, outer_rel->joininfo);
subbuild_joinrel_joinlist(joinrel, inner_rel->joininfo);
}
static List *
subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
List *joininfo_list)
{
List *restrictlist = NIL;
List *xjoininfo;
foreach(xjoininfo, joininfo_list)
{
JoinInfo *joininfo = (JoinInfo *) lfirst(xjoininfo);
Relids new_unjoined_relids;
new_unjoined_relids = set_differencei(joininfo->unjoined_relids,
joinrel->relids);
if (new_unjoined_relids == NIL)
{
/*
* Clauses in this JoinInfo list become restriction clauses
* for the joinrel, since they refer to no outside rels.
*
* We must copy the list to avoid disturbing the input relation,
* but we can use a shallow copy.
*/
restrictlist = nconc(restrictlist,
listCopy(joininfo->jinfo_restrictinfo));
}
else
{
/*
* These clauses are still join clauses at this level,
* so we ignore them in this routine.
*/
}
}
return restrictlist;
}
static void
subbuild_joinrel_joinlist(RelOptInfo *joinrel,
List *joininfo_list)
{
List *xjoininfo;
foreach(xjoininfo, joininfo_list)
{
JoinInfo *joininfo = (JoinInfo *) lfirst(xjoininfo);
Relids new_unjoined_relids;
new_unjoined_relids = set_differencei(joininfo->unjoined_relids,
joinrel->relids);
if (new_unjoined_relids == NIL)
{
/*
* Clauses in this JoinInfo list become restriction clauses
* for the joinrel, since they refer to no outside rels.
* So we can ignore them in this routine.
*/
}
else
{
/*
* These clauses are still join clauses at this level,
* so find or make the appropriate JoinInfo item for the joinrel,
* and add the clauses to it (eliminating duplicates).
*/
JoinInfo *new_joininfo;
new_joininfo = find_joininfo_node(joinrel, new_unjoined_relids);
new_joininfo->jinfo_restrictinfo =
LispUnion(new_joininfo->jinfo_restrictinfo,
joininfo->jinfo_restrictinfo);
}
}
}