/*------------------------------------------------------------------------- * * 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.32 1999/02/22 06:08:48 momjian Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "nodes/pg_list.h" #include "nodes/relation.h" #include "optimizer/internal.h" #include "optimizer/cost.h" #include "optimizer/paths.h" #include "optimizer/tlist.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" static List *new_joininfo_list(List *joininfo_list, Relids join_relids); static bool nonoverlap_sets(List *s1, List *s2); static bool is_subset(List *s1, List *s2); static void set_joinrel_size(RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel, JoinInfo *jinfo); /* * make_rels_by_joins * Find all possible joins for each of the outer join relations in * 'outer_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 *joined_rels = NIL; List *join_list = NIL; List *r = NIL; foreach(r, old_rels) { RelOptInfo *old_rel = (RelOptInfo *) lfirst(r); 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 * Determines whether joins can be performed between an outer relation * 'outer_rel' and those relations within 'outer_rel's joininfo nodes * (i.e., relations that participate in join clauses that 'outer_rel' * participates in). This is possible if all but one of the relations * contained within the join clauses of the joininfo node are already * contained within 'outer_rel'. * * 'outer_rel' is the relation entry for the outer relation * 'joininfo_list' is a list of join clauses which 'outer_rel' * participates in * * 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 = NIL; foreach(i, joininfo_list) { JoinInfo *joininfo = (JoinInfo *) lfirst(i); RelOptInfo *joined_rel; Relids unjoined_relids = joininfo->unjoined_relids; if (unjoined_relids != NIL) { if (length(unjoined_relids) == 1 && (only_relids == NIL || /* geqo only wants certain relids to make new rels */ intMember(lfirsti(unjoined_relids), only_relids))) { joined_rel = make_join_rel(old_rel, get_base_rel(root, lfirsti(unjoined_relids)), joininfo); join_list = lappend(join_list, joined_rel); /* Right-sided plan */ if (length(old_rel->relids) > 1) { joined_rel = make_join_rel( get_base_rel(root, lfirsti(unjoined_relids)), old_rel, joininfo); join_list = lappend(join_list, joined_rel); } } if (only_relids == NIL) /* no bushy from geqo */ { List *r; 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, joininfo); join_list = lappend(join_list, joined_rel); } } } } } return join_list; } /* * make_rels_by_clauseless_joins * Given an outer relation 'outer_rel' and a list of inner relations * 'inner_rels', create a join relation between 'outer_rel' and each * member of 'inner_rels' that isn't already included in 'outer_rel'. * * Returns a list of new join relations. */ List * make_rels_by_clauseless_joins(RelOptInfo *old_rel, List *inner_rels) { RelOptInfo *inner_rel; List *t_list = NIL; List *i = NIL; foreach(i, inner_rels) { inner_rel = (RelOptInfo *) lfirst(i); if (nonoverlap_sets(inner_rel->relids, old_rel->relids)) { t_list = lappend(t_list, make_join_rel(old_rel, inner_rel, (JoinInfo *) NULL)); } } return t_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 * 'joininfo' is the joininfo node(join clause) containing both * 'outer_rel' and 'inner_rel', if any exists * * Returns the new join relation node. */ RelOptInfo * make_join_rel(RelOptInfo *outer_rel, RelOptInfo *inner_rel, JoinInfo *joininfo) { RelOptInfo *joinrel = makeNode(RelOptInfo); List *joinrel_joininfo_list = NIL; List *new_outer_tlist; List *new_inner_tlist; /* * 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->relids = NIL; joinrel->indexed = false; joinrel->pages = 0; joinrel->tuples = 0; joinrel->width = 0; /* joinrel->targetlist = NIL;*/ joinrel->pathlist = NIL; joinrel->cheapestpath = (Path *) NULL; joinrel->pruneable = true; joinrel->classlist = NULL; joinrel->relam = InvalidOid; joinrel->ordering = NULL; joinrel->restrictinfo = NIL; joinrel->joininfo = NULL; joinrel->innerjoin = NIL; /* * This function uses a trick to pass inner/outer rels as * different lists, and then flattens it out later. bjm */ joinrel->relids = lcons(outer_rel->relids, lcons(inner_rel->relids, NIL)); new_outer_tlist = nconc(new_outer_tlist, new_inner_tlist); joinrel->targetlist = new_outer_tlist; if (joininfo) joinrel->restrictinfo = joininfo->jinfo_restrictinfo; joinrel_joininfo_list = new_joininfo_list( nconc(copyObject(outer_rel->joininfo), copyObject(inner_rel->joininfo)), nconc(listCopy(outer_rel->relids), listCopy(inner_rel->relids))); joinrel->joininfo = joinrel_joininfo_list; set_joinrel_size(joinrel, outer_rel, inner_rel, joininfo); 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'. * * '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. */ List * new_join_tlist(List *tlist, int first_resdomno) { int resdomno = first_resdomno - 1; TargetEntry *xtl = NULL; List *t_list = NIL; List *i = NIL; List *join_list = NIL; bool in_final_tlist = false; foreach(i, tlist) { xtl = 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)); } } return t_list; } /* * new_joininfo_list * Builds a join relation's joininfo list by checking for join clauses * which still need to used in future joins involving this relation. A * join clause is still needed if there are still relations in the clause * not contained in the list of relations comprising this join relation. * New joininfo nodes are only created and added to * 'current_joininfo_list' if a node for a particular join hasn't already * been created. * * 'current_joininfo_list' contains a list of those joininfo nodes that * have already been built * 'joininfo_list' is the list of join clauses involving this relation * 'join_relids' is a list of relids corresponding to the relations * currently being joined * * Returns a list of joininfo nodes, new and old. */ static List * new_joininfo_list(List *joininfo_list, Relids join_relids) { List *current_joininfo_list = NIL; Relids new_unjoined_relids = NIL; JoinInfo *other_joininfo = (JoinInfo *) NULL; List *xjoininfo = NIL; foreach(xjoininfo, joininfo_list) { List *or; JoinInfo *joininfo = (JoinInfo *) lfirst(xjoininfo); new_unjoined_relids = joininfo->unjoined_relids; foreach(or, new_unjoined_relids) { if (intMember(lfirsti(or), join_relids)) new_unjoined_relids = lremove((void *) lfirst(or), new_unjoined_relids); } joininfo->unjoined_relids = new_unjoined_relids; if (new_unjoined_relids != NIL) { other_joininfo = joininfo_member(new_unjoined_relids, current_joininfo_list); if (other_joininfo) { other_joininfo->jinfo_restrictinfo = (List *) LispUnion(joininfo->jinfo_restrictinfo, other_joininfo->jinfo_restrictinfo); } else { other_joininfo = makeNode(JoinInfo); other_joininfo->unjoined_relids = joininfo->unjoined_relids; other_joininfo->jinfo_restrictinfo = joininfo->jinfo_restrictinfo; other_joininfo->mergejoinable = joininfo->mergejoinable; other_joininfo->hashjoinable = joininfo->hashjoinable; current_joininfo_list = lcons(other_joininfo, current_joininfo_list); } } } return current_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) { List *xrel = NIL; RelOptInfo *final_rel = NULL; /* * find the relations that have no further joins, i.e., its joininfos * all have unjoined_relids nil. */ foreach(xrel, join_rel_list) { RelOptInfo *rel = (RelOptInfo *) lfirst(xrel); List *xjoininfo = NIL; bool final = true; 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 bool nonoverlap_sets(List *s1, List *s2) { List *x = NIL; foreach(x, s1) { int e = lfirsti(x); if (intMember(e, s2)) return false; } return true; } static bool is_subset(List *s1, List *s2) { List *x = NIL; foreach(x, s1) { int e = lfirsti(x); if (!intMember(e, s2)) return false; } return true; } static void set_joinrel_size(RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel, JoinInfo *jinfo) { int ntuples; float selec; /* * voodoo magic. but better than a size of 0. I have no idea why we * didn't set the size before. -ay 2/95 */ if (jinfo == NULL) { /* worst case: the cartesian product */ ntuples = outer_rel->tuples * inner_rel->tuples; } else { selec = product_selec(jinfo->jinfo_restrictinfo); /* ntuples = Min(outer_rel->tuples,inner_rel->tuples) * selec; */ ntuples = outer_rel->tuples * inner_rel->tuples * selec; } /* * 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; }