1 /*-------------------------------------------------------------------------
6 * Copyright (c) 1994, Regents of the University of California
10 * $Header: /cvsroot/pgsql/src/backend/optimizer/path/Attic/predmig.c,v 1.18 1999/02/13 23:16:22 momjian Exp $
12 *-------------------------------------------------------------------------
16 ** Main Routines to handle Predicate Migration (i.e. correct optimization
17 ** of queries with expensive functions.)
19 ** The reasoning behind some of these algorithms is rather detailed.
20 ** Have a look at Sequoia Tech Report 92/13 for more info. Also
21 ** see Monma and Sidney's paper "Sequencing with Series-Parallel
22 ** Precedence Constraints", in "Mathematics of Operations Research",
23 ** volume 4 (1979), pp. 215-224.
25 ** The main thing that this code does that wasn't handled in xfunc.c is
26 ** it considers the possibility that two joins in a stream may not
27 ** be ordered by ascending rank -- in such a scenario, it may be optimal
28 ** to pullup more restrictions than we did via xfunc_try_pullup.
30 ** This code in some sense generalizes xfunc_try_pullup; if you
31 ** run postgres -x noprune, you'll turn off xfunc_try_pullup, and this
32 ** code will do everything that xfunc_try_pullup would have, and maybe
33 ** more. However, this results in no pruning, which may slow down the
34 ** optimizer and/or cause the system to run out of memory.
38 #include "nodes/pg_list.h"
39 #include "nodes/nodes.h"
40 #include "nodes/primnodes.h"
41 #include "nodes/relation.h"
42 #include "utils/palloc.h"
43 #include "utils/elog.h"
44 #include "optimizer/xfunc.h"
45 #include "optimizer/pathnode.h"
46 #include "optimizer/internal.h"
47 #include "optimizer/cost.h"
48 #include "optimizer/keys.h"
49 #include "optimizer/tlist.h"
51 #define is_clause(node) (get_cinfo(node)) /* a stream node
53 * (not a join) iff it has
54 * a non-NULL cinfo field */
56 static void xfunc_predmig(JoinPath pathnode, Stream streamroot,
57 Stream laststream, bool *progressp);
58 static bool xfunc_series_llel(Stream stream);
59 static bool xfunc_llel_chains(Stream root, Stream bottom);
60 static Stream xfunc_complete_stream(Stream stream);
61 static bool xfunc_prdmig_pullup(Stream origstream, Stream pullme,
63 static void xfunc_form_groups(Stream root, Stream bottom);
64 static void xfunc_free_stream(Stream root);
65 static Stream xfunc_add_clauses(Stream current);
66 static void xfunc_setup_group(Stream node, Stream bottom);
67 static Stream xfunc_streaminsert(RestrictInfo restrictinfo, Stream current,
69 static int xfunc_num_relids(Stream node);
70 static StreamPtr xfunc_get_downjoin(Stream node);
71 static StreamPtr xfunc_get_upjoin(Stream node);
72 static Stream xfunc_stream_qsort(Stream root, Stream bottom);
73 static int xfunc_stream_compare(void *arg1, void *arg2);
74 static bool xfunc_check_stream(Stream node);
75 static bool xfunc_in_stream(Stream node, Stream stream);
77 /* ----------------- MAIN FUNCTIONS ------------------------ */
80 ** wrapper for Predicate Migration. It calls xfunc_predmig until no
81 ** more progress is made.
82 ** return value says if any changes were ever made.
85 xfunc_do_predmig(Path root)
94 Assert(IsA(root, JoinPath));
95 xfunc_predmig((JoinPath) root, (Stream) NULL, (Stream) NULL,
97 if (changed && progress)
98 elog(DEBUG, "Needed to do a second round of predmig!\n");
108 ** The main routine for Predicate Migration. It traverses a join tree,
109 ** and for each root-to-leaf path in the plan tree it constructs a
110 ** "Stream", which it passes to xfunc_series_llel for optimization.
111 ** Destructively modifies the join tree (via predicate pullup).
114 xfunc_predmig(JoinPath pathnode,/* root of the join tree */
116 Stream laststream,/* for recursive calls -- these are the
117 * root of the stream under construction,
118 * and the lowest node created so far */
124 * * traverse the join tree dfs-style, constructing a stream as you
125 * go. * When you hit a scan node, pass the stream off to
130 if ((!streamroot && laststream) ||
131 (streamroot && !laststream))
132 elog(ERROR, "called xfunc_predmig with bad inputs");
134 Assert(xfunc_check_stream(streamroot));
136 /* add path node to stream */
137 newstream = RMakeStream();
139 streamroot = newstream;
140 set_upstream(newstream, (StreamPtr) laststream);
142 set_downstream(laststream, (StreamPtr) newstream);
143 set_downstream(newstream, (StreamPtr) NULL);
144 set_pathptr(newstream, (pathPtr) pathnode);
145 set_cinfo(newstream, (RestrictInfo) NULL);
146 set_clausetype(newstream, XFUNC_UNKNOWN);
148 /* base case: we're at a leaf, call xfunc_series_llel */
149 if (!is_join(pathnode))
151 /* form a fleshed-out copy of the stream */
152 Stream fullstream = xfunc_complete_stream(streamroot);
154 /* sort it via series-llel */
155 if (xfunc_series_llel(fullstream))
158 /* free up the copy */
159 xfunc_free_stream(fullstream);
163 /* visit left child */
164 xfunc_predmig((JoinPath) get_outerjoinpath(pathnode),
165 streamroot, newstream, progressp);
167 /* visit right child */
168 xfunc_predmig((JoinPath) get_innerjoinpath(pathnode),
169 streamroot, newstream, progressp);
172 /* remove this node */
173 if (get_upstream(newstream))
174 set_downstream((Stream) get_upstream(newstream), (StreamPtr) NULL);
180 ** A flavor of Monma and Sidney's Series-Parallel algorithm.
181 ** Traverse stream downwards. When you find a node with restrictions on it,
182 ** call xfunc_llel_chains on the substream from root to that node.
185 xfunc_series_llel(Stream stream)
189 bool progress = false;
191 for (temp = stream; temp != (Stream) NULL; temp = next)
193 next = (Stream) xfunc_get_downjoin(temp);
196 * * if there are restrictions/secondary join clauses above this *
197 * node, call xfunc_llel_chains
199 if (get_upstream(temp) && is_clause((Stream) get_upstream(temp)))
200 if (xfunc_llel_chains(stream, temp))
208 ** A flavor of Monma and Sidney's Parallel Chains algorithm.
209 ** Given a stream which has been well-ordered except for its lowermost
210 ** restrictions/2-ary joins, pull up the restrictions/2-arys as appropriate.
211 ** What that means here is to form groups in the chain above the lowest
212 ** join node above bottom inclusive, and then take all the restrictions
213 ** following bottom, and try to pull them up as far as possible.
216 xfunc_llel_chains(Stream root, Stream bottom)
218 bool progress = false;
222 Stream rootcopy = root;
224 Assert(xfunc_check_stream(root));
226 /* xfunc_prdmig_pullup will need an unmodified copy of the stream */
227 origstream = (Stream) copyObject((Node) root);
229 /* form groups among ill-ordered nodes */
230 xfunc_form_groups(root, bottom);
232 /* sort chain by rank */
233 Assert(xfunc_in_stream(bottom, root));
234 rootcopy = xfunc_stream_qsort(root, bottom);
237 * * traverse sorted stream -- if any restriction has moved above a
238 * join, * we must pull it up in the plan. That is, make plan tree *
239 * reflect order of sorted stream.
241 for (tmpstream = rootcopy,
242 pathstream = (Stream) xfunc_get_downjoin(rootcopy);
243 tmpstream != (Stream) NULL && pathstream != (Stream) NULL;
244 tmpstream = (Stream) get_downstream(tmpstream))
246 if (is_clause(tmpstream)
247 && get_pathptr(pathstream) != get_pathptr(tmpstream))
251 * * If restriction moved above a Join after sort, we pull it *
252 * up in the join plan. * If restriction moved down, we
253 * ignore it. * This is because Joey's Sequoia paper proves
254 * that * restrictions should never move down. If this * one
255 * were moved down, it would violate "semantic correctness", *
256 * i.e. it would be lower than the attributes it references.
258 Assert(xfunc_num_relids(pathstream) > xfunc_num_relids(tmpstream));
259 progress = xfunc_prdmig_pullup(origstream, tmpstream,
260 (JoinPath) get_pathptr(pathstream));
262 if (get_downstream(tmpstream))
263 pathstream = (Stream) xfunc_get_downjoin((Stream) get_downstream(tmpstream));
266 /* free up origstream */
267 xfunc_free_stream(origstream);
272 ** xfunc_complete_stream
273 ** Given a stream composed of join nodes only, make a copy containing the
274 ** join nodes along with the associated restriction nodes.
277 xfunc_complete_stream(Stream stream)
281 curstream = (Stream) NULL;
283 copystream = (Stream) copyObject((Node) stream);
284 Assert(xfunc_check_stream(copystream));
286 curstream = copystream;
287 Assert(!is_clause(curstream));
289 /* curstream = (Stream)xfunc_get_downjoin(curstream); */
291 while (curstream != (Stream) NULL)
293 xfunc_add_clauses(curstream);
294 curstream = (Stream) xfunc_get_downjoin(curstream);
297 /* find top of stream and return it */
298 for (tmpstream = copystream; get_upstream(tmpstream) != (StreamPtr) NULL;
299 tmpstream = (Stream) get_upstream(tmpstream))
300 /* no body in for loop */ ;
306 ** xfunc_prdmig_pullup
307 ** pullup a clause in a path above joinpath. Since the JoinPath tree
308 ** doesn't have upward pointers, it's difficult to deal with. Thus we
309 ** require the original stream, which maintains pointers to all the path
310 ** nodes. We use the original stream to find out what joins are
314 xfunc_prdmig_pullup(Stream origstream, Stream pullme, JoinPath joinpath)
316 RestrictInfo restrictinfo = get_cinfo(pullme);
317 bool progress = false;
323 /* find node in origstream that contains clause */
324 for (orignode = origstream;
325 orignode != (Stream) NULL
326 && get_cinfo(orignode) != restrictinfo;
327 orignode = (Stream) get_downstream(orignode))
328 /* empty body in for loop */ ;
330 elog(ERROR, "Didn't find matching node in original stream");
333 /* pull up this node as far as it should go */
334 for (upjoin = (Stream) xfunc_get_upjoin(orignode);
335 upjoin != (Stream) NULL
336 && (JoinPath) get_pathptr((Stream) xfunc_get_downjoin(upjoin))
338 upjoin = (Stream) xfunc_get_upjoin(upjoin))
341 elog(DEBUG, "pulling up in xfunc_predmig_pullup!");
343 /* move clause up in path */
344 if (get_pathptr((Stream) get_downstream(upjoin))
345 == (pathPtr) get_outerjoinpath((JoinPath) get_pathptr(upjoin)))
349 restrictinfo = xfunc_pullup((Path) get_pathptr((Stream) get_downstream(upjoin)),
350 (JoinPath) get_pathptr(upjoin),
353 get_clausetype(orignode));
354 set_pathptr(pullme, get_pathptr(upjoin));
355 /* pullme has been moved into locrestrictinfo */
356 set_clausetype(pullme, XFUNC_LOCPRD);
359 * * xfunc_pullup makes new path nodes for children of *
360 * get_pathptr(current). We must modify the stream nodes to point *
361 * to these path nodes
363 if (whichchild == OUTER)
365 for (temp = (Stream) get_downstream(upjoin); is_clause(temp);
366 temp = (Stream) get_downstream(temp))
369 get_outerjoinpath((JoinPath) get_pathptr(upjoin)));
372 (pathPtr) get_outerjoinpath((JoinPath) get_pathptr(upjoin)));
376 for (temp = (Stream) get_downstream(upjoin); is_clause(temp);
377 temp = (Stream) get_downstream(temp))
380 get_innerjoinpath((JoinPath) get_pathptr(upjoin)));
383 get_innerjoinpath((JoinPath) get_pathptr(upjoin)));
388 elog(DEBUG, "didn't succeed in pulling up in xfunc_prdmig_pullup");
394 ** A group is a pair of stream nodes a,b such that a is constrained to
395 ** precede b (for instance if a and b are both joins), but rank(a) > rank(b).
396 ** In such a situation, Monma and Sidney prove that no clauses should end
397 ** up between a and b, and therefore we may treat them as a group, with
398 ** selectivity equal to the product of their selectivities, and cost
399 ** equal to the cost of the first plus the selectivity of the first times the
400 ** cost of the second. We define each node to be in a group by itself,
401 ** and then repeatedly find adjacent groups which are ordered by descending
402 ** rank, and make larger groups. You know that two adjacent nodes are in a
403 ** group together if the lower has groupup set to true. They will both have
404 ** the same groupcost and groupsel (since they're in the same group!)
407 xfunc_form_groups(Query *queryInfo, Stream root, Stream bottom)
411 int lowest = xfunc_num_relids((Stream) xfunc_get_upjoin(bottom));
417 return; /* no joins in stream, so no groups */
419 /* initialize groups to be single nodes */
421 temp != (Stream) NULL && temp != bottom;
422 temp = (Stream) get_downstream(temp))
425 if (!is_clause(temp))
427 if (get_pathptr((Stream) get_downstream(temp))
428 == (pathPtr) get_outerjoinpath((JoinPath) get_pathptr(temp)))
433 xfunc_join_expense((JoinPath) get_pathptr(temp),
435 if (primjoin = xfunc_primary_join((JoinPath) get_pathptr(temp)))
438 compute_clause_selec(queryInfo,
442 set_groupsel(temp, 1.0);
445 /* a restriction, or 2-ary join pred */
448 xfunc_expense(queryInfo,
449 get_clause(get_cinfo(temp))));
451 compute_clause_selec(queryInfo,
452 get_clause(get_cinfo(temp)),
455 set_groupup(temp, false);
458 /* make passes upwards, forming groups */
462 for (temp = (Stream) get_upstream(bottom);
463 temp != (Stream) NULL;
464 temp = (Stream) get_upstream(temp))
466 /* check for grouping with node upstream */
467 if (!get_groupup(temp) && /* not already grouped */
468 (parent = (Stream) get_upstream(temp)) != (Stream) NULL &&
469 /* temp is a join or temp is the top of a group */
470 (is_join((Path) get_pathptr(temp)) ||
471 get_downstream(temp) &&
472 get_groupup((Stream) get_downstream(temp))) &&
473 get_grouprank(parent) < get_grouprank(temp))
475 progress = true;/* we formed a new group */
476 set_groupup(temp, true);
478 get_groupcost(temp) +
479 get_groupsel(temp) * get_groupcost(parent));
480 set_groupsel(temp, get_groupsel(temp) * get_groupsel(parent));
482 /* fix costs and sels of all members of group */
483 xfunc_setup_group(temp, bottom);
490 /* ------------------- UTILITY FUNCTIONS ------------------------- */
494 ** walk down a stream and pfree it
497 xfunc_free_stream(Stream root)
502 Assert(xfunc_check_stream(root));
504 if (root != (Stream) NULL)
505 for (cur = root; cur != (Stream) NULL; cur = next)
507 next = (Stream) get_downstream(cur);
513 ** xfunc_add<_clauses
514 ** find any clauses above current, and insert them into stream as
515 ** appropriate. Return uppermost clause inserted, or current if none.
518 xfunc_add_clauses(Stream current)
520 Stream topnode = current;
524 /* first add in the local clauses */
525 foreach(temp, get_loc_restrictinfo((Path) get_pathptr(current)))
527 topnode = xfunc_streaminsert((RestrictInfo) lfirst(temp), topnode,
531 /* and add in the join clauses */
532 if (IsA(get_pathptr(current), JoinPath))
534 primjoin = xfunc_primary_join((JoinPath) get_pathptr(current));
535 foreach(temp, get_pathrestrictinfo((JoinPath) get_pathptr(current)))
537 if (!equal(get_clause((RestrictInfo) lfirst(temp)), primjoin))
538 topnode = xfunc_streaminsert((RestrictInfo) lfirst(temp), topnode,
548 ** find all elements of stream that are grouped with node and are above
549 ** bottom, and set their groupcost and groupsel to be the same as node's.
552 xfunc_setup_group(Stream node, Stream bottom)
557 /* traverse downwards */
558 for (temp = (Stream) get_downstream(node);
559 temp != (Stream) NULL && temp != bottom;
560 temp = (Stream) get_downstream(temp))
562 if (!get_groupup(temp))
566 set_groupcost(temp, get_groupcost(node));
567 set_groupsel(temp, get_groupsel(node));
571 /* traverse upwards */
572 for (temp = (Stream) get_upstream(node); temp != (Stream) NULL;
573 temp = (Stream) get_upstream(temp))
575 if (!get_groupup((Stream) get_downstream(temp)))
579 set_groupcost(temp, get_groupcost(node));
580 set_groupsel(temp, get_groupsel(node));
587 ** xfunc_streaminsert
588 ** Make a new Stream node to hold clause, and insert it above current.
592 xfunc_streaminsert(RestrictInfo restrictinfo,
594 int clausetype) /* XFUNC_LOCPRD or XFUNC_JOINPRD */
596 Stream newstream = RMakeStream();
598 set_upstream(newstream, get_upstream(current));
599 if (get_upstream(current))
600 set_downstream((Stream) (get_upstream(current)), (StreamPtr) newstream);
601 set_upstream(current, (StreamPtr) newstream);
602 set_downstream(newstream, (StreamPtr) current);
603 set_pathptr(newstream, get_pathptr(current));
604 set_cinfo(newstream, restrictinfo);
605 set_clausetype(newstream, clausetype);
610 ** Given a Stream node, find the number of relids referenced in the pathnode
611 ** associated with the stream node. The number of relids gives a unique
612 ** ordering on the joins in a stream, which we use to compare the height of
616 xfunc_num_relids(Stream node)
618 if (!node || !IsA(get_pathptr(node), JoinPath))
622 (get_relids(get_parent((JoinPath) get_pathptr(node)))));
626 ** xfunc_get_downjoin
627 ** Given a stream node, find the next lowest node which points to a
628 ** join predicate or a scan node.
631 xfunc_get_downjoin(Stream node)
635 if (!is_clause(node)) /* if this is a join */
636 node = (Stream) get_downstream(node);
637 for (temp = node; temp && is_clause(temp);
638 temp = (Stream) get_downstream(temp))
639 /* empty body in for loop */ ;
641 return (StreamPtr) temp;
646 ** same as above, but upwards.
649 xfunc_get_upjoin(Stream node)
653 if (!is_clause(node)) /* if this is a join */
654 node = (Stream) get_upstream(node);
655 for (temp = node; temp && is_clause(temp);
656 temp = (Stream) get_upstream(temp))
657 /* empty body in for loop */ ;
659 return (StreamPtr) temp;
663 ** xfunc_stream_qsort
664 ** Given a stream, sort by group rank the elements in the stream from the
665 ** node "bottom" up. DESTRUCTIVELY MODIFIES STREAM! Returns new root.
668 xfunc_stream_qsort(Stream root, Stream bottom)
676 /* find size of list */
677 for (num = 0, tmp = root; tmp != bottom;
678 tmp = (Stream) get_downstream(tmp))
683 /* copy elements of the list into an array */
684 nodearray = (Stream *) palloc(num * sizeof(Stream));
686 for (tmp = root, i = 0; tmp != bottom;
687 tmp = (Stream) get_downstream(tmp), i++)
691 qsort(nodearray, num, sizeof(LispValue), xfunc_stream_compare);
693 /* paste together the array elements */
694 output = nodearray[num - 1];
695 set_upstream(output, (StreamPtr) NULL);
696 for (i = num - 2; i >= 0; i--)
698 set_downstream(nodearray[i + 1], (StreamPtr) nodearray[i]);
699 set_upstream(nodearray[i], (StreamPtr) nodearray[i + 1]);
701 set_downstream(nodearray[0], (StreamPtr) bottom);
703 set_upstream(bottom, (StreamPtr) nodearray[0]);
705 Assert(xfunc_check_stream(output));
710 ** xfunc_stream_compare
711 ** comparison function for xfunc_stream_qsort.
712 ** Compare nodes by group rank. If group ranks are equal, ensure that
713 ** join nodes appear in same order as in plan tree.
716 xfunc_stream_compare(void *arg1, void *arg2)
718 Stream stream1 = *(Stream *) arg1;
719 Stream stream2 = *(Stream *) arg2;
723 rank1 = get_grouprank(stream1);
724 rank2 = get_grouprank(stream2);
728 else if (rank1 < rank2)
732 if (is_clause(stream1) && is_clause(stream2))
733 return 0; /* doesn't matter what order if both are
735 else if (!is_clause(stream1) && !is_clause(stream2))
737 if (xfunc_num_relids(stream1) < xfunc_num_relids(stream2))
742 else if (is_clause(stream1) && !is_clause(stream2))
744 if (xfunc_num_relids(stream1) == xfunc_num_relids(stream2))
745 /* stream1 is a restriction over stream2 */
750 else if (!is_clause(stream1) && is_clause(stream2))
752 /* stream2 is a restriction over stream1: never push down */
758 /* ------------------ DEBUGGING ROUTINES ---------------------------- */
761 ** Make sure all pointers in stream make sense. Make sure no joins are
765 xfunc_check_stream(Stream node)
771 /* set numrelids higher than max */
772 if (!is_clause(node))
773 numrelids = xfunc_num_relids(node) + 1;
774 else if (xfunc_get_downjoin(node))
775 numrelids = xfunc_num_relids((Stream) xfunc_get_downjoin(node)) + 1;
779 for (temp = node; get_downstream(temp); temp = (Stream) get_downstream(temp))
781 if ((Stream) get_upstream((Stream) get_downstream(temp)) != temp)
783 elog(ERROR, "bad pointers in stream");
786 if (!is_clause(temp))
788 if ((tmp = xfunc_num_relids(temp)) >= numrelids)
790 elog(ERROR, "Joins got reordered!");
802 ** check if node is in stream
805 xfunc_in_stream(Stream node, Stream stream)
809 for (temp = stream; temp; temp = (Stream) get_downstream(temp))