diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index bd4e575dba..5cfc15c339 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.142 2005/11/29 01:25:49 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.143 2006/02/05 02:59:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -904,9 +904,15 @@ explain_outNode(StringInfo str, appendStringInfo(str, " "); appendStringInfo(str, " -> "); + /* + * Ordinarily we don't pass down our own outer_plan value to our + * child nodes, but in an Append we must, since we might be + * looking at an appendrel indexscan with outer references + * from the member scans. + */ explain_outNode(str, subnode, appendstate->appendplans[j], - NULL, + outer_plan, indent + 3, es); j++; } diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index fc5c445db0..0b5e81096d 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeAppend.c,v 1.65 2005/10/15 02:49:17 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeAppend.c,v 1.66 2006/02/05 02:59:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -345,10 +345,12 @@ ExecReScanAppend(AppendState *node, ExprContext *exprCtxt) UpdateChangedParamSet(subnode, node->ps.chgParam); /* - * if chgParam of subnode is not null then plan will be re-scanned by - * first ExecProcNode. + * If chgParam of subnode is not null then plan will be re-scanned by + * first ExecProcNode. However, if caller is passing us an exprCtxt + * then forcibly rescan all the subnodes now, so that we can pass + * the exprCtxt down to the subnodes (needed for appendrel indexscan). */ - if (subnode->chgParam == NULL) + if (subnode->chgParam == NULL || exprCtxt != NULL) { /* make sure estate is correct for this subnode (needed??) */ node->as_whichplan = i; diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index c258accd8e..f946c69b9b 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -49,7 +49,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.152 2005/12/28 01:29:59 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.153 2006/02/05 02:59:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -991,6 +991,38 @@ cost_group(Path *path, PlannerInfo *root, path->total_cost = total_cost; } +/* + * If a nestloop's inner path is an indexscan, be sure to use its estimated + * output row count, which may be lower than the restriction-clause-only row + * count of its parent. (We don't include this case in the PATH_ROWS macro + * because it applies *only* to a nestloop's inner relation.) We have to + * be prepared to recurse through Append nodes in case of an appendrel. + */ +static double +nestloop_inner_path_rows(Path *path) +{ + double result; + + if (IsA(path, IndexPath)) + result = ((IndexPath *) path)->rows; + else if (IsA(path, BitmapHeapPath)) + result = ((BitmapHeapPath *) path)->rows; + else if (IsA(path, AppendPath)) + { + ListCell *l; + + result = 0; + foreach(l, ((AppendPath *) path)->subpaths) + { + result += nestloop_inner_path_rows((Path *) lfirst(l)); + } + } + else + result = PATH_ROWS(path); + + return result; +} + /* * cost_nestloop * Determines and returns the cost of joining two relations using the @@ -1008,21 +1040,10 @@ cost_nestloop(NestPath *path, PlannerInfo *root) Cost cpu_per_tuple; QualCost restrict_qual_cost; double outer_path_rows = PATH_ROWS(outer_path); - double inner_path_rows = PATH_ROWS(inner_path); + double inner_path_rows = nestloop_inner_path_rows(inner_path); double ntuples; Selectivity joininfactor; - /* - * If inner path is an indexscan, be sure to use its estimated output row - * count, which may be lower than the restriction-clause-only row count of - * its parent. (We don't include this case in the PATH_ROWS macro because - * it applies *only* to a nestloop's inner relation.) - */ - if (IsA(inner_path, IndexPath)) - inner_path_rows = ((IndexPath *) inner_path)->rows; - else if (IsA(inner_path, BitmapHeapPath)) - inner_path_rows = ((BitmapHeapPath *) inner_path)->rows; - if (!enable_nestloop) startup_cost += disable_cost; diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index dc93aa6787..e762ee77f7 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.200 2006/01/29 17:40:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.201 2006/02/05 02:59:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -129,7 +129,7 @@ static Const *string_to_const(const char *str, Oid datatype); * * 'rel' is the relation for which we want to generate index paths * - * Note: check_partial_indexes() must have been run previously. + * Note: check_partial_indexes() must have been run previously for this rel. */ void create_index_paths(PlannerInfo *root, RelOptInfo *rel) @@ -1290,6 +1290,9 @@ matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, Relids outer_relids) * negligible startup cost. (True today, but someday we might have to think * harder.) Therefore, there is only one dimension of comparison and so it's * sufficient to return a single "best" path. + * + * Note: create_index_paths() must have been run previously for this rel, + * else the result will always be NULL. */ Path * best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel, diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 450bc26bed..62724e6244 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.101 2006/02/04 23:03:20 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.102 2006/02/05 02:59:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,9 +35,8 @@ static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel, static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, List *restrictlist, JoinType jointype); -static void join_before_append(PlannerInfo *root, RelOptInfo *joinrel, - RelOptInfo *outerrel, RelOptInfo *innerrel, - JoinType jointype); +static Path *best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel, + Relids outer_relids, JoinType jointype); static List *select_mergejoin_clauses(RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, @@ -118,13 +117,6 @@ add_paths_to_joinrel(PlannerInfo *root, if (enable_hashjoin) hash_inner_and_outer(root, joinrel, outerrel, innerrel, restrictlist, jointype); - - /* - * 5. If the inner relation is an append relation, consider joining - * the outer rel to each append member and then appending the results. - */ - if (innerrel->cheapest_total_path->pathtype == T_Append) - join_before_append(root, joinrel, outerrel, innerrel, jointype); } /* @@ -405,8 +397,17 @@ match_unsorted_outer(PlannerInfo *root, * Get the best innerjoin indexpath (if any) for this outer rel. It's * the same for all outer paths. */ - bestinnerjoin = best_inner_indexscan(root, innerrel, - outerrel->relids, jointype); + if (innerrel->reloptkind != RELOPT_JOINREL) + { + if (IsA(inner_cheapest_total, AppendPath)) + bestinnerjoin = best_appendrel_indexscan(root, innerrel, + outerrel->relids, + jointype); + else if (innerrel->rtekind == RTE_RELATION) + bestinnerjoin = best_inner_indexscan(root, innerrel, + outerrel->relids, + jointype); + } } foreach(l, outerrel->pathlist) @@ -788,75 +789,27 @@ hash_inner_and_outer(PlannerInfo *root, } /* - * join_before_append - * Creates possible join paths for processing a single join relation - * 'joinrel' when the inner input is an append relation. - * - * The idea here is to swap the order of the APPEND and JOIN operators. - * This is only really helpful if it allows us to reduce the cost of - * scanning the members of the append relation, and so we only consider - * plans involving nestloops with inner indexscans. Also, since the APPEND - * will certainly yield an unsorted result, there's no point in considering - * any but the cheapest-total outer path. - * - * XXX this is a bit of a kluge, because the resulting plan has to evaluate - * the outer relation multiple times. Would be better to allow - * best_inner_indexscan to generate an AppendPath and not have this routine - * at all. But we can't do that without some executor changes (need a way - * to pass outer keys down through Append). FIXME later. - * - * 'joinrel' is the join relation - * 'outerrel' is the outer join relation - * 'innerrel' is the inner join relation - * 'jointype' is the type of join to do + * best_appendrel_indexscan + * Finds the best available set of inner indexscans for a nestloop join + * with the given append relation on the inside and the given outer_relids + * outside. Returns an AppendPath comprising the best inner scans, or + * NULL if there are no possible inner indexscans. */ -static void -join_before_append(PlannerInfo *root, - RelOptInfo *joinrel, - RelOptInfo *outerrel, - RelOptInfo *innerrel, - JoinType jointype) +static Path * +best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel, + Relids outer_relids, JoinType jointype) { - Path *outer_cheapest_total = outerrel->cheapest_total_path; - int parentRTindex = innerrel->relid; + int parentRTindex = rel->relid; List *append_paths = NIL; + bool found_indexscan = false; ListCell *l; - /* - * Swapping JOIN with APPEND only works for inner joins, not outer joins. - * However, we can also handle a unique-ified outer path. - */ - switch (jointype) - { - case JOIN_INNER: - break; - case JOIN_UNIQUE_OUTER: - outer_cheapest_total = (Path *) - create_unique_path(root, outerrel, outer_cheapest_total); - break; - case JOIN_LEFT: - case JOIN_RIGHT: - case JOIN_FULL: - case JOIN_IN: - case JOIN_UNIQUE_INNER: - return; /* can't join this way */ - default: - elog(ERROR, "unrecognized join type: %d", - (int) jointype); - break; - } - - /* - * Generate suitable access paths for each member relation. - */ foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RelOptInfo *childrel; Path *bestinnerjoin; - RelOptInfo *this_joinrel; - List *this_restrictlist; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -876,42 +829,30 @@ join_before_append(PlannerInfo *root, continue; /* OK, we can ignore it */ /* - * Get the best innerjoin indexpath (if any) for this outer rel. + * Get the best innerjoin indexpath (if any) for this child rel. */ bestinnerjoin = best_inner_indexscan(root, childrel, - outerrel->relids, JOIN_INNER); + outer_relids, jointype); + /* * If no luck on an indexpath for this rel, we'll still consider - * an Append substituting the cheapest-total inner path. This - * is only likely to win if there's at least one member rel for - * which an indexscan path does exist. + * an Append substituting the cheapest-total inner path. However + * we must find at least one indexpath, else there's not going to + * be any improvement over the base path for the appendrel. */ - if (!bestinnerjoin) + if (bestinnerjoin) + found_indexscan = true; + else bestinnerjoin = childrel->cheapest_total_path; - /* - * We need a joinrel that describes this join accurately. Although - * the joinrel won't ever be used by the join path search algorithm - * in joinrels.c, it provides necessary context for the Path, - * such as properly-translated target and quals lists. - */ - this_joinrel = translate_join_rel(root, joinrel, appinfo, - outerrel, childrel, jointype, - &this_restrictlist); - - /* Build Path for join and add to result list */ - append_paths = lappend(append_paths, - create_nestloop_path(root, - this_joinrel, - JOIN_INNER, - outer_cheapest_total, - bestinnerjoin, - this_restrictlist, - NIL)); + append_paths = lappend(append_paths, bestinnerjoin); } - /* Form the completed Append path and add it to the join relation. */ - add_path(joinrel, (Path *) create_append_path(joinrel, append_paths)); + if (!found_indexscan) + return NULL; + + /* Form and return the completed Append path. */ + return (Path *) create_append_path(rel, append_paths); } /* diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 5a716ead47..56be7ea149 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.119 2005/11/26 22:14:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.120 2006/02/05 02:59:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -769,8 +769,9 @@ set_join_references(Join *join, List *rtable) * Handle join references appearing in an inner indexscan's quals * * To handle bitmap-scan plan trees, we have to be able to recurse down - * to the bottom BitmapIndexScan nodes, so this is split out as a separate - * function. + * to the bottom BitmapIndexScan nodes; likewise, appendrel indexscans + * require recursing through Append nodes. This is split out as a separate + * function so that it can recurse. */ static void set_inner_join_references(Plan *inner_plan, @@ -910,6 +911,22 @@ set_inner_join_references(Plan *inner_plan, outer_itlist); } } + else if (IsA(inner_plan, Append)) + { + /* + * The inner side is an append plan. Recurse to see if it contains + * indexscans that need to be fixed. + */ + Append *appendplan = (Append *) inner_plan; + ListCell *l; + + foreach(l, appendplan->appendplans) + { + set_inner_join_references((Plan *) lfirst(l), + rtable, + outer_itlist); + } + } else if (IsA(inner_plan, TidScan)) { TidScan *innerscan = (TidScan *) inner_plan; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index ca258d5381..67fdffd5cc 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.76 2006/02/03 21:08:49 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.77 2006/02/05 02:59:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,7 +18,6 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/plancat.h" -#include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parsetree.h" @@ -571,144 +570,3 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, } } } - -/* - * translate_join_rel - * Returns relation entry corresponding to the union of two given rels, - * creating a new relation entry if none already exists. This is used - * when one of the inputs is an append child relation. In addition to - * data about the input rels themselves, the corresponding joinrel for - * the append parent relation must be provided, plus the AppendRelInfo - * showing the parent-to-child translation. - * - * The reason for having this code, instead of just applying build_join_rel, - * is that we must have corresponding tlist orderings for all joinrels that - * are involved in an Append plan. So we generate the tlist for joinrels - * involving append child relations by translating the parent joinrel's tlist, - * rather than examining the input relations directly. (Another reason for - * doing it this way is that the base relation attr_needed info in relations - * being joined to the appendrel doesn't refer to the append child rel, but - * the append parent, and so couldn't be used directly anyway.) Otherwise - * this is exactly like build_join_rel. - */ -RelOptInfo * -translate_join_rel(PlannerInfo *root, - RelOptInfo *oldjoinrel, - AppendRelInfo *appinfo, - RelOptInfo *outer_rel, - RelOptInfo *inner_rel, - JoinType jointype, - List **restrictlist_ptr) -{ - RelOptInfo *joinrel; - Relids joinrelids; - List *restrictlist; - - /* - * Construct the Relids set for the translated joinrel, and see if - * we've already built it. - */ - joinrelids = bms_copy(oldjoinrel->relids); - joinrelids = bms_del_member(joinrelids, appinfo->parent_relid); - joinrelids = bms_add_member(joinrelids, appinfo->child_relid); - joinrel = find_join_rel(root, joinrelids); - if (joinrel) - { - /* - * Yes, so we only need to figure the restrictlist for this particular - * pair of component relations. - */ - bms_free(joinrelids); - if (restrictlist_ptr) - *restrictlist_ptr = build_joinrel_restrictlist(root, - joinrel, - outer_rel, - inner_rel, - jointype); - return joinrel; - } - - /* - * Nope, so make one. - */ - joinrel = makeNode(RelOptInfo); - joinrel->reloptkind = RELOPT_JOINREL; - joinrel->relids = joinrelids; - joinrel->rows = 0; - joinrel->width = 0; - joinrel->reltargetlist = NIL; - joinrel->pathlist = NIL; - joinrel->cheapest_startup_path = NULL; - joinrel->cheapest_total_path = NULL; - joinrel->cheapest_unique_path = NULL; - joinrel->relid = 0; /* indicates not a baserel */ - joinrel->rtekind = RTE_JOIN; - joinrel->min_attr = 0; - joinrel->max_attr = 0; - joinrel->attr_needed = NULL; - joinrel->attr_widths = NULL; - joinrel->indexlist = NIL; - joinrel->pages = 0; - joinrel->tuples = 0; - joinrel->subplan = NULL; - joinrel->baserestrictinfo = NIL; - joinrel->baserestrictcost.startup = 0; - joinrel->baserestrictcost.per_tuple = 0; - joinrel->joininfo = NIL; - joinrel->index_outer_relids = NULL; - joinrel->index_inner_paths = NIL; - - /* - * Make the tlist by translating oldjoinrel's tlist, to ensure they - * are in compatible orders. Since we don't call build_joinrel_tlist, - * we need another way to set the rel width; for the moment, just - * assume it is the same as oldjoinrel. (The correct value may well be - * less, but it's not clear it's worth the trouble to get it right.) - */ - joinrel->reltargetlist = (List *) - adjust_appendrel_attrs((Node *) oldjoinrel->reltargetlist, - appinfo); - joinrel->width = oldjoinrel->width; - - /* - * Construct restrict and join clause lists for the new joinrel. (The - * caller might or might not need the restrictlist, but I need it anyway - * for set_joinrel_size_estimates().) - */ - restrictlist = build_joinrel_restrictlist(root, - joinrel, - outer_rel, - inner_rel, - jointype); - if (restrictlist_ptr) - *restrictlist_ptr = restrictlist; - build_joinrel_joinlist(joinrel, outer_rel, inner_rel); - - /* - * Set estimates of the joinrel's size. - */ - set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel, - jointype, restrictlist); - - /* - * Add the joinrel to the query's joinrel list, and store it into the - * auxiliary hashtable if there is one. NB: GEQO requires us to append - * the new joinrel to the end of the list! - */ - root->join_rel_list = lappend(root->join_rel_list, joinrel); - - if (root->join_rel_hash) - { - JoinHashEntry *hentry; - bool found; - - hentry = (JoinHashEntry *) hash_search(root->join_rel_hash, - &(joinrel->relids), - HASH_ENTER, - &found); - Assert(!found); - hentry->join_rel = joinrel; - } - - return joinrel; -} diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 3ba612c195..fc5387505b 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.65 2006/02/03 21:08:49 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.66 2006/02/05 02:59:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -95,12 +95,5 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root, RelOptInfo *inner_rel, JoinType jointype, List **restrictlist_ptr); -extern RelOptInfo *translate_join_rel(PlannerInfo *root, - RelOptInfo *oldjoinrel, - AppendRelInfo *appinfo, - RelOptInfo *outer_rel, - RelOptInfo *inner_rel, - JoinType jointype, - List **restrictlist_ptr); #endif /* PATHNODE_H */