/*------------------------------------------------------------------------- * * joinrels.c * Routines to determine which relations should be joined * * Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.39 1999/08/16 02:17:51 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #ifdef HAVE_LIMITS_H #include #ifndef MAXINT #define MAXINT INT_MAX #endif #else #ifdef HAVE_VALUES_H #include #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 void set_joinrel_size(RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel); /* * 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. * * Returns a list of rel nodes corresponding to the new join relations. */ List * make_rels_by_joins(Query *root, List *old_rels) { List *join_list = NIL; List *r; foreach(r, old_rels) { RelOptInfo *old_rel = (RelOptInfo *) lfirst(r); List *joined_rels; if (!(joined_rels = make_rels_by_clause_joins(root, old_rel, old_rel->joininfo, NIL))) { /* * 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)); } 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) { 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)); /* 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); /* 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 (only_relids == NIL) /* no bushy plans for geqo */ { 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 *join_rel = lfirst(r); Assert(length(join_rel->relids) > 1); if (is_subset(unjoined_relids, join_rel->relids) && nonoverlap_sets(old_rel->relids, join_rel->relids)) { joined_rel = make_join_rel(old_rel, join_rel); join_list = lappend(join_list, joined_rel); } } } } 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'. * * Returns a list of new join relations. */ List * make_rels_by_clauseless_joins(RelOptInfo *old_rel, List *inner_rels) { List *join_list = NIL; List *i; foreach(i, inner_rels) { RelOptInfo *inner_rel = (RelOptInfo *) lfirst(i); if (nonoverlap_sets(inner_rel->relids, old_rel->relids)) { join_list = lappend(join_list, make_join_rel(old_rel, inner_rel)); } } return join_list; } /* * 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->indexed = false; joinrel->pages = 0; joinrel->tuples = 0; joinrel->size = 0; joinrel->width = 0; /* joinrel->targetlist = NIL;*/ joinrel->pathlist = NIL; joinrel->cheapestpath = (Path *) NULL; joinrel->pruneable = true; joinrel->classlist = NULL; joinrel->indexkeys = NULL; joinrel->ordering = NULL; joinrel->relam = InvalidOid; 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. */ build_joinrel_restrict_and_join(joinrel, nconc(copyObject(outer_rel->joininfo), copyObject(inner_rel->joininfo)), nconc(listCopy(outer_rel->relids), listCopy(inner_rel->relids))); set_joinrel_size(joinrel, outer_rel, inner_rel); return joinrel; } /* * new_join_tlist * Builds a join relations'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 this seems to be a dead test --- we don't keep track of joinlists * for individual targetlist entries anymore, if we ever did... * * '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 * * 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; List *join_list = NIL; foreach(i, tlist) { TargetEntry *xtl = lfirst(i); bool in_final_tlist; /* * 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)); } } return t_list; } /* * 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: the elements of joininfo_list have all been COPIED and so can safely * be destructively modified and/or inserted in the new joinrel's lists. * The amount of copying going on here is probably vastly excessive, * since we copied the underlying clauses as well... */ static void build_joinrel_restrict_and_join(RelOptInfo *joinrel, List *joininfo_list, Relids join_relids) { List *output_restrictinfo_list = NIL; List *output_joininfo_list = NIL; 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... */ output_restrictinfo_list = LispUnion(output_restrictinfo_list, joininfo->jinfo_restrictinfo); } else { JoinInfo *old_joininfo; /* * There might already be a JoinInfo with the same set of * unjoined relids in output_joininfo_list; don't make a * redundant entry. */ old_joininfo = joininfo_member(new_unjoined_relids, output_joininfo_list); if (old_joininfo) { old_joininfo->jinfo_restrictinfo = LispUnion(old_joininfo->jinfo_restrictinfo, joininfo->jinfo_restrictinfo); } else { joininfo->unjoined_relids = new_unjoined_relids; output_joininfo_list = lcons(joininfo, output_joininfo_list); } } } joinrel->restrictinfo = output_restrictinfo_list; joinrel->joininfo = output_joininfo_list; } /* * 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. */ RelOptInfo * get_cheapest_complete_rel(List *join_rel_list) { RelOptInfo *final_rel = NULL; List *xrel; /* * 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...) */ foreach(xrel, join_rel_list) { RelOptInfo *rel = (RelOptInfo *) lfirst(xrel); bool final = true; List *xjoininfo; foreach(xjoininfo, rel->joininfo) { JoinInfo *joininfo = (JoinInfo *) lfirst(xjoininfo); 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; } return final_rel; } static void set_joinrel_size(RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel) { double dtuples; int ntuples; /* avoid overflow ... probably, tuple estimates in RelOptInfo * just ought to be double ... */ dtuples = (double) outer_rel->tuples * (double) inner_rel->tuples; if (joinrel->restrictinfo != NULL) dtuples *= product_selec(joinrel->restrictinfo); if (dtuples >= MAXINT) /* avoid overflow */ ntuples = MAXINT; else ntuples = (int) dtuples; /* * I bet sizes less than 1 will screw up optimization so make the best * case 1 instead of 0 - jolly */ if (ntuples < 1) ntuples = 1; joinrel->tuples = ntuples; } /* * Subset-inclusion tests on integer lists. * * XXX these probably ought to be in nodes/list.c or some such place. */ bool nonoverlap_sets(List *s1, List *s2) { List *x; foreach(x, s1) { int e = lfirsti(x); if (intMember(e, s2)) return false; } return true; } bool is_subset(List *s1, List *s2) { List *x; foreach(x, s1) { int e = lfirsti(x); if (!intMember(e, s2)) return false; } return true; }