Summary ------- These directories take the Query structure returned by the parser, and generate a plan used by the executor. The /plan directory generates the actual output plan, the /path code generates all possible ways to join the tables, and /prep handles special cases like inheritance. /util is utility stuff. /geqo is the separate "genetic optimization" planner --- it does a semi-random search through the join tree space, rather than exhaustively considering all possible join trees. (But each join considered by geqo is given to /path to create paths for, so we consider all possible implementation paths for each specific join even in GEQO mode.) Join Tree Construction ---------------------- The optimizer generates optimal query plans by doing a more-or-less exhaustive search through the ways of executing the query. During the planning/optimizing process, we build "Path" trees representing the different ways of doing a query. We select the cheapest Path that generates the desired relation and turn it into a Plan to pass to the executor. (There is pretty much a one-to-one correspondence between the Path and Plan trees, but Path nodes omit info that won't be needed during planning, and include info needed for planning that won't be needed by the executor.) The best Path tree is found by a recursive process: 1) Take each base relation in the query, and make a RelOptInfo structure for it. Find each potentially useful way of accessing the relation, including sequential and index scans, and make a Path representing that way. All the Paths made for a given relation are placed in its RelOptInfo.pathlist. (Actually, we discard Paths that are obviously inferior alternatives before they ever get into the pathlist --- what ends up in the pathlist is the cheapest way of generating each potentially useful sort ordering of the relation.) Also create RelOptInfo.joininfo nodes that list all the joins that involve this relation. For example, the WHERE clause "tab1.col1 = tab2.col1" generates a JoinInfo for tab1 listing tab2 as an unjoined relation, and also one for tab2 showing tab1 as an unjoined relation. If we have only a single base relation in the query, we are done here. Otherwise we have to figure out how to join the base relations into a single join relation. 2) Consider joining each RelOptInfo to each other RelOptInfo specified in its RelOptInfo.joininfo, and generate a Path for each possible join method. (If we have a RelOptInfo with no join clauses, we have no choice but to generate a clauseless Cartesian-product join; so we consider joining that rel to each other available rel. But in the presence of join clauses we will only consider joins that use available join clauses.) At this stage each input RelOptInfo is a single relation, so we are joining every relation to the other relations as joined in the WHERE clause. We generate a new "join" RelOptInfo for each possible combination of two "base" RelOptInfos, and put all the plausible paths for that combination into the join RelOptInfo's pathlist. (As before, we keep only the cheapest alternative that generates any one sort ordering of the result.) Joins always occur using two RelOptInfos. One is outer, the other inner. Outers drive lookups of values in the inner. In a nested loop, lookups of values in the inner occur by scanning the inner path once per outer tuple to find each matching inner row. In a mergejoin, inner and outer rows are ordered, and are accessed in order, so only one scan is required to perform the entire join: both inner and outer paths are scanned in-sync. (There's not a lot of difference between inner and outer in a mergejoin...) In a hashjoin, the inner is scanned first and all its rows are entered in a hashtable, then the outer is scanned and for each row we lookup the join key in the hashtable. A Path for a join relation is actually a tree structure, with the top Path node representing the join method. It has left and right subpaths that represent the scan methods used for the two input relations. 3) If we only had two base relations, we are done: we just pick the cheapest path for the join RelOptInfo. If we had more than two, we now need to consider ways of joining join RelOptInfos to each other to make join RelOptInfos that represent more than two base relations. The join tree is constructed using a "dynamic programming" algorithm: in the first pass (already described) we consider ways to create join rels representing exactly two base relations. The second pass considers ways to make join rels that represent exactly three base relations; the next pass, four relations, etc. The last pass considers how to make the final join relation that includes all base rels --- obviously there can be only one join rel at this top level, whereas there can be more than one join rel at lower levels. At each level we use joins that follow available join clauses, if possible, just as described for the first level. For example: SELECT * FROM tab1, tab2, tab3, tab4 WHERE tab1.col = tab2.col AND tab2.col = tab3.col AND tab3.col = tab4.col Tables 1, 2, 3, and 4 are joined as: {1 2},{2 3},{3 4} {1 2 3},{2 3 4} {1 2 3 4} (other possibilities will be excluded for lack of join clauses) SELECT * FROM tab1, tab2, tab3, tab4 WHERE tab1.col = tab2.col AND tab1.col = tab3.col AND tab1.col = tab4.col Tables 1, 2, 3, and 4 are joined as: {1 2},{1 3},{1 4} {1 2 3},{1 3 4},{1 2 4} {1 2 3 4} We consider left-handed plans (the outer rel of an upper join is a joinrel, but the inner is always a base rel); right-handed plans (outer rel is always a base rel); and bushy plans (both inner and outer can be joins themselves). For example, when building {1 2 3 4} we consider joining {1 2 3} to {4} (left-handed), {4} to {1 2 3} (right-handed), and {1 2} to {3 4} (bushy), among other choices. Although the jointree scanning code produces these potential join combinations one at a time, all the ways to produce the same set of joined base rels will share the same RelOptInfo, so the paths produced from different join combinations that produce equivalent joinrels will compete in add_pathlist. Once we have built the final join rel, we use either the cheapest path for it or the cheapest path with the desired ordering (if that's cheaper than applying a sort to the cheapest other path). Optimizer Functions ------------------- planner() handle inheritance by processing separately -init_query_planner() preprocess target list preprocess qualifications(WHERE) --query_planner() simplify constant subexpressions canonicalize_qual() Attempt to reduce WHERE clause to either CNF or DNF canonical form. CNF (top-level-AND) is preferred, since the optimizer can then use any of the AND subclauses to filter tuples; but quals that are in or close to DNF form will suffer exponential expansion if we try to force them to CNF. In pathological cases either transform may expand the qual unreasonably; so we may have to leave it un-normalized, thereby reducing the accuracy of selectivity estimates. pull out constants from target list get a target list that only contains column names, no expressions if none, then return ---subplanner() make list of relations in target make list of relations in where clause split up the qual into restrictions (a=1) and joins (b=c) find relation clauses can do merge sort and hash joins ----make_one_rel() set_base_rel_pathlist() find scan and all index paths for each relation find selectivity of columns used in joins -----make_one_rel_by_joins() jump to geqo if needed else call make_rels_by_joins() for each level of join tree needed make_rels_by_joins(): For each joinrel of the prior level, do make_rels_by_clause_joins() if it has join clauses, or make_rels_by_clauseless_joins() if not. Also generate "bushy plan" joins between joinrels of lower levels. Back at make_one_rel_by_joins(), apply set_cheapest() to extract the cheapest path for each newly constructed joinrel. Loop back if this wasn't the top join level. do group(GROUP) do aggregate put back constants re-flatten target list make unique(DISTINCT) make sort(ORDER BY) Optimizer Data Structures ------------------------- RelOptInfo - a relation or joined relations RestrictInfo - restriction clauses, like "x = 3" JoinInfo - join clauses, including the relids needed for the join Path - every way to generate a RelOptInfo(sequential,index,joins) SeqScan - a plain Path node with nodeTag = T_SeqScan IndexPath - index scans NestPath - nested-loop joins MergePath - merge joins HashPath - hash joins PathKeys - a data structure representing the ordering of a path The optimizer spends a good deal of its time worrying about the ordering of the tuples returned by a path. The reason this is useful is that by knowing the sort ordering of a path, we may be able to use that path as the left or right input of a mergejoin and avoid an explicit sort step. Nestloops and hash joins don't really care what the order of their inputs is, but mergejoin needs suitably ordered inputs. Therefore, all paths generated during the optimization process are marked with their sort order (to the extent that it is known) for possible use by a higher-level merge. It is also possible to avoid an explicit sort step to implement a user's ORDER BY clause if the final path has the right ordering already, so the sort ordering is of interest even at the top level. subplanner() will look for the cheapest path with a sort order matching the desired order, and will compare its cost to the cost of using the cheapest-overall path and doing an explicit sort. When we are generating paths for a particular RelOptInfo, we discard a path if it is more expensive than another known path that has the same or better sort order. We will never discard a path that is the only known way to achieve a given sort order (without an explicit sort, that is). In this way, the next level up will have the maximum freedom to build mergejoins without sorting, since it can pick from any of the paths retained for its inputs. See path/pathkeys.c for an explanation of the PathKeys data structure that represents what is known about the sort order of a particular Path.
-
Tom Lane authored
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'.
d8733ce6