summaryrefslogtreecommitdiff
path: root/src/backend/optimizer/util/pathnode.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/util/pathnode.c')
-rw-r--r--src/backend/optimizer/util/pathnode.c897
1 files changed, 650 insertions, 247 deletions
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 0ca161a31d..d29b454f72 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -29,6 +29,15 @@
#include "utils/selfuncs.h"
+typedef enum
+{
+ COSTS_EQUAL, /* path costs are fuzzily equal */
+ COSTS_BETTER1, /* first path is cheaper than second */
+ COSTS_BETTER2, /* second path is cheaper than first */
+ COSTS_DIFFERENT /* neither path dominates the other on cost */
+} PathCostComparison;
+
+static void add_parameterized_path(RelOptInfo *parent_rel, Path *new_path);
static List *translate_sub_tlist(List *tlist, int relid);
static bool query_is_distinct_for(Query *query, List *colnos, List *opids);
static Oid distinct_col_search(int colno, List *colnos, List *opids);
@@ -81,58 +90,6 @@ compare_path_costs(Path *path1, Path *path2, CostSelector criterion)
}
/*
- * compare_fuzzy_path_costs
- * Return -1, 0, or +1 according as path1 is cheaper, the same cost,
- * or more expensive than path2 for the specified criterion.
- *
- * This differs from compare_path_costs in that we consider the costs the
- * same if they agree to within a "fuzz factor". This is used by add_path
- * to avoid keeping both of a pair of paths that really have insignificantly
- * different cost.
- */
-static int
-compare_fuzzy_path_costs(Path *path1, Path *path2, CostSelector criterion)
-{
- /*
- * We use a fuzz factor of 1% of the smaller cost.
- *
- * XXX does this percentage need to be user-configurable?
- */
- if (criterion == STARTUP_COST)
- {
- if (path1->startup_cost > path2->startup_cost * 1.01)
- return +1;
- if (path2->startup_cost > path1->startup_cost * 1.01)
- return -1;
-
- /*
- * If paths have the same startup cost (not at all unlikely), order
- * them by total cost.
- */
- if (path1->total_cost > path2->total_cost * 1.01)
- return +1;
- if (path2->total_cost > path1->total_cost * 1.01)
- return -1;
- }
- else
- {
- if (path1->total_cost > path2->total_cost * 1.01)
- return +1;
- if (path2->total_cost > path1->total_cost * 1.01)
- return -1;
-
- /*
- * If paths have the same total cost, order them by startup cost.
- */
- if (path1->startup_cost > path2->startup_cost * 1.01)
- return +1;
- if (path2->startup_cost > path1->startup_cost * 1.01)
- return -1;
- }
- return 0;
-}
-
-/*
* compare_path_fractional_costs
* Return -1, 0, or +1 according as path1 is cheaper, the same cost,
* or more expensive than path2 for fetching the specified fraction
@@ -162,38 +119,119 @@ compare_fractional_path_costs(Path *path1, Path *path2,
}
/*
+ * compare_path_costs_fuzzily
+ * Compare the costs of two paths to see if either can be said to
+ * dominate the other.
+ *
+ * We use fuzzy comparisons so that add_path() can avoid keeping both of
+ * a pair of paths that really have insignificantly different cost.
+ * The fuzz factor is 1% of the smaller cost. (XXX does this percentage
+ * need to be user-configurable?)
+ *
+ * The two paths are said to have "equal" costs if both startup and total
+ * costs are fuzzily the same. Path1 is said to be better than path2 if
+ * it has fuzzily better startup cost and fuzzily no worse total cost,
+ * or if it has fuzzily better total cost and fuzzily no worse startup cost.
+ * Path2 is better than path1 if the reverse holds. Finally, if one path
+ * is fuzzily better than the other on startup cost and fuzzily worse on
+ * total cost, we just say that their costs are "different", since neither
+ * dominates the other across the whole performance spectrum.
+ */
+static PathCostComparison
+compare_path_costs_fuzzily(Path *path1, Path *path2)
+{
+ /*
+ * Check total cost first since it's more likely to be different; many
+ * paths have zero startup cost.
+ */
+ if (path1->total_cost > path2->total_cost * 1.01)
+ {
+ /* path1 fuzzily worse on total cost */
+ if (path2->startup_cost > path1->startup_cost * 1.01)
+ {
+ /* ... but path2 fuzzily worse on startup, so DIFFERENT */
+ return COSTS_DIFFERENT;
+ }
+ /* else path2 dominates */
+ return COSTS_BETTER2;
+ }
+ if (path2->total_cost > path1->total_cost * 1.01)
+ {
+ /* path2 fuzzily worse on total cost */
+ if (path1->startup_cost > path2->startup_cost * 1.01)
+ {
+ /* ... but path1 fuzzily worse on startup, so DIFFERENT */
+ return COSTS_DIFFERENT;
+ }
+ /* else path1 dominates */
+ return COSTS_BETTER1;
+ }
+ /* fuzzily the same on total cost */
+ if (path1->startup_cost > path2->startup_cost * 1.01)
+ {
+ /* ... but path1 fuzzily worse on startup, so path2 wins */
+ return COSTS_BETTER2;
+ }
+ if (path2->startup_cost > path1->startup_cost * 1.01)
+ {
+ /* ... but path2 fuzzily worse on startup, so path1 wins */
+ return COSTS_BETTER1;
+ }
+ /* fuzzily the same on both costs */
+ return COSTS_EQUAL;
+}
+
+/*
* set_cheapest
* Find the minimum-cost paths from among a relation's paths,
* and save them in the rel's cheapest-path fields.
*
+ * Only unparameterized paths are considered candidates for cheapest_startup
+ * and cheapest_total. The cheapest_parameterized_paths list collects paths
+ * that are cheapest-total for their parameterization (i.e., there is no
+ * cheaper path with the same or weaker parameterization). This list always
+ * includes the unparameterized cheapest-total path, too.
+ *
* This is normally called only after we've finished constructing the path
* list for the rel node.
- *
- * If we find two paths of identical costs, try to keep the better-sorted one.
- * The paths might have unrelated sort orderings, in which case we can only
- * guess which might be better to keep, but if one is superior then we
- * definitely should keep it.
*/
void
set_cheapest(RelOptInfo *parent_rel)
{
- List *pathlist = parent_rel->pathlist;
- ListCell *p;
Path *cheapest_startup_path;
Path *cheapest_total_path;
+ bool have_parameterized_paths;
+ ListCell *p;
Assert(IsA(parent_rel, RelOptInfo));
- if (pathlist == NIL)
- elog(ERROR, "could not devise a query plan for the given query");
-
- cheapest_startup_path = cheapest_total_path = (Path *) linitial(pathlist);
+ cheapest_startup_path = cheapest_total_path = NULL;
+ have_parameterized_paths = false;
- for_each_cell(p, lnext(list_head(pathlist)))
+ foreach(p, parent_rel->pathlist)
{
Path *path = (Path *) lfirst(p);
int cmp;
+ /* We only consider unparameterized paths in this step */
+ if (path->required_outer)
+ {
+ have_parameterized_paths = true;
+ continue;
+ }
+
+ if (cheapest_total_path == NULL)
+ {
+ cheapest_startup_path = cheapest_total_path = path;
+ continue;
+ }
+
+ /*
+ * If we find two paths of identical costs, try to keep the
+ * better-sorted one. The paths might have unrelated sort orderings,
+ * in which case we can only guess which might be better to keep, but
+ * if one is superior then we definitely should keep that one.
+ */
cmp = compare_path_costs(cheapest_startup_path, path, STARTUP_COST);
if (cmp > 0 ||
(cmp == 0 &&
@@ -209,9 +247,27 @@ set_cheapest(RelOptInfo *parent_rel)
cheapest_total_path = path;
}
+ if (cheapest_total_path == NULL)
+ elog(ERROR, "could not devise a query plan for the given query");
+
parent_rel->cheapest_startup_path = cheapest_startup_path;
parent_rel->cheapest_total_path = cheapest_total_path;
parent_rel->cheapest_unique_path = NULL; /* computed only if needed */
+
+ /* Seed the parameterized-paths list with the cheapest total */
+ parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path);
+
+ /* And, if there are any parameterized paths, add them in one at a time */
+ if (have_parameterized_paths)
+ {
+ foreach(p, parent_rel->pathlist)
+ {
+ Path *path = (Path *) lfirst(p);
+
+ if (path->required_outer)
+ add_parameterized_path(parent_rel, path);
+ }
+ }
}
/*
@@ -219,17 +275,25 @@ set_cheapest(RelOptInfo *parent_rel)
* Consider a potential implementation path for the specified parent rel,
* and add it to the rel's pathlist if it is worthy of consideration.
* A path is worthy if it has either a better sort order (better pathkeys)
- * or cheaper cost (on either dimension) than any of the existing old paths.
+ * or cheaper cost (on either dimension) than any of the existing old paths
+ * that have the same or superset required_outer rels.
*
* We also remove from the rel's pathlist any old paths that are dominated
- * by new_path --- that is, new_path is both cheaper and at least as well
- * ordered.
+ * by new_path --- that is, new_path is cheaper, at least as well ordered,
+ * and requires no outer rels not required by old path.
+ *
+ * There is one policy decision embedded in this function, along with its
+ * sibling add_path_precheck: we treat all parameterized paths as having
+ * NIL pathkeys, so that they compete only on cost. This is to reduce
+ * the number of parameterized paths that are kept. See discussion in
+ * src/backend/optimizer/README.
*
- * The pathlist is kept sorted by TOTAL_COST metric, with cheaper paths
- * at the front. No code depends on that for correctness; it's simply
- * a speed hack within this routine. Doing it that way makes it more
- * likely that we will reject an inferior path after a few comparisons,
- * rather than many comparisons.
+ * The pathlist is kept sorted by total_cost, with cheaper paths
+ * at the front. Within this routine, that's simply a speed hack:
+ * doing it that way makes it more likely that we will reject an inferior
+ * path after a few comparisons, rather than many comparisons.
+ * However, add_path_precheck relies on this ordering to exit early
+ * when possible.
*
* NOTE: discarded Path objects are immediately pfree'd to reduce planner
* memory consumption. We dare not try to free the substructure of a Path,
@@ -250,6 +314,7 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
+ List *new_path_pathkeys;
ListCell *p1;
ListCell *p1_prev;
ListCell *p1_next;
@@ -260,6 +325,9 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
*/
CHECK_FOR_INTERRUPTS();
+ /* Pretend parameterized paths have no pathkeys, per comment above */
+ new_path_pathkeys = new_path->required_outer ? NIL : new_path->pathkeys;
+
/*
* Loop to check proposed new path against old paths. Note it is possible
* for more than one old path to be tossed out because new_path dominates
@@ -273,62 +341,99 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
{
Path *old_path = (Path *) lfirst(p1);
bool remove_old = false; /* unless new proves superior */
- int costcmp;
+ PathCostComparison costcmp;
+ PathKeysComparison keyscmp;
+ BMS_Comparison outercmp;
p1_next = lnext(p1);
- /*
- * As of Postgres 8.0, we use fuzzy cost comparison to avoid wasting
- * cycles keeping paths that are really not significantly different in
- * cost.
- */
- costcmp = compare_fuzzy_path_costs(new_path, old_path, TOTAL_COST);
+ costcmp = compare_path_costs_fuzzily(new_path, old_path);
/*
* If the two paths compare differently for startup and total cost,
- * then we want to keep both, and we can skip the (much slower)
- * comparison of pathkeys. If they compare the same, proceed with the
- * pathkeys comparison. Note: this test relies on the fact that
- * compare_fuzzy_path_costs will only return 0 if both costs are
- * effectively equal (and, therefore, there's no need to call it twice
- * in that case).
+ * then we want to keep both, and we can skip comparing pathkeys and
+ * required_outer rels. If they compare the same, proceed with the
+ * other comparisons. (We make the tests in this order because the
+ * cost comparison is most likely to turn out "different", and the
+ * pathkeys comparison next most likely.)
*/
- if (costcmp == 0 ||
- costcmp == compare_fuzzy_path_costs(new_path, old_path,
- STARTUP_COST))
+ if (costcmp != COSTS_DIFFERENT)
{
- switch (compare_pathkeys(new_path->pathkeys, old_path->pathkeys))
+ /* Similarly check to see if either dominates on pathkeys */
+ List *old_path_pathkeys;
+
+ old_path_pathkeys = old_path->required_outer ? NIL : old_path->pathkeys;
+ keyscmp = compare_pathkeys(new_path_pathkeys,
+ old_path_pathkeys);
+ if (keyscmp != PATHKEYS_DIFFERENT)
{
- case PATHKEYS_EQUAL:
- if (costcmp < 0)
- remove_old = true; /* new dominates old */
- else if (costcmp > 0)
- accept_new = false; /* old dominates new */
- else
- {
+ switch (costcmp)
+ {
+ case COSTS_EQUAL:
+ outercmp = bms_subset_compare(new_path->required_outer,
+ old_path->required_outer);
+ if (keyscmp == PATHKEYS_BETTER1)
+ {
+ if (outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET1)
+ remove_old = true; /* new dominates old */
+ }
+ else if (keyscmp == PATHKEYS_BETTER2)
+ {
+ if (outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET2)
+ accept_new = false; /* old dominates new */
+ }
+ else /* keyscmp == PATHKEYS_EQUAL */
+ {
+ if (outercmp == BMS_EQUAL)
+ {
+ /*
+ * Same pathkeys and outer rels, and fuzzily
+ * the same cost, so keep just one --- but
+ * we'll do an exact cost comparison to decide
+ * which.
+ */
+ if (compare_path_costs(new_path, old_path,
+ TOTAL_COST) < 0)
+ remove_old = true; /* new dominates old */
+ else
+ accept_new = false; /* old equals or dominates new */
+ }
+ else if (outercmp == BMS_SUBSET1)
+ remove_old = true; /* new dominates old */
+ else if (outercmp == BMS_SUBSET2)
+ accept_new = false; /* old dominates new */
+ /* else different parameterizations, keep both */
+ }
+ break;
+ case COSTS_BETTER1:
+ if (keyscmp != PATHKEYS_BETTER2)
+ {
+ outercmp = bms_subset_compare(new_path->required_outer,
+ old_path->required_outer);
+ if (outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET1)
+ remove_old = true; /* new dominates old */
+ }
+ break;
+ case COSTS_BETTER2:
+ if (keyscmp != PATHKEYS_BETTER1)
+ {
+ outercmp = bms_subset_compare(new_path->required_outer,
+ old_path->required_outer);
+ if (outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET2)
+ accept_new = false; /* old dominates new */
+ }
+ break;
+ case COSTS_DIFFERENT:
/*
- * Same pathkeys, and fuzzily the same cost, so keep
- * just one --- but we'll do an exact cost comparison
- * to decide which.
+ * can't get here, but keep this case to keep compiler
+ * quiet
*/
- if (compare_path_costs(new_path, old_path,
- TOTAL_COST) < 0)
- remove_old = true; /* new dominates old */
- else
- accept_new = false; /* old equals or dominates new */
- }
- break;
- case PATHKEYS_BETTER1:
- if (costcmp <= 0)
- remove_old = true; /* new dominates old */
- break;
- case PATHKEYS_BETTER2:
- if (costcmp >= 0)
- accept_new = false; /* old dominates new */
- break;
- case PATHKEYS_DIFFERENT:
- /* keep both paths, since they have different ordering */
- break;
+ break;
+ }
}
}
@@ -350,7 +455,7 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
else
{
/* new belongs after this old path if it has cost >= old's */
- if (costcmp >= 0)
+ if (new_path->total_cost >= old_path->total_cost)
insert_after = p1;
/* p1_prev advances */
p1_prev = p1;
@@ -381,6 +486,185 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
}
}
+/*
+ * add_path_precheck
+ * Check whether a proposed new path could possibly get accepted.
+ * We assume we know the path's pathkeys and parameterization accurately,
+ * and have lower bounds for its costs.
+ *
+ * At the time this is called, we haven't actually built a Path structure,
+ * so the required information has to be passed piecemeal.
+ */
+bool
+add_path_precheck(RelOptInfo *parent_rel,
+ Cost startup_cost, Cost total_cost,
+ List *pathkeys, Relids required_outer)
+{
+ List *new_path_pathkeys;
+ ListCell *p1;
+
+ /* Pretend parameterized paths have no pathkeys, per comment above */
+ new_path_pathkeys = required_outer ? NIL : pathkeys;
+
+ foreach(p1, parent_rel->pathlist)
+ {
+ Path *old_path = (Path *) lfirst(p1);
+ PathKeysComparison keyscmp;
+ BMS_Comparison outercmp;
+
+ /*
+ * We are looking for an old_path that dominates the new path across
+ * all four metrics. If we find one, we can reject the new path.
+ *
+ * For speed, we make exact rather than fuzzy cost comparisons.
+ * If an old path dominates the new path exactly on both costs, it
+ * will surely do so fuzzily.
+ */
+ if (total_cost >= old_path->total_cost)
+ {
+ if (startup_cost >= old_path->startup_cost)
+ {
+ List *old_path_pathkeys;
+
+ old_path_pathkeys = old_path->required_outer ? NIL : old_path->pathkeys;
+ keyscmp = compare_pathkeys(new_path_pathkeys,
+ old_path_pathkeys);
+ if (keyscmp == PATHKEYS_EQUAL ||
+ keyscmp == PATHKEYS_BETTER2)
+ {
+ outercmp = bms_subset_compare(required_outer,
+ old_path->required_outer);
+ if (outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET2)
+ return false;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Since the pathlist is sorted by total_cost, we can stop
+ * looking once we reach a path with a total_cost larger
+ * than the new path's.
+ */
+ break;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * add_parameterized_path
+ * Consider a parameterized implementation path for the specified rel,
+ * and add it to the rel's cheapest_parameterized_paths list if it
+ * belongs there, removing any old entries that it dominates.
+ *
+ * This is essentially a cut-down form of add_path(): we do not care about
+ * startup cost or sort ordering, only total cost and parameterization.
+ * Also, we should not recycle rejected paths, since they will still be
+ * present in the rel's pathlist.
+ *
+ * 'parent_rel' is the relation entry to which the path corresponds.
+ * 'new_path' is a parameterized path for parent_rel.
+ *
+ * Returns nothing, but modifies parent_rel->cheapest_parameterized_paths.
+ */
+static void
+add_parameterized_path(RelOptInfo *parent_rel, Path *new_path)
+{
+ bool accept_new = true; /* unless we find a superior old path */
+ ListCell *insert_after = NULL; /* where to insert new item */
+ ListCell *p1;
+ ListCell *p1_prev;
+ ListCell *p1_next;
+
+ /*
+ * Loop to check proposed new path against old paths. Note it is possible
+ * for more than one old path to be tossed out because new_path dominates
+ * it.
+ *
+ * We can't use foreach here because the loop body may delete the current
+ * list cell.
+ */
+ p1_prev = NULL;
+ for (p1 = list_head(parent_rel->cheapest_parameterized_paths);
+ p1 != NULL; p1 = p1_next)
+ {
+ Path *old_path = (Path *) lfirst(p1);
+ bool remove_old = false; /* unless new proves superior */
+ int costcmp;
+ BMS_Comparison outercmp;
+
+ p1_next = lnext(p1);
+
+ costcmp = compare_path_costs(new_path, old_path, TOTAL_COST);
+ outercmp = bms_subset_compare(new_path->required_outer,
+ old_path->required_outer);
+ if (outercmp != BMS_DIFFERENT)
+ {
+ if (costcmp < 0)
+ {
+ if (outercmp != BMS_SUBSET2)
+ remove_old = true; /* new dominates old */
+ }
+ else if (costcmp > 0)
+ {
+ if (outercmp != BMS_SUBSET1)
+ accept_new = false; /* old dominates new */
+ }
+ else if (outercmp == BMS_SUBSET1)
+ remove_old = true; /* new dominates old */
+ else if (outercmp == BMS_SUBSET2)
+ accept_new = false; /* old dominates new */
+ else
+ {
+ /* Same cost and outer rels, arbitrarily keep the old */
+ accept_new = false; /* old equals or dominates new */
+ }
+ }
+
+ /*
+ * Remove current element from cheapest_parameterized_paths if
+ * dominated by new.
+ */
+ if (remove_old)
+ {
+ parent_rel->cheapest_parameterized_paths =
+ list_delete_cell(parent_rel->cheapest_parameterized_paths,
+ p1, p1_prev);
+ /* p1_prev does not advance */
+ }
+ else
+ {
+ /* new belongs after this old path if it has cost >= old's */
+ if (costcmp >= 0)
+ insert_after = p1;
+ /* p1_prev advances */
+ p1_prev = p1;
+ }
+
+ /*
+ * If we found an old path that dominates new_path, we can quit
+ * scanning the list; we will not add new_path, and we assume
+ * new_path cannot dominate any other elements of the list.
+ */
+ if (!accept_new)
+ break;
+ }
+
+ if (accept_new)
+ {
+ /* Accept the new path: insert it at proper place in list */
+ if (insert_after)
+ lappend_cell(parent_rel->cheapest_parameterized_paths,
+ insert_after, new_path);
+ else
+ parent_rel->cheapest_parameterized_paths =
+ lcons(new_path, parent_rel->cheapest_parameterized_paths);
+ }
+}
+
/*****************************************************************************
* PATH NODE CREATION ROUTINES
@@ -399,6 +683,8 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_SeqScan;
pathnode->parent = rel;
pathnode->pathkeys = NIL; /* seqscan has unordered result */
+ pathnode->required_outer = NULL;
+ pathnode->param_clauses = NIL;
cost_seqscan(pathnode, root, rel);
@@ -423,8 +709,9 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel)
* for an ordered index, or NoMovementScanDirection for
* an unordered index.
* 'indexonly' is true if an index-only scan is wanted.
- * 'outer_rel' is the outer relation if this is a join inner indexscan path.
- * (pathkeys and indexscandir are ignored if so.) NULL if not.
+ * 'required_outer' is the set of outer relids referenced in indexclauses.
+ * 'loop_count' is the number of repetitions of the indexscan to factor into
+ * estimates of caching behavior.
*
* Returns the new path node.
*/
@@ -438,29 +725,35 @@ create_index_path(PlannerInfo *root,
List *pathkeys,
ScanDirection indexscandir,
bool indexonly,
- RelOptInfo *outer_rel)
+ Relids required_outer,
+ double loop_count)
{
IndexPath *pathnode = makeNode(IndexPath);
RelOptInfo *rel = index->rel;
List *indexquals,
*indexqualcols;
- /*
- * For a join inner scan, there's no point in marking the path with any
- * pathkeys, since it will only ever be used as the inner path of a
- * nestloop, and so its ordering does not matter. For the same reason we
- * don't really care what order it's scanned in. (We could expect the
- * caller to supply the correct values, but it's easier to force it here.)
- */
- if (outer_rel != NULL)
- {
- pathkeys = NIL;
- indexscandir = NoMovementScanDirection;
- }
-
pathnode->path.pathtype = indexonly ? T_IndexOnlyScan : T_IndexScan;
pathnode->path.parent = rel;
pathnode->path.pathkeys = pathkeys;
+ pathnode->path.required_outer = required_outer;
+ if (required_outer)
+ {
+ /* Identify index clauses that are join clauses */
+ List *jclauses = NIL;
+ ListCell *lc;
+
+ foreach(lc, indexclauses)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (!bms_is_subset(rinfo->clause_relids, rel->relids))
+ jclauses = lappend(jclauses, rinfo);
+ }
+ pathnode->path.param_clauses = jclauses;
+ }
+ else
+ pathnode->path.param_clauses = NIL;
/* Convert clauses to indexquals the executor can handle */
expand_indexqual_conditions(index, indexclauses, indexclausecols,
@@ -473,50 +766,9 @@ create_index_path(PlannerInfo *root,
pathnode->indexqualcols = indexqualcols;
pathnode->indexorderbys = indexorderbys;
pathnode->indexorderbycols = indexorderbycols;
-
- pathnode->isjoininner = (outer_rel != NULL);
pathnode->indexscandir = indexscandir;
- if (outer_rel != NULL)
- {
- /*
- * We must compute the estimated number of output rows for the
- * indexscan. This is less than rel->rows because of the additional
- * selectivity of the join clauses. Since indexclauses may contain
- * both restriction and join clauses, we have to do a set union to get
- * the full set of clauses that must be considered to compute the
- * correct selectivity. (Without the union operation, we might have
- * some restriction clauses appearing twice, which'd mislead
- * clauselist_selectivity into double-counting their selectivity.
- * However, since RestrictInfo nodes aren't copied when linking them
- * into different lists, it should be sufficient to use pointer
- * comparison to remove duplicates.)
- *
- * Note that we force the clauses to be treated as non-join clauses
- * during selectivity estimation.
- */
- List *allclauses;
-
- allclauses = list_union_ptr(rel->baserestrictinfo, indexclauses);
- pathnode->rows = rel->tuples *
- clauselist_selectivity(root,
- allclauses,
- rel->relid, /* do not use 0! */
- JOIN_INNER,
- NULL);
- /* Like costsize.c, force estimate to be at least one row */
- pathnode->rows = clamp_row_est(pathnode->rows);
- }
- else
- {
- /*
- * The number of rows is the same as the parent rel's estimate, since
- * this isn't a join inner indexscan.
- */
- pathnode->rows = rel->rows;
- }
-
- cost_index(pathnode, root, outer_rel);
+ cost_index(pathnode, root, loop_count);
return pathnode;
}
@@ -526,55 +778,29 @@ create_index_path(PlannerInfo *root,
* Creates a path node for a bitmap scan.
*
* 'bitmapqual' is a tree of IndexPath, BitmapAndPath, and BitmapOrPath nodes.
+ * 'loop_count' is the number of repetitions of the indexscan to factor into
+ * estimates of caching behavior.
*
- * If this is a join inner indexscan path, 'outer_rel' is the outer relation,
- * and all the component IndexPaths should have been costed accordingly.
+ * loop_count should match the value used when creating the component
+ * IndexPaths.
*/
BitmapHeapPath *
create_bitmap_heap_path(PlannerInfo *root,
RelOptInfo *rel,
Path *bitmapqual,
- RelOptInfo *outer_rel)
+ double loop_count)
{
BitmapHeapPath *pathnode = makeNode(BitmapHeapPath);
pathnode->path.pathtype = T_BitmapHeapScan;
pathnode->path.parent = rel;
pathnode->path.pathkeys = NIL; /* always unordered */
+ pathnode->path.required_outer = bitmapqual->required_outer;
+ pathnode->path.param_clauses = bitmapqual->param_clauses;
pathnode->bitmapqual = bitmapqual;
- pathnode->isjoininner = (outer_rel != NULL);
-
- if (pathnode->isjoininner)
- {
- /*
- * We must compute the estimated number of output rows for the
- * indexscan. This is less than rel->rows because of the additional
- * selectivity of the join clauses. We make use of the selectivity
- * estimated for the bitmap to do this; this isn't really quite right
- * since there may be restriction conditions not included in the
- * bitmap ...
- */
- Cost indexTotalCost;
- Selectivity indexSelectivity;
-
- cost_bitmap_tree_node(bitmapqual, &indexTotalCost, &indexSelectivity);
- pathnode->rows = rel->tuples * indexSelectivity;
- if (pathnode->rows > rel->rows)
- pathnode->rows = rel->rows;
- /* Like costsize.c, force estimate to be at least one row */
- pathnode->rows = clamp_row_est(pathnode->rows);
- }
- else
- {
- /*
- * The number of rows is the same as the parent rel's estimate, since
- * this isn't a join inner indexscan.
- */
- pathnode->rows = rel->rows;
- }
- cost_bitmap_heap_scan(&pathnode->path, root, rel, bitmapqual, outer_rel);
+ cost_bitmap_heap_scan(&pathnode->path, root, rel, bitmapqual, loop_count);
return pathnode;
}
@@ -589,13 +815,29 @@ create_bitmap_and_path(PlannerInfo *root,
List *bitmapquals)
{
BitmapAndPath *pathnode = makeNode(BitmapAndPath);
+ ListCell *lc;
pathnode->path.pathtype = T_BitmapAnd;
pathnode->path.parent = rel;
pathnode->path.pathkeys = NIL; /* always unordered */
+ pathnode->path.required_outer = NULL;
+ pathnode->path.param_clauses = NIL;
pathnode->bitmapquals = bitmapquals;
+ /* required_outer and param_clauses are the union of the inputs' values */
+ foreach(lc, bitmapquals)
+ {
+ Path *bpath = (Path *) lfirst(lc);
+
+ pathnode->path.required_outer =
+ bms_add_members(pathnode->path.required_outer,
+ bpath->required_outer);
+ pathnode->path.param_clauses =
+ list_concat(pathnode->path.param_clauses,
+ list_copy(bpath->param_clauses));
+ }
+
/* this sets bitmapselectivity as well as the regular cost fields: */
cost_bitmap_and_node(pathnode, root);
@@ -612,13 +854,29 @@ create_bitmap_or_path(PlannerInfo *root,
List *bitmapquals)
{
BitmapOrPath *pathnode = makeNode(BitmapOrPath);
+ ListCell *lc;
pathnode->path.pathtype = T_BitmapOr;
pathnode->path.parent = rel;
pathnode->path.pathkeys = NIL; /* always unordered */
+ pathnode->path.required_outer = NULL;
+ pathnode->path.param_clauses = NIL;
pathnode->bitmapquals = bitmapquals;
+ /* required_outer and param_clauses are the union of the inputs' values */
+ foreach(lc, bitmapquals)
+ {
+ Path *bpath = (Path *) lfirst(lc);
+
+ pathnode->path.required_outer =
+ bms_add_members(pathnode->path.required_outer,
+ bpath->required_outer);
+ pathnode->path.param_clauses =
+ list_concat(pathnode->path.param_clauses,
+ list_copy(bpath->param_clauses));
+ }
+
/* this sets bitmapselectivity as well as the regular cost fields: */
cost_bitmap_or_node(pathnode, root);
@@ -637,6 +895,8 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals)
pathnode->path.pathtype = T_TidScan;
pathnode->path.parent = rel;
pathnode->path.pathkeys = NIL;
+ pathnode->path.required_outer = NULL;
+ pathnode->path.param_clauses = NIL;
pathnode->tidquals = tidquals;
@@ -662,24 +922,46 @@ create_append_path(RelOptInfo *rel, List *subpaths)
pathnode->path.parent = rel;
pathnode->path.pathkeys = NIL; /* result is always considered
* unsorted */
+ pathnode->path.required_outer = NULL; /* updated below */
+ pathnode->path.param_clauses = NIL; /* XXX see below */
pathnode->subpaths = subpaths;
/*
- * Compute cost as sum of subplan costs. We charge nothing extra for the
- * Append itself, which perhaps is too optimistic, but since it doesn't do
- * any selection or projection, it is a pretty cheap node.
+ * We don't bother with inventing a cost_append(), but just do it here.
+ *
+ * Compute rows and costs as sums of subplan rows and costs. We charge
+ * nothing extra for the Append itself, which perhaps is too optimistic,
+ * but since it doesn't do any selection or projection, it is a pretty
+ * cheap node. If you change this, see also make_append().
*
- * If you change this, see also make_append().
+ * We also compute the correct required_outer set, namely the union of
+ * the input paths' requirements.
+ *
+ * XXX We should also compute a proper param_clauses list, but that
+ * will require identifying which joinclauses are enforced by all the
+ * subplans, as well as locating the original parent RestrictInfo from
+ * which they were generated. For the moment we punt and leave the list
+ * as NIL. This will result in uselessly rechecking such joinclauses
+ * at the parameter-supplying nestloop join, which is slightly annoying,
+ * as well as overestimating the sizes of any intermediate joins, which
+ * is significantly more annoying.
*/
+ pathnode->path.rows = 0;
pathnode->path.startup_cost = 0;
pathnode->path.total_cost = 0;
foreach(l, subpaths)
{
Path *subpath = (Path *) lfirst(l);
+ pathnode->path.rows += subpath->rows;
+
if (l == list_head(subpaths)) /* first node? */
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost += subpath->total_cost;
+
+ pathnode->path.required_outer =
+ bms_add_members(pathnode->path.required_outer,
+ subpath->required_outer);
}
return pathnode;
@@ -704,6 +986,8 @@ create_merge_append_path(PlannerInfo *root,
pathnode->path.pathtype = T_MergeAppend;
pathnode->path.parent = rel;
pathnode->path.pathkeys = pathkeys;
+ pathnode->path.required_outer = NULL; /* updated below */
+ pathnode->path.param_clauses = NIL; /* XXX see below */
pathnode->subpaths = subpaths;
/*
@@ -735,13 +1019,22 @@ create_merge_append_path(PlannerInfo *root,
}
}
- /* Add up all the costs of the input paths */
+ /*
+ * Add up the sizes and costs of the input paths, and also compute the
+ * real required_outer value.
+ *
+ * XXX as in create_append_path(), we should compute param_clauses but
+ * it will require more work.
+ */
+ pathnode->path.rows = 0;
input_startup_cost = 0;
input_total_cost = 0;
foreach(l, subpaths)
{
Path *subpath = (Path *) lfirst(l);
+ pathnode->path.rows += subpath->rows;
+
if (pathkeys_contained_in(pathkeys, subpath->pathkeys))
{
/* Subpath is adequately ordered, we won't need to sort it */
@@ -765,6 +1058,10 @@ create_merge_append_path(PlannerInfo *root,
input_startup_cost += sort_path.startup_cost;
input_total_cost += sort_path.total_cost;
}
+
+ pathnode->path.required_outer =
+ bms_add_members(pathnode->path.required_outer,
+ subpath->required_outer);
}
/* Now we can compute total costs of the MergeAppend */
@@ -789,9 +1086,12 @@ create_result_path(List *quals)
pathnode->path.pathtype = T_Result;
pathnode->path.parent = NULL;
pathnode->path.pathkeys = NIL;
+ pathnode->path.required_outer = NULL;
+ pathnode->path.param_clauses = NIL;
pathnode->quals = quals;
- /* Ideally should define cost_result(), but I'm too lazy */
+ /* Hardly worth defining a cost_result() function ... just do it */
+ pathnode->path.rows = 1;
pathnode->path.startup_cost = 0;
pathnode->path.total_cost = cpu_tuple_cost;
@@ -817,15 +1117,16 @@ create_material_path(RelOptInfo *rel, Path *subpath)
pathnode->path.pathtype = T_Material;
pathnode->path.parent = rel;
-
pathnode->path.pathkeys = subpath->pathkeys;
+ pathnode->path.required_outer = subpath->required_outer;
+ pathnode->path.param_clauses = subpath->param_clauses;
pathnode->subpath = subpath;
cost_material(&pathnode->path,
subpath->startup_cost,
subpath->total_cost,
- rel->rows,
+ subpath->rows,
rel->width);
return pathnode;
@@ -874,7 +1175,6 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
/*
* We must ensure path struct and subsidiary data are allocated in main
* planning context; otherwise GEQO memory management causes trouble.
- * (Compare best_inner_indexscan().)
*/
oldcontext = MemoryContextSwitchTo(root->planner_cxt);
@@ -1032,6 +1332,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
* to represent it. (This might get overridden below.)
*/
pathnode->path.pathkeys = NIL;
+ pathnode->path.required_outer = subpath->required_outer;
+ pathnode->path.param_clauses = subpath->param_clauses;
pathnode->subpath = subpath;
pathnode->in_operators = in_operators;
@@ -1048,7 +1350,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
uniq_exprs, in_operators))
{
pathnode->umethod = UNIQUE_PATH_NOOP;
- pathnode->rows = rel->rows;
+ pathnode->path.rows = rel->rows;
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost;
pathnode->path.pathkeys = subpath->pathkeys;
@@ -1081,7 +1383,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
sub_tlist_colnos, in_operators))
{
pathnode->umethod = UNIQUE_PATH_NOOP;
- pathnode->rows = rel->rows;
+ pathnode->path.rows = rel->rows;
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost;
pathnode->path.pathkeys = subpath->pathkeys;
@@ -1095,7 +1397,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
}
/* Estimate number of output rows */
- pathnode->rows = estimate_num_groups(root, uniq_exprs, rel->rows);
+ pathnode->path.rows = estimate_num_groups(root, uniq_exprs, rel->rows);
numCols = list_length(uniq_exprs);
if (all_btree)
@@ -1128,12 +1430,12 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
*/
int hashentrysize = rel->width + 64;
- if (hashentrysize * pathnode->rows > work_mem * 1024L)
+ if (hashentrysize * pathnode->path.rows > work_mem * 1024L)
all_hash = false; /* don't try to hash */
else
cost_agg(&agg_path, root,
AGG_HASHED, NULL,
- numCols, pathnode->rows,
+ numCols, pathnode->path.rows,
subpath->startup_cost,
subpath->total_cost,
rel->rows);
@@ -1367,6 +1669,8 @@ create_subqueryscan_path(RelOptInfo *rel, List *pathkeys)
pathnode->pathtype = T_SubqueryScan;
pathnode->parent = rel;
pathnode->pathkeys = pathkeys;
+ pathnode->required_outer = NULL;
+ pathnode->param_clauses = NIL;
cost_subqueryscan(pathnode, rel);
@@ -1386,6 +1690,8 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_FunctionScan;
pathnode->parent = rel;
pathnode->pathkeys = NIL; /* for now, assume unordered result */
+ pathnode->required_outer = NULL;
+ pathnode->param_clauses = NIL;
cost_functionscan(pathnode, root, rel);
@@ -1405,6 +1711,8 @@ create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_ValuesScan;
pathnode->parent = rel;
pathnode->pathkeys = NIL; /* result is always unordered */
+ pathnode->required_outer = NULL;
+ pathnode->param_clauses = NIL;
cost_valuesscan(pathnode, root, rel);
@@ -1424,6 +1732,8 @@ create_ctescan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_CteScan;
pathnode->parent = rel;
pathnode->pathkeys = NIL; /* XXX for now, result is always unordered */
+ pathnode->required_outer = NULL;
+ pathnode->param_clauses = NIL;
cost_ctescan(pathnode, root, rel);
@@ -1443,6 +1753,8 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_WorkTableScan;
pathnode->parent = rel;
pathnode->pathkeys = NIL; /* result is always unordered */
+ pathnode->required_outer = NULL;
+ pathnode->param_clauses = NIL;
/* Cost is the same as for a regular CTE scan */
cost_ctescan(pathnode, root, rel);
@@ -1466,6 +1778,8 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->path.pathtype = T_ForeignScan;
pathnode->path.parent = rel;
pathnode->path.pathkeys = NIL; /* result is always unordered */
+ pathnode->path.required_outer = NULL;
+ pathnode->path.param_clauses = NIL;
/* Get FDW's callback info */
rte = planner_rt_fetch(rel->relid, root);
@@ -1479,6 +1793,7 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->fdwplan = fdwplan;
/* use costs estimated by FDW */
+ pathnode->path.rows = rel->rows;
pathnode->path.startup_cost = fdwplan->startup_cost;
pathnode->path.total_cost = fdwplan->total_cost;
@@ -1486,17 +1801,75 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
}
/*
+ * calc_nestloop_required_outer
+ * Compute the required_outer set for a nestloop join path
+ *
+ * Note: result must not share storage with either input
+ */
+Relids
+calc_nestloop_required_outer(Path *outer_path, Path *inner_path)
+{
+ Relids required_outer;
+
+ /* inner_path can require rels from outer path, but not vice versa */
+ Assert(!bms_overlap(outer_path->required_outer,
+ inner_path->parent->relids));
+ /* easy case if inner path is not parameterized */
+ if (!inner_path->required_outer)
+ return bms_copy(outer_path->required_outer);
+ /* else, form the union ... */
+ required_outer = bms_union(outer_path->required_outer,
+ inner_path->required_outer);
+ /* ... and remove any mention of now-satisfied outer rels */
+ required_outer = bms_del_members(required_outer,
+ outer_path->parent->relids);
+ /* maintain invariant that required_outer is exactly NULL if empty */
+ if (bms_is_empty(required_outer))
+ {
+ bms_free(required_outer);
+ required_outer = NULL;
+ }
+ return required_outer;
+}
+
+/*
+ * calc_non_nestloop_required_outer
+ * Compute the required_outer set for a merge or hash join path
+ *
+ * Note: result must not share storage with either input
+ */
+Relids
+calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path)
+{
+ Relids required_outer;
+
+ /* neither path can require rels from the other */
+ Assert(!bms_overlap(outer_path->required_outer,
+ inner_path->parent->relids));
+ Assert(!bms_overlap(inner_path->required_outer,
+ outer_path->parent->relids));
+ /* form the union ... */
+ required_outer = bms_union(outer_path->required_outer,
+ inner_path->required_outer);
+ /* we do not need an explicit test for empty; bms_union gets it right */
+ return required_outer;
+}
+
+/*
* create_nestloop_path
* Creates a pathnode corresponding to a nestloop join between two
* relations.
*
* 'joinrel' is the join relation.
* 'jointype' is the type of join required
+ * 'workspace' is the result from initial_cost_nestloop
* 'sjinfo' is extra info about the join for selectivity estimation
+ * 'semifactors' contains valid data if jointype is SEMI or ANTI
* 'outer_path' is the outer path
* 'inner_path' is the inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
* 'pathkeys' are the path keys of the new join path
+ * 'required_outer' is the set of required outer rels
*
* Returns the resulting path node.
*/
@@ -1504,23 +1877,46 @@ NestPath *
create_nestloop_path(PlannerInfo *root,
RelOptInfo *joinrel,
JoinType jointype,
+ JoinCostWorkspace *workspace,
SpecialJoinInfo *sjinfo,
+ SemiAntiJoinFactors *semifactors,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
- List *pathkeys)
+ List *pathkeys,
+ Relids required_outer)
{
NestPath *pathnode = makeNode(NestPath);
pathnode->path.pathtype = T_NestLoop;
pathnode->path.parent = joinrel;
+ pathnode->path.pathkeys = pathkeys;
+ pathnode->path.required_outer = required_outer;
+ if (pathnode->path.required_outer)
+ {
+ /* Identify parameter clauses not yet applied here */
+ List *jclauses;
+ ListCell *lc;
+
+ /* LHS clauses could not be satisfied here */
+ jclauses = list_copy(outer_path->param_clauses);
+ foreach(lc, inner_path->param_clauses)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (!bms_is_subset(rinfo->clause_relids, joinrel->relids))
+ jclauses = lappend(jclauses, rinfo);
+ }
+ pathnode->path.param_clauses = jclauses;
+ }
+ else
+ pathnode->path.param_clauses = NIL;
pathnode->jointype = jointype;
pathnode->outerjoinpath = outer_path;
pathnode->innerjoinpath = inner_path;
pathnode->joinrestrictinfo = restrict_clauses;
- pathnode->path.pathkeys = pathkeys;
- cost_nestloop(pathnode, root, sjinfo);
+ final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors);
return pathnode;
}
@@ -1532,11 +1928,13 @@ create_nestloop_path(PlannerInfo *root,
*
* 'joinrel' is the join relation
* 'jointype' is the type of join required
+ * 'workspace' is the result from initial_cost_mergejoin
* 'sjinfo' is extra info about the join for selectivity estimation
* 'outer_path' is the outer path
* 'inner_path' is the inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
* 'pathkeys' are the path keys of the new join path
+ * 'required_outer' is the set of required outer rels
* 'mergeclauses' are the RestrictInfo nodes to use as merge clauses
* (this should be a subset of the restrict_clauses list)
* 'outersortkeys' are the sort varkeys for the outer relation
@@ -1546,41 +1944,36 @@ MergePath *
create_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
JoinType jointype,
+ JoinCostWorkspace *workspace,
SpecialJoinInfo *sjinfo,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
+ Relids required_outer,
List *mergeclauses,
List *outersortkeys,
List *innersortkeys)
{
MergePath *pathnode = makeNode(MergePath);
- /*
- * If the given paths are already well enough ordered, we can skip doing
- * an explicit sort.
- */
- if (outersortkeys &&
- pathkeys_contained_in(outersortkeys, outer_path->pathkeys))
- outersortkeys = NIL;
- if (innersortkeys &&
- pathkeys_contained_in(innersortkeys, inner_path->pathkeys))
- innersortkeys = NIL;
-
pathnode->jpath.path.pathtype = T_MergeJoin;
pathnode->jpath.path.parent = joinrel;
+ pathnode->jpath.path.pathkeys = pathkeys;
+ pathnode->jpath.path.required_outer = required_outer;
+ pathnode->jpath.path.param_clauses =
+ list_concat(list_copy(outer_path->param_clauses),
+ inner_path->param_clauses);
pathnode->jpath.jointype = jointype;
pathnode->jpath.outerjoinpath = outer_path;
pathnode->jpath.innerjoinpath = inner_path;
pathnode->jpath.joinrestrictinfo = restrict_clauses;
- pathnode->jpath.path.pathkeys = pathkeys;
pathnode->path_mergeclauses = mergeclauses;
pathnode->outersortkeys = outersortkeys;
pathnode->innersortkeys = innersortkeys;
- /* pathnode->materialize_inner will be set by cost_mergejoin */
+ /* pathnode->materialize_inner will be set by final_cost_mergejoin */
- cost_mergejoin(pathnode, root, sjinfo);
+ final_cost_mergejoin(root, pathnode, workspace, sjinfo);
return pathnode;
}
@@ -1591,10 +1984,13 @@ create_mergejoin_path(PlannerInfo *root,
*
* 'joinrel' is the join relation
* 'jointype' is the type of join required
+ * 'workspace' is the result from initial_cost_hashjoin
* 'sjinfo' is extra info about the join for selectivity estimation
+ * 'semifactors' contains valid data if jointype is SEMI or ANTI
* 'outer_path' is the cheapest outer path
* 'inner_path' is the cheapest inner path
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
+ * 'required_outer' is the set of required outer rels
* 'hashclauses' are the RestrictInfo nodes to use as hash clauses
* (this should be a subset of the restrict_clauses list)
*/
@@ -1602,20 +1998,19 @@ HashPath *
create_hashjoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
JoinType jointype,
+ JoinCostWorkspace *workspace,
SpecialJoinInfo *sjinfo,
+ SemiAntiJoinFactors *semifactors,
Path *outer_path,
Path *inner_path,
List *restrict_clauses,
+ Relids required_outer,
List *hashclauses)
{
HashPath *pathnode = makeNode(HashPath);
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
- pathnode->jpath.jointype = jointype;
- pathnode->jpath.outerjoinpath = outer_path;
- pathnode->jpath.innerjoinpath = inner_path;
- pathnode->jpath.joinrestrictinfo = restrict_clauses;
/*
* A hashjoin never has pathkeys, since its output ordering is
@@ -1629,10 +2024,18 @@ create_hashjoin_path(PlannerInfo *root,
* outer rel than it does now.)
*/
pathnode->jpath.path.pathkeys = NIL;
+ pathnode->jpath.path.required_outer = required_outer;
+ pathnode->jpath.path.param_clauses =
+ list_concat(list_copy(outer_path->param_clauses),
+ inner_path->param_clauses);
+ pathnode->jpath.jointype = jointype;
+ pathnode->jpath.outerjoinpath = outer_path;
+ pathnode->jpath.innerjoinpath = inner_path;
+ pathnode->jpath.joinrestrictinfo = restrict_clauses;
pathnode->path_hashclauses = hashclauses;
- /* cost_hashjoin will fill in pathnode->num_batches */
+ /* final_cost_hashjoin will fill in pathnode->num_batches */
- cost_hashjoin(pathnode, root, sjinfo);
+ final_cost_hashjoin(root, pathnode, workspace, sjinfo, semifactors);
return pathnode;
}