aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/path/joinrels.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/path/joinrels.c')
-rw-r--r--src/backend/optimizer/path/joinrels.c494
1 files changed, 179 insertions, 315 deletions
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 5fc673e5dba..e872b67623a 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -8,399 +8,263 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.42 2000/02/06 03:27:32 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.43 2000/02/07 04:40:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
-#ifdef HAVE_LIMITS_H
-#include <limits.h>
-#ifndef MAXINT
-#define MAXINT INT_MAX
-#endif
-#else
-#ifdef HAVE_VALUES_H
-#include <values.h>
-#endif
-#endif
-
#include "optimizer/cost.h"
#include "optimizer/joininfo.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/tlist.h"
-static RelOptInfo *make_join_rel(RelOptInfo *outer_rel, RelOptInfo *inner_rel);
-static List *new_join_tlist(List *tlist, int first_resdomno);
-static void build_joinrel_restrict_and_join(RelOptInfo *joinrel,
- List *joininfo_list,
- Relids join_relids);
+
+static RelOptInfo *make_join_rel(Query *root, RelOptInfo *rel1,
+ RelOptInfo *rel2);
+
/*
* make_rels_by_joins
- * Find all possible joins for each of the outer join relations in
- * 'old_rels'. A rel node is created for each possible join relation,
- * and the resulting list of nodes is returned. If at all possible, only
- * those relations for which join clauses exist are considered. If none
- * of these exist for a given relation, all remaining possibilities are
- * considered.
+ * Consider ways to produce join relations containing exactly 'level'
+ * base relations. (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.
*
- * Returns a list of rel nodes corresponding to the new join relations.
+ * Returns nothing, but adds entries to root->join_rel_list.
*/
-List *
-make_rels_by_joins(Query *root, List *old_rels)
+void
+make_rels_by_joins(Query *root, int level)
{
- List *join_list = NIL;
List *r;
- foreach(r, old_rels)
+ /*
+ * 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.
+ *
+ * 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
+ * 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.
+ */
+ 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))
{
RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
- List *joined_rels;
+ int old_level = length(old_rel->relids);
+ List *other_rels;
+
+ if (old_level != level-1)
+ break;
- if (!(joined_rels = make_rels_by_clause_joins(root, old_rel,
- old_rel->joininfo,
- NIL)))
+ if (level == 2)
+ other_rels = lnext(r); /* only consider remaining base rels */
+ else
+ other_rels = root->base_rel_list; /* consider all base rels */
+
+ if (old_rel->joininfo != NIL)
+ {
+ /*
+ * Note that if all available join clauses for this rel require
+ * more than one other rel, we will fail to make any joins against
+ * it here. That's OK; it'll be considered by "bushy plan" join
+ * code in a higher-level pass.
+ */
+ make_rels_by_clause_joins(root,
+ old_rel,
+ other_rels);
+ }
+ else
{
/*
* Oops, we have a relation that is not joined to any other
* relation. Cartesian product time.
*/
- joined_rels = make_rels_by_clauseless_joins(old_rel,
- root->base_rel_list);
- joined_rels = nconc(joined_rels,
- make_rels_by_clauseless_joins(old_rel,
- old_rels));
+ make_rels_by_clauseless_joins(root,
+ old_rel,
+ other_rels);
}
-
- join_list = nconc(join_list, joined_rels);
}
- return join_list;
-}
-
-/*
- * make_rels_by_clause_joins
- * Build joins between an outer relation 'old_rel' and relations
- * within old_rel's joininfo nodes
- * (i.e., relations that participate in join clauses that 'old_rel'
- * also participates in).
- *
- * 'old_rel' is the relation entry for the outer relation
- * 'joininfo_list' is a list of join clauses which 'old_rel'
- * participates in
- * 'only_relids': if not NIL, only joins against base rels mentioned in
- * only_relids are allowable.
- *
- * Returns a list of new join relations.
- */
-List *
-make_rels_by_clause_joins(Query *root, RelOptInfo *old_rel,
- List *joininfo_list, Relids only_relids)
-{
- List *join_list = NIL;
- List *i;
-
- foreach(i, joininfo_list)
+ /*
+ * 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.
+ *
+ * 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))
{
- JoinInfo *joininfo = (JoinInfo *) lfirst(i);
- Relids unjoined_relids = joininfo->unjoined_relids;
- RelOptInfo *joined_rel;
-
- if (unjoined_relids == NIL)
- continue; /* probably can't happen */
-
- if (length(unjoined_relids) == 1 &&
- (only_relids == NIL ||
- /* geqo only wants certain relids to be joined to old_rel */
- intMember(lfirsti(unjoined_relids), only_relids)))
- {
- RelOptInfo *base_rel = get_base_rel(root,
- lfirsti(unjoined_relids));
+ RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
+ int old_level = length(old_rel->relids);
+ List *r2;
- /* Left-sided join of outer rel against a single base rel */
- joined_rel = make_join_rel(old_rel, base_rel);
- join_list = lappend(join_list, joined_rel);
+ /* We can quit once past the halfway point (make_join_rel took care
+ * of making the opposite-direction joins)
+ */
+ if (old_level * 2 < level)
+ break;
- /* Consider right-sided plan as well */
- if (length(old_rel->relids) > 1)
- {
- joined_rel = make_join_rel(base_rel, old_rel);
- join_list = lappend(join_list, joined_rel);
- }
- }
+ if (old_rel->joininfo == NIL)
+ continue; /* we ignore clauseless joins here */
- if (only_relids == NIL) /* no bushy plans for geqo */
+ foreach(r2, lnext(r))
{
- List *r;
-
- /* Build "bushy" plans: join old_rel against all pre-existing
- * joins of rels it doesn't already contain, if there is a
- * suitable join clause.
- */
- foreach(r, root->join_rel_list)
+ RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
+ int new_level = length(new_rel->relids);
+
+ 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))
{
- RelOptInfo *join_rel = lfirst(r);
+ List *i;
- Assert(length(join_rel->relids) > 1);
- if (is_subseti(unjoined_relids, join_rel->relids) &&
- nonoverlap_setsi(old_rel->relids, join_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)
{
- joined_rel = make_join_rel(old_rel, join_rel);
- join_list = lappend(join_list, joined_rel);
+ JoinInfo *joininfo = (JoinInfo *) lfirst(i);
+
+ if (is_subseti(joininfo->unjoined_relids, new_rel->relids))
+ {
+ make_join_rel(root, old_rel, new_rel);
+ break;
+ }
}
}
}
}
-
- return join_list;
}
/*
- * make_rels_by_clauseless_joins
- * Given an outer relation 'old_rel' and a list of inner relations
- * 'inner_rels', create a join relation between 'old_rel' and each
- * member of 'inner_rels' that isn't already included in 'old_rel'.
+ * make_rels_by_clause_joins
+ * 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.
+ *
+ * '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
+ * 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
+ * only succeed when other_rel is not already part of old_rel.)
*
- * Returns a list of new join relations.
+ * 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.)
*/
-List *
-make_rels_by_clauseless_joins(RelOptInfo *old_rel, List *inner_rels)
+RelOptInfo *
+make_rels_by_clause_joins(Query *root,
+ RelOptInfo *old_rel,
+ List *other_rels)
{
- List *join_list = NIL;
- List *i;
+ RelOptInfo *result = NULL;
+ List *i,
+ *j;
- foreach(i, inner_rels)
+ foreach(i, old_rel->joininfo)
{
- RelOptInfo *inner_rel = (RelOptInfo *) lfirst(i);
+ JoinInfo *joininfo = (JoinInfo *) lfirst(i);
+ Relids unjoined_relids = joininfo->unjoined_relids;
- if (nonoverlap_setsi(inner_rel->relids, old_rel->relids))
+ foreach(j, other_rels)
{
- join_list = lappend(join_list,
- make_join_rel(old_rel, inner_rel));
+ RelOptInfo *other_rel = (RelOptInfo *) lfirst(j);
+
+ if (is_subseti(unjoined_relids, other_rel->relids))
+ result = make_join_rel(root, old_rel, other_rel);
}
}
- return join_list;
+ return result;
}
/*
- * make_join_rel
- * Creates and initializes a new join relation.
- *
- * 'outer_rel' and 'inner_rel' are relation nodes for the relations to be
- * joined
- *
- * Returns the new join relation node.
- */
-static RelOptInfo *
-make_join_rel(RelOptInfo *outer_rel, RelOptInfo *inner_rel)
-{
- RelOptInfo *joinrel = makeNode(RelOptInfo);
- List *new_outer_tlist;
- List *new_inner_tlist;
-
- /*
- * This function uses a trick to pass inner/outer rels as two sublists.
- * The list will be flattened out in update_rels_pathlist_for_joins().
- */
- joinrel->relids = lcons(outer_rel->relids, lcons(inner_rel->relids, NIL));
- 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->restrictinfo = 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.
- */
- 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.
- *
- * nconc(listCopy(x), y) is an idiom for making a new list without
- * changing either input list.
- */
- build_joinrel_restrict_and_join(joinrel,
- nconc(listCopy(outer_rel->joininfo),
- inner_rel->joininfo),
- nconc(listCopy(outer_rel->relids),
- inner_rel->relids));
-
- 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'.
+ * make_rels_by_clauseless_joins
+ * 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'.
*
- * XXX this seems to be a dead test --- we don't keep track of joinlists
- * for individual targetlist entries anymore, if we ever did...
+ * 'old_rel' is the relation entry for the relation to be joined
+ * 'other_rels': other rels to be considered for joining
*
- * 'tlist' is the target list of one of the join relations
- * 'other_relids' is a list of relids contained within the other
- * join relation
- * 'first_resdomno' is the resdom number to use for the first created
- * target list entry
+ * Currently, this is only used with base rels in other_rels, but it would
+ * work for joining to joinrels too.
*
- * Returns the new target list.
+ * 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.)
*/
-static List *
-new_join_tlist(List *tlist,
- int first_resdomno)
+RelOptInfo *
+make_rels_by_clauseless_joins(Query *root,
+ RelOptInfo *old_rel,
+ List *other_rels)
{
- int resdomno = first_resdomno - 1;
- List *t_list = NIL;
+ RelOptInfo *result = NULL;
List *i;
- List *join_list = NIL;
- foreach(i, tlist)
+ foreach(i, other_rels)
{
- TargetEntry *xtl = lfirst(i);
- bool in_final_tlist;
+ RelOptInfo *other_rel = (RelOptInfo *) lfirst(i);
- /*
- * XXX surely this is wrong? join_list is never changed? tgl
- * 2/99
- */
- in_final_tlist = (join_list == NIL);
- if (in_final_tlist)
- {
- resdomno += 1;
- t_list = lappend(t_list,
- create_tl_element(get_expr(xtl), resdomno));
- }
+ if (nonoverlap_setsi(other_rel->relids, old_rel->relids))
+ result = make_join_rel(root, old_rel, other_rel);
}
- return t_list;
+ return result;
}
-/*
- * build_joinrel_restrict_and_join
- * Builds a join relation's restrictinfo and joininfo lists from the
- * joininfo lists of the relations it joins. 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 put it into
- * the joinrel's restrictinfo list. (It will not need to be considered
- * further up the join tree.)
- *
- * 'joininfo_list' is a list of joininfo nodes from the relations being joined
- * 'join_relids' is a list of all base relids in the new join relation
- *
- * 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 add
- * the original nodes to the lists belonging to the join relation.
- */
-static void
-build_joinrel_restrict_and_join(RelOptInfo *joinrel,
- List *joininfo_list,
- Relids join_relids)
-{
- List *xjoininfo;
-
- foreach(xjoininfo, joininfo_list)
- {
- JoinInfo *joininfo = (JoinInfo *) lfirst(xjoininfo);
- Relids new_unjoined_relids;
-
- new_unjoined_relids = set_differencei(joininfo->unjoined_relids,
- join_relids);
- if (new_unjoined_relids == NIL)
- {
- /*
- * Clauses in this JoinInfo list become restriction clauses
- * for the joinrel, since they refer to no outside rels.
- *
- * Be careful to eliminate duplicates, since we will see the
- * same clauses arriving from both input relations...
- */
- joinrel->restrictinfo =
- LispUnion(joinrel->restrictinfo,
- joininfo->jinfo_restrictinfo);
- }
- 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);
- }
- }
-}
/*
- * get_cheapest_complete_rel
- * Find the join relation that includes all the original
- * relations, i.e. the final join result.
- *
- * 'join_rel_list' is a list of join relations.
- *
- * Returns the list of final join relations.
+ * make_join_rel
+ * Find or create a join RelOptInfo that represents the join of
+ * the two given rels, and add to it path information for paths
+ * 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.
*/
-RelOptInfo *
-get_cheapest_complete_rel(List *join_rel_list)
+static RelOptInfo *
+make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2)
{
- RelOptInfo *final_rel = NULL;
- List *xrel;
+ RelOptInfo *joinrel;
+ List *restrictlist;
/*
- * find the relations that have no further joins, i.e., its joininfos
- * all have unjoined_relids nil. (Actually, a JoinInfo shouldn't
- * ever have nil unjoined_relids, so I think this code is overly
- * complex. In fact it seems wrong; shouldn't we be looking for
- * rels with complete relids lists??? Seems like a cartesian-product
- * case could fail because sub-relations could have nil JoinInfo lists.
- * Doesn't actually fail but I don't really understand why...)
+ * Find or build the join RelOptInfo, and compute the restrictlist
+ * that goes with this particular joining.
*/
- foreach(xrel, join_rel_list)
- {
- RelOptInfo *rel = (RelOptInfo *) lfirst(xrel);
- bool final = true;
- List *xjoininfo;
-
- foreach(xjoininfo, rel->joininfo)
- {
- JoinInfo *joininfo = (JoinInfo *) lfirst(xjoininfo);
+ joinrel = get_join_rel(root, rel1, rel2, &restrictlist);
- if (joininfo->unjoined_relids != NIL)
- {
- final = false;
- break;
- }
- }
- if (final)
- if (final_rel == NULL ||
- path_is_cheaper(rel->cheapestpath, final_rel->cheapestpath))
- final_rel = rel;
- }
+ /*
+ * We 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);
- return final_rel;
+ return joinrel;
}