total_cost,
NIL, /* no pathkeys */
NULL, /* no outer rel either */
- NIL,
NIL)); /* no fdw_private data */
/*
COPY_SCALAR_FIELD(pseudoconstant);
COPY_BITMAPSET_FIELD(clause_relids);
COPY_BITMAPSET_FIELD(required_relids);
+ COPY_BITMAPSET_FIELD(outer_relids);
COPY_BITMAPSET_FIELD(nullable_relids);
COPY_BITMAPSET_FIELD(left_relids);
COPY_BITMAPSET_FIELD(right_relids);
COMPARE_SCALAR_FIELD(is_pushed_down);
COMPARE_SCALAR_FIELD(outerjoin_delayed);
COMPARE_BITMAPSET_FIELD(required_relids);
+ COMPARE_BITMAPSET_FIELD(outer_relids);
COMPARE_BITMAPSET_FIELD(nullable_relids);
/*
*
* Note we do NOT print the parent, else we'd be in infinite recursion.
* We can print the parent's relids for identification purposes, though.
+ * We also do not print the whole of param_info, since it's printed by
+ * _outRelOptInfo; it's sufficient and less cluttering to print just the
+ * required outer relids.
*/
static void
_outPathInfo(StringInfo str, const Path *node)
WRITE_ENUM_FIELD(pathtype, NodeTag);
appendStringInfo(str, " :parent_relids ");
_outBitmapset(str, node->parent->relids);
+ appendStringInfo(str, " :required_outer ");
+ if (node->param_info)
+ _outBitmapset(str, node->param_info->ppi_req_outer);
+ else
+ _outBitmapset(str, NULL);
WRITE_FLOAT_FIELD(rows, "%.0f");
WRITE_FLOAT_FIELD(startup_cost, "%.2f");
WRITE_FLOAT_FIELD(total_cost, "%.2f");
WRITE_NODE_FIELD(pathkeys);
- WRITE_BITMAPSET_FIELD(required_outer);
- WRITE_NODE_FIELD(param_clauses);
}
/*
WRITE_INT_FIELD(width);
WRITE_NODE_FIELD(reltargetlist);
WRITE_NODE_FIELD(pathlist);
+ WRITE_NODE_FIELD(ppilist);
WRITE_NODE_FIELD(cheapest_startup_path);
WRITE_NODE_FIELD(cheapest_total_path);
WRITE_NODE_FIELD(cheapest_unique_path);
WRITE_BOOL_FIELD(pk_nulls_first);
}
+static void
+_outParamPathInfo(StringInfo str, const ParamPathInfo *node)
+{
+ WRITE_NODE_TYPE("PARAMPATHINFO");
+
+ WRITE_BITMAPSET_FIELD(ppi_req_outer);
+ WRITE_FLOAT_FIELD(ppi_rows, "%.0f");
+ WRITE_NODE_FIELD(ppi_clauses);
+}
+
static void
_outRestrictInfo(StringInfo str, const RestrictInfo *node)
{
WRITE_BOOL_FIELD(pseudoconstant);
WRITE_BITMAPSET_FIELD(clause_relids);
WRITE_BITMAPSET_FIELD(required_relids);
+ WRITE_BITMAPSET_FIELD(outer_relids);
WRITE_BITMAPSET_FIELD(nullable_relids);
WRITE_BITMAPSET_FIELD(left_relids);
WRITE_BITMAPSET_FIELD(right_relids);
case T_PathKey:
_outPathKey(str, obj);
break;
+ case T_ParamPathInfo:
+ _outParamPathInfo(str, obj);
+ break;
case T_RestrictInfo:
_outRestrictInfo(str, obj);
break;
plan shown above, and it will compete in the usual way with paths built
from non-parameterized scans.
+While all ordinary paths for a particular relation generate the same set
+of rows (since they must all apply the same set of restriction clauses),
+parameterized paths typically generate fewer rows than less-parameterized
+paths, since they have additional clauses to work with. This means we
+must consider the number of rows generated as an additional figure of
+merit. A path that costs more than another, but generates fewer rows,
+must be kept since the smaller number of rows might save work at some
+intermediate join level. (It would not save anything if joined
+immediately to the source of the parameters.)
+
+To keep cost estimation rules relatively simple, we make an implementation
+restriction that all paths for a given relation of the same parameterization
+(i.e., the same set of outer relations supplying parameters) must have the
+same rowcount estimate. This is justified by insisting that each such path
+apply *all* join clauses that are available with the named outer relations.
+Different paths might, for instance, choose different join clauses to use
+as index clauses; but they must then apply any other join clauses available
+from the same outer relations as filter conditions, so that the set of rows
+returned is held constant. This restriction doesn't degrade the quality of
+the finished plan: it amounts to saying that we should always push down
+movable join clauses to the lowest possible evaluation level, which is a
+good thing anyway. The restriction is useful in particular to support
+pre-filtering of join paths in add_path_precheck. Without this rule we
+could never reject a parameterized path in advance of computing its rowcount
+estimate, which would greatly reduce the value of the pre-filter mechanism.
+
To limit planning time, we have to avoid generating an unreasonably large
number of parameterized paths. We do this by only generating parameterized
relation scan paths for index scans, and then only for indexes for which
do not ignore merge joins entirely, joinpath.c does not fully explore the
space of potential merge joins with parameterized inputs. Also, add_path
treats parameterized paths as having no pathkeys, so that they compete
-only on cost and don't get preference for producing a special sort order.
-This creates additional bias against merge joins, since we might discard
-a path that could have been useful for performing a merge without an
-explicit sort step. Since a parameterized path must ultimately be used
-on the inside of a nestloop, where its sort order is uninteresting, these
-choices do not affect any requirement for the final output order of a
-query --- they only make it harder to use a merge join at a lower level.
-The savings in planning work justifies that.
+only on cost and rowcount; they don't get preference for producing a
+special sort order. This creates additional bias against merge joins,
+since we might discard a path that could have been useful for performing
+a merge without an explicit sort step. Since a parameterized path must
+ultimately be used on the inside of a nestloop, where its sort order is
+uninteresting, these choices do not affect any requirement for the final
+output order of a query --- they only make it harder to use a merge join
+at a lower level. The savings in planning work justifies that.
-- bjm & tgl
Index rti, RangeTblEntry *rte);
static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels,
- List *all_child_pathkeys,
- Relids required_outer);
+ List *all_child_pathkeys);
static List *accumulate_append_subpath(List *subpaths, Path *path);
static void set_dummy_rel_pathlist(RelOptInfo *rel);
static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
/* Consider sequential scan */
- add_path(rel, create_seqscan_path(root, rel));
+ add_path(rel, create_seqscan_path(root, rel, NULL));
/* Consider index scans */
create_index_paths(root, rel);
{
Path *childpath = (Path *) lfirst(lcp);
List *childkeys = childpath->pathkeys;
- Relids childouter = childpath->required_outer;
+ Relids childouter = PATH_REQ_OUTER(childpath);
/* Unsorted paths don't contribute to pathkey list */
if (childkeys != NIL)
* (Note: this is correct even if we have zero or one live subpath due to
* constraint exclusion.)
*/
- add_path(rel, (Path *) create_append_path(rel, subpaths));
+ add_path(rel, (Path *) create_append_path(rel, subpaths, NULL));
/*
* Build unparameterized MergeAppend paths based on the collected list of
* child pathkeys.
*/
- generate_mergeappend_paths(root, rel, live_childrels,
- all_child_pathkeys, NULL);
+ generate_mergeappend_paths(root, rel, live_childrels, all_child_pathkeys);
/*
- * Build Append and MergeAppend paths for each parameterization seen
- * among the child rels. (This may look pretty expensive, but in most
- * cases of practical interest, the child relations will tend to expose
- * the same parameterizations and pathkeys, so that not that many cases
- * actually get considered here.)
+ * Build Append paths for each parameterization seen among the child rels.
+ * (This may look pretty expensive, but in most cases of practical
+ * interest, the child rels will expose mostly the same parameterizations,
+ * so that not that many cases actually get considered here.)
+ *
+ * The Append node itself cannot enforce quals, so all qual checking must
+ * be done in the child paths. This means that to have a parameterized
+ * Append path, we must have the exact same parameterization for each
+ * child path; otherwise some children might be failing to check the
+ * moved-down quals. To make them match up, we can try to increase the
+ * parameterization of lesser-parameterized paths.
*/
foreach(l, all_child_outers)
{
Relids required_outer = (Relids) lfirst(l);
+ bool ok = true;
ListCell *lcr;
/* Select the child paths for an Append with this parameterization */
TOTAL_COST);
Assert(cheapest_total != NULL);
+ /* Children must have exactly the desired parameterization */
+ if (!bms_equal(PATH_REQ_OUTER(cheapest_total), required_outer))
+ {
+ cheapest_total = reparameterize_path(root, cheapest_total,
+ required_outer, 1.0);
+ if (cheapest_total == NULL)
+ {
+ ok = false;
+ break;
+ }
+ }
+
subpaths = accumulate_append_subpath(subpaths, cheapest_total);
}
- add_path(rel, (Path *) create_append_path(rel, subpaths));
- /* And build parameterized MergeAppend paths */
- generate_mergeappend_paths(root, rel, live_childrels,
- all_child_pathkeys, required_outer);
+ if (ok)
+ add_path(rel, (Path *)
+ create_append_path(rel, subpaths, required_outer));
}
/* Select cheapest paths */
* Generate MergeAppend paths for an append relation
*
* Generate a path for each ordering (pathkey list) appearing in
- * all_child_pathkeys. If required_outer isn't NULL, accept paths having
- * those relations as required outer relations.
+ * all_child_pathkeys.
*
* We consider both cheapest-startup and cheapest-total cases, ie, for each
* interesting ordering, collect all the cheapest startup subpaths and all the
* cheapest total paths, and build a MergeAppend path for each case.
+ *
+ * We don't currently generate any parameterized MergeAppend paths. While
+ * it would not take much more code here to do so, it's very unclear that it
+ * is worth the planning cycles to investigate such paths: there's little
+ * use for an ordered path on the inside of a nestloop. In fact, it's likely
+ * that the current coding of add_path would reject such paths out of hand,
+ * because add_path gives no credit for sort ordering of parameterized paths,
+ * and a parameterized MergeAppend is going to be more expensive than the
+ * corresponding parameterized Append path. If we ever try harder to support
+ * parameterized mergejoin plans, it might be worth adding support for
+ * parameterized MergeAppends to feed such joins. (See notes in
+ * optimizer/README for why that might not ever happen, though.)
*/
static void
generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels,
- List *all_child_pathkeys,
- Relids required_outer)
+ List *all_child_pathkeys)
{
ListCell *lcp;
cheapest_startup =
get_cheapest_path_for_pathkeys(childrel->pathlist,
pathkeys,
- required_outer,
+ NULL,
STARTUP_COST);
cheapest_total =
get_cheapest_path_for_pathkeys(childrel->pathlist,
pathkeys,
- required_outer,
+ NULL,
TOTAL_COST);
/*
* If we can't find any paths with the right order just use the
- * cheapest-total path; we'll have to sort it later. We can
- * use the cheapest path for the parameterization, though.
+ * cheapest-total path; we'll have to sort it later.
*/
if (cheapest_startup == NULL || cheapest_total == NULL)
{
- if (required_outer)
- cheapest_startup = cheapest_total =
- get_cheapest_path_for_pathkeys(childrel->pathlist,
- NIL,
- required_outer,
- TOTAL_COST);
- else
- cheapest_startup = cheapest_total =
- childrel->cheapest_total_path;
+ cheapest_startup = cheapest_total =
+ childrel->cheapest_total_path;
Assert(cheapest_total != NULL);
}
add_path(rel, (Path *) create_merge_append_path(root,
rel,
startup_subpaths,
- pathkeys));
+ pathkeys,
+ NULL));
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
- pathkeys));
+ pathkeys,
+ NULL));
}
}
/* Discard any pre-existing paths; no further need for them */
rel->pathlist = NIL;
- add_path(rel, (Path *) create_append_path(rel, NIL));
+ add_path(rel, (Path *) create_append_path(rel, NIL, NULL));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);
/* Generate appropriate path */
- add_path(rel, create_subqueryscan_path(rel, pathkeys));
+ add_path(rel, create_subqueryscan_path(root, rel, pathkeys, NULL));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
+#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
static void cost_rescan(PlannerInfo *root, Path *path,
Cost *rescan_startup_cost, Cost *rescan_total_cost);
static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
-static bool has_indexed_join_quals(NestPath *path, List *joinclauses);
+static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
+ QualCost *qpqual_cost);
+static bool has_indexed_join_quals(NestPath *joinpath);
static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
List *quals);
-static void set_joinpath_size_estimate(PlannerInfo *root, JoinPath *path,
- SpecialJoinInfo *sjinfo,
- List *restrictlist);
static double calc_joinrel_size_estimate(PlannerInfo *root,
double outer_rows,
double inner_rows,
/*
* cost_seqscan
* Determines and returns the cost of scanning a relation sequentially.
+ *
+ * 'baserel' is the relation to be scanned
+ * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
*/
void
cost_seqscan(Path *path, PlannerInfo *root,
- RelOptInfo *baserel)
+ RelOptInfo *baserel, ParamPathInfo *param_info)
{
- double spc_seq_page_cost;
Cost startup_cost = 0;
Cost run_cost = 0;
+ double spc_seq_page_cost;
+ QualCost qpqual_cost;
Cost cpu_per_tuple;
/* Should only be applied to base relations */
Assert(baserel->relid > 0);
Assert(baserel->rtekind == RTE_RELATION);
- /* For now, at least, seqscans are never parameterized */
- path->rows = baserel->rows;
+ /* Mark the path with the correct row estimate */
+ if (param_info)
+ path->rows = param_info->ppi_rows;
+ else
+ path->rows = baserel->rows;
if (!enable_seqscan)
startup_cost += disable_cost;
run_cost += spc_seq_page_cost * baserel->pages;
/* CPU costs */
- startup_cost += baserel->baserestrictcost.startup;
- cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+ get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
+
+ startup_cost += qpqual_cost.startup;
+ cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost += cpu_per_tuple * baserel->tuples;
path->startup_cost = startup_cost;
Assert(baserel->relid > 0);
Assert(baserel->rtekind == RTE_RELATION);
- /* Estimate the number of rows returned by the indexscan */
- if (path->path.required_outer)
+ /* Mark the path with the correct row estimate */
+ if (path->path.param_info)
{
- /*
- * The estimate should be less than baserel->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.
- */
- allclauses = list_union_ptr(baserel->baserestrictinfo,
- path->indexclauses);
- path->path.rows = baserel->tuples *
- clauselist_selectivity(root,
- allclauses,
- baserel->relid, /* do not use 0! */
- JOIN_INNER,
- NULL);
- if (path->path.rows > baserel->rows)
- path->path.rows = baserel->rows;
- path->path.rows = clamp_row_est(path->path.rows);
+ path->path.rows = path->path.param_info->ppi_rows;
+ /* also get the set of clauses that should be enforced by the scan */
+ allclauses = list_concat(list_copy(path->path.param_info->ppi_clauses),
+ baserel->baserestrictinfo);
}
else
{
+ path->path.rows = baserel->rows;
/* allclauses should just be the rel's restriction clauses */
allclauses = baserel->baserestrictinfo;
-
- /*
- * The number of rows is the same as the parent rel's estimate, since
- * this isn't a parameterized path.
- */
- path->path.rows = baserel->rows;
}
if (!enable_indexscan)
*
* What we want here is cpu_tuple_cost plus the evaluation costs of any
* qual clauses that we have to evaluate as qpquals. We approximate that
- * list as allclauses minus any clauses appearing in indexquals (as
- * before, assuming that pointer equality is enough to recognize duplicate
- * RestrictInfos). This method neglects some considerations such as
+ * list as allclauses minus any clauses appearing in indexquals. (We
+ * assume that pointer equality is enough to recognize duplicate
+ * RestrictInfos.) This method neglects some considerations such as
* clauses that needn't be checked because they are implied by a partial
* index's predicate. It does not seem worth the cycles to try to factor
* those things in at this stage, even though createplan.c will take pains
* index-then-heap plan.
*
* 'baserel' is the relation to be scanned
+ * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
* 'bitmapqual' is a tree of IndexPaths, BitmapAndPaths, and BitmapOrPaths
* 'loop_count' is the number of repetitions of the indexscan to factor into
* estimates of caching behavior
*/
void
cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
Path *bitmapqual, double loop_count)
{
Cost startup_cost = 0;
Cost run_cost = 0;
Cost indexTotalCost;
Selectivity indexSelectivity;
+ QualCost qpqual_cost;
Cost cpu_per_tuple;
Cost cost_per_page;
double tuples_fetched;
Assert(baserel->relid > 0);
Assert(baserel->rtekind == RTE_RELATION);
- /* Estimate the number of rows returned by the bitmap scan */
- if (path->required_outer)
- {
- /*
- * The estimate should be less than baserel->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);
- path->rows = baserel->tuples * indexSelectivity;
- if (path->rows > baserel->rows)
- path->rows = baserel->rows;
- path->rows = clamp_row_est(path->rows);
- }
+ /* Mark the path with the correct row estimate */
+ if (param_info)
+ path->rows = param_info->ppi_rows;
else
- {
- /*
- * The number of rows is the same as the parent rel's estimate, since
- * this isn't a parameterized path.
- */
path->rows = baserel->rows;
- }
if (!enable_bitmapscan)
startup_cost += disable_cost;
* Often the indexquals don't need to be rechecked at each tuple ... but
* not always, especially not if there are enough tuples involved that the
* bitmaps become lossy. For the moment, just assume they will be
- * rechecked always.
+ * rechecked always. This means we charge the full freight for all the
+ * scan clauses.
*/
- startup_cost += baserel->baserestrictcost.startup;
- cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+ get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
+
+ startup_cost += qpqual_cost.startup;
+ cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost += cpu_per_tuple * tuples_fetched;
/*
* cost_subqueryscan
* Determines and returns the cost of scanning a subquery RTE.
+ *
+ * 'baserel' is the relation to be scanned
+ * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
*/
void
-cost_subqueryscan(Path *path, RelOptInfo *baserel)
+cost_subqueryscan(Path *path, PlannerInfo *root,
+ RelOptInfo *baserel, ParamPathInfo *param_info)
{
Cost startup_cost;
Cost run_cost;
+ QualCost qpqual_cost;
Cost cpu_per_tuple;
/* Should only be applied to base relations that are subqueries */
Assert(baserel->relid > 0);
Assert(baserel->rtekind == RTE_SUBQUERY);
- /* subqueryscans are never parameterized */
- path->rows = baserel->rows;
+ /* Mark the path with the correct row estimate */
+ if (param_info)
+ path->rows = param_info->ppi_rows;
+ else
+ path->rows = baserel->rows;
/*
* Cost of path is cost of evaluating the subplan, plus cost of evaluating
path->startup_cost = baserel->subplan->startup_cost;
path->total_cost = baserel->subplan->total_cost;
- startup_cost = baserel->baserestrictcost.startup;
- cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+ get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
+
+ startup_cost = qpqual_cost.startup;
+ cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost = cpu_per_tuple * baserel->tuples;
path->startup_cost += startup_cost;
Cost run_cost = workspace->run_cost;
Cost inner_rescan_run_cost = workspace->inner_rescan_run_cost;
Cost cpu_per_tuple;
- List *joinclauses;
QualCost restrict_qual_cost;
double ntuples;
- /* Estimate the number of rows returned by the join */
- if (path->path.required_outer)
- {
- /*
- * The nestloop is (still) parameterized because of upper-level join
- * clauses used by the input paths. So the rowcount estimate should
- * be less than the joinrel's row count because of the additional
- * selectivity of those join clauses. To estimate the size we need
- * to know which of the joinrestrictinfo clauses nominally associated
- * with the join have been applied in the inner input path.
- *
- * We should also assume that such clauses won't be evaluated at the
- * join node at runtime, so exclude them from restrict_qual_cost.
- */
- joinclauses = select_nonredundant_join_clauses(path->joinrestrictinfo,
- path->innerjoinpath->param_clauses);
- set_joinpath_size_estimate(root, path, sjinfo, joinclauses);
- }
+ /* Mark the path with the correct row estimate */
+ if (path->path.param_info)
+ path->path.rows = path->path.param_info->ppi_rows;
else
- {
- joinclauses = path->joinrestrictinfo;
path->path.rows = path->path.parent->rows;
- }
/*
* We could include disable_cost in the preliminary estimate, but that
* return the first tuple of a nonempty scan. Otherwise, the executor
* will have to scan the whole inner rel; not so cheap.
*/
- if (has_indexed_join_quals(path, joinclauses))
+ if (has_indexed_join_quals(path))
{
run_cost += (outer_path_rows - outer_matched_rows) *
inner_rescan_run_cost / inner_path_rows;
}
/* CPU costs */
- cost_qual_eval(&restrict_qual_cost, joinclauses, root);
+ cost_qual_eval(&restrict_qual_cost, path->joinrestrictinfo, root);
startup_cost += restrict_qual_cost.startup;
cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple;
run_cost += cpu_per_tuple * ntuples;
if (inner_path_rows <= 0 || isnan(inner_path_rows))
inner_path_rows = 1;
- /* Estimate the number of rows returned by the join */
- set_joinpath_size_estimate(root, &path->jpath, sjinfo,
- path->jpath.joinrestrictinfo);
+ /* Mark the path with the correct row estimate */
+ if (path->jpath.path.param_info)
+ path->jpath.path.rows = path->jpath.path.param_info->ppi_rows;
+ else
+ path->jpath.path.rows = path->jpath.path.parent->rows;
/*
* We could include disable_cost in the preliminary estimate, but that
Selectivity innerbucketsize;
ListCell *hcl;
- /* Estimate the number of rows returned by the join */
- set_joinpath_size_estimate(root, &path->jpath, sjinfo,
- path->jpath.joinrestrictinfo);
+ /* Mark the path with the correct row estimate */
+ if (path->jpath.path.param_info)
+ path->jpath.path.rows = path->jpath.path.param_info->ppi_rows;
+ else
+ path->jpath.path.rows = path->jpath.path.parent->rows;
/*
* We could include disable_cost in the preliminary estimate, but that
(void *) context);
}
+/*
+ * get_restriction_qual_cost
+ * Compute evaluation costs of a baserel's restriction quals, plus any
+ * movable join quals that have been pushed down to the scan.
+ * Results are returned into *qpqual_cost.
+ *
+ * This is a convenience subroutine that works for seqscans and other cases
+ * where all the given quals will be evaluated the hard way. It's not useful
+ * for cost_index(), for example, where the index machinery takes care of
+ * some of the quals. We assume baserestrictcost was previously set by
+ * set_baserel_size_estimates().
+ */
+static void
+get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
+ QualCost *qpqual_cost)
+{
+ if (param_info)
+ {
+ /* Include costs of pushed-down clauses */
+ cost_qual_eval(qpqual_cost, param_info->ppi_clauses, root);
+
+ qpqual_cost->startup += baserel->baserestrictcost.startup;
+ qpqual_cost->per_tuple += baserel->baserestrictcost.per_tuple;
+ }
+ else
+ *qpqual_cost = baserel->baserestrictcost;
+}
+
/*
* compute_semi_anti_join_factors
* expensive.
*/
static bool
-has_indexed_join_quals(NestPath *path, List *joinclauses)
+has_indexed_join_quals(NestPath *joinpath)
{
- NodeTag pathtype = path->innerjoinpath->pathtype;
+ Relids joinrelids = joinpath->path.parent->relids;
+ Path *innerpath = joinpath->innerjoinpath;
+ List *indexclauses;
+ bool found_one;
+ ListCell *lc;
+
+ /* If join still has quals to evaluate, it's not fast */
+ if (joinpath->joinrestrictinfo != NIL)
+ return false;
+ /* Nor if the inner path isn't parameterized at all */
+ if (innerpath->param_info == NULL)
+ return false;
- if (pathtype == T_IndexScan ||
- pathtype == T_IndexOnlyScan ||
- pathtype == T_BitmapHeapScan)
+ /* Find the indexclauses list for the inner scan */
+ switch (innerpath->pathtype)
{
- if (path->joinrestrictinfo != NIL)
+ case T_IndexScan:
+ case T_IndexOnlyScan:
+ indexclauses = ((IndexPath *) innerpath)->indexclauses;
+ break;
+ case T_BitmapHeapScan:
{
- /* OK if all those clauses were found to be redundant */
- return (joinclauses == NIL);
+ /* Accept only a simple bitmap scan, not AND/OR cases */
+ Path *bmqual = ((BitmapHeapPath *) innerpath)->bitmapqual;
+
+ if (IsA(bmqual, IndexPath))
+ indexclauses = ((IndexPath *) bmqual)->indexclauses;
+ else
+ return false;
+ break;
}
- else
- {
- /* a clauseless join does NOT qualify */
+ default:
+ /*
+ * If it's not a simple indexscan, it probably doesn't run quickly
+ * for zero rows out, even if it's a parameterized path using all
+ * the joinquals.
+ */
return false;
- }
}
- else
+
+ /*
+ * Examine the inner path's param clauses. Any that are from the outer
+ * path must be found in the indexclauses list, either exactly or in an
+ * equivalent form generated by equivclass.c. Also, we must find at
+ * least one such clause, else it's a clauseless join which isn't fast.
+ */
+ found_one = false;
+ foreach(lc, innerpath->param_info->ppi_clauses)
{
- /*
- * If it's not a simple indexscan, it probably doesn't run quickly for
- * zero rows out, even if it's a parameterized path using all the
- * joinquals.
- */
- return false;
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (join_clause_is_movable_into(rinfo,
+ innerpath->parent->relids,
+ joinrelids))
+ {
+ if (!(list_member_ptr(indexclauses, rinfo) ||
+ is_redundant_derived_clause(rinfo, indexclauses)))
+ return false;
+ found_one = true;
+ }
}
+ return found_one;
}
set_rel_width(root, rel);
}
+/*
+ * get_parameterized_baserel_size
+ * Make a size estimate for a parameterized scan of a base relation.
+ *
+ * 'param_clauses' lists the additional join clauses to be used.
+ *
+ * set_baserel_size_estimates must have been applied already.
+ */
+double
+get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
+ List *param_clauses)
+{
+ List *allclauses;
+ double nrows;
+
+ /*
+ * Estimate the number of rows returned by the parameterized scan, knowing
+ * that it will apply all the extra join clauses as well as the rel's own
+ * restriction clauses. Note that we force the clauses to be treated as
+ * non-join clauses during selectivity estimation.
+ */
+ allclauses = list_concat(list_copy(param_clauses),
+ rel->baserestrictinfo);
+ nrows = rel->tuples *
+ clauselist_selectivity(root,
+ allclauses,
+ rel->relid, /* do not use 0! */
+ JOIN_INNER,
+ NULL);
+ nrows = clamp_row_est(nrows);
+ /* For safety, make sure result is not more than the base estimate */
+ if (nrows > rel->rows)
+ nrows = rel->rows;
+ return nrows;
+}
+
/*
* set_joinrel_size_estimates
* Set the size estimates for the given join relation.
* routines don't handle all cases equally well, we might not. But there's
* not much to be done about it. (Would it make sense to repeat the
* calculations for each pair of input rels that's encountered, and somehow
- * average the results? Probably way more trouble than it's worth.)
+ * average the results? Probably way more trouble than it's worth, and
+ * anyway we must keep the rowcount estimate the same for all paths for the
+ * joinrel.)
*
* We set only the rows field here. The width field was already set by
* build_joinrel_tlist, and baserestrictcost is not used for join rels.
}
/*
- * set_joinpath_size_estimate
- * Set the rows estimate for the given join path.
- *
- * If the join is not parameterized by any joinclauses from higher joins, the
- * estimate is the same as previously computed by set_joinrel_size_estimates.
- * Otherwise, we estimate afresh using the identical logic, but with the rows
- * estimates from the input paths (which are typically less than their rels'
- * regular row estimates) and the restriction clauses actually being applied
- * at the join.
+ * get_parameterized_joinrel_size
+ * Make a size estimate for a parameterized scan of a join relation.
+ *
+ * 'rel' is the joinrel under consideration.
+ * 'outer_rows', 'inner_rows' are the sizes of the (probably also
+ * parameterized) join inputs under consideration.
+ * 'sjinfo' is any SpecialJoinInfo relevant to this join.
+ * 'restrict_clauses' lists the join clauses that need to be applied at the
+ * join node (including any movable clauses that were moved down to this join,
+ * and not including any movable clauses that were pushed down into the
+ * child paths).
+ *
+ * set_joinrel_size_estimates must have been applied already.
*/
-static void
-set_joinpath_size_estimate(PlannerInfo *root, JoinPath *path,
- SpecialJoinInfo *sjinfo,
- List *restrictlist)
+double
+get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
+ double outer_rows,
+ double inner_rows,
+ SpecialJoinInfo *sjinfo,
+ List *restrict_clauses)
{
- if (path->path.required_outer)
- {
- path->path.rows = calc_joinrel_size_estimate(root,
- path->outerjoinpath->rows,
- path->innerjoinpath->rows,
- sjinfo,
- restrictlist);
- /* For safety, make sure result is not more than the base estimate */
- if (path->path.rows > path->path.parent->rows)
- path->path.rows = path->path.parent->rows;
- }
- else
- path->path.rows = path->path.parent->rows;
+ double nrows;
+
+ /*
+ * Estimate the number of rows returned by the parameterized join as the
+ * sizes of the input paths times the selectivity of the clauses that have
+ * ended up at this join node.
+ *
+ * As with set_joinrel_size_estimates, the rowcount estimate could depend
+ * on the pair of input paths provided, though ideally we'd get the same
+ * estimate for any pair with the same parameterization.
+ */
+ nrows = calc_joinrel_size_estimate(root,
+ outer_rows,
+ inner_rows,
+ sjinfo,
+ restrict_clauses);
+ /* For safety, make sure result is not more than the base estimate */
+ if (nrows > rel->rows)
+ nrows = rel->rows;
+ return nrows;
}
/*
* calc_joinrel_size_estimate
- * Workhorse for set_joinrel_size_estimates and set_joinpath_size_estimate
+ * Workhorse for set_joinrel_size_estimates and
+ * get_parameterized_joinrel_size.
*/
static double
calc_joinrel_size_estimate(PlannerInfo *root,
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
+#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
EquivalenceClass *ec);
static List *generate_join_implied_equalities_normal(PlannerInfo *root,
EquivalenceClass *ec,
- RelOptInfo *joinrel,
- RelOptInfo *outer_rel,
- RelOptInfo *inner_rel);
+ Relids join_relids,
+ Relids outer_relids,
+ Relids inner_relids);
static List *generate_join_implied_equalities_broken(PlannerInfo *root,
EquivalenceClass *ec,
- RelOptInfo *joinrel,
- RelOptInfo *outer_rel,
- RelOptInfo *inner_rel);
+ Relids nominal_join_relids,
+ Relids outer_relids,
+ Relids nominal_inner_relids,
+ AppendRelInfo *inner_appinfo);
static Oid select_equality_operator(EquivalenceClass *ec,
Oid lefttype, Oid righttype);
static RestrictInfo *create_join_clause(PlannerInfo *root,
bool outer_on_left);
static bool reconsider_full_join_clause(PlannerInfo *root,
RestrictInfo *rinfo);
-static Index get_parent_relid(PlannerInfo *root, RelOptInfo *rel);
/*
* of the EC back into the main restrictinfo datastructures. Multi-relation
* clauses will be regurgitated later by generate_join_implied_equalities().
* (We do it this way to maintain continuity with the case that ec_broken
- * becomes set only after we've gone up a join level or two.)
+ * becomes set only after we've gone up a join level or two.) However, for
+ * an EC that contains constants, we can adopt a simpler strategy and just
+ * throw back all the source RestrictInfos immediately; that works because
+ * we know that such an EC can't become broken later. (This rule justifies
+ * ignoring ec_has_const ECs in generate_join_implied_equalities, even when
+ * they are broken.)
*/
static void
generate_base_implied_equalities_broken(PlannerInfo *root,
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc);
- if (bms_membership(restrictinfo->required_relids) != BMS_MULTIPLE)
+ if (ec->ec_has_const ||
+ bms_membership(restrictinfo->required_relids) != BMS_MULTIPLE)
distribute_restrictinfo_to_rels(root, restrictinfo);
}
}
* that all equivalence-class members computable at that node are equal.
* Since the set of clauses to enforce can vary depending on which subset
* relations are the inputs, we have to compute this afresh for each join
- * path pair. Hence a fresh List of RestrictInfo nodes is built and passed
- * back on each call.
+ * relation pair. Hence a fresh List of RestrictInfo nodes is built and
+ * passed back on each call.
+ *
+ * In addition to its use at join nodes, this can be applied to generate
+ * eclass-based join clauses for use in a parameterized scan of a base rel.
+ * The reason for the asymmetry of specifying the inner rel as a RelOptInfo
+ * and the outer rel by Relids is that this usage occurs before we have
+ * built any join RelOptInfos.
+ *
+ * An annoying special case for parameterized scans is that the inner rel can
+ * be an appendrel child (an "other rel"). In this case we must generate
+ * appropriate clauses using child EC members. add_child_rel_equivalences
+ * must already have been done for the child rel.
*
* The results are sufficient for use in merge, hash, and plain nestloop join
* methods. We do not worry here about selecting clauses that are optimal
- * for use in a nestloop-with-parameterized-inner-scan. indxpath.c makes
- * its own selections of clauses to use, and if the ones we pick here are
- * redundant with those, the extras will be eliminated in createplan.c.
+ * for use in a parameterized indexscan. indxpath.c makes its own selections
+ * of clauses to use, and if the ones we pick here are redundant with those,
+ * the extras will be eliminated at createplan time, using the parent_ec
+ * markers that we provide (see is_redundant_derived_clause()).
*
* Because the same join clauses are likely to be needed multiple times as
* we consider different join paths, we avoid generating multiple copies:
* we check to see if the pair matches any original clause (in ec_sources)
* or previously-built clause (in ec_derives). This saves memory and allows
* re-use of information cached in RestrictInfos.
+ *
+ * join_relids should always equal bms_union(outer_relids, inner_rel->relids).
+ * We could simplify this function's API by computing it internally, but in
+ * all current uses, the caller has the value at hand anyway.
*/
List *
generate_join_implied_equalities(PlannerInfo *root,
- RelOptInfo *joinrel,
- RelOptInfo *outer_rel,
+ Relids join_relids,
+ Relids outer_relids,
RelOptInfo *inner_rel)
{
List *result = NIL;
+ Relids inner_relids = inner_rel->relids;
+ Relids nominal_inner_relids;
+ Relids nominal_join_relids;
+ AppendRelInfo *inner_appinfo;
ListCell *lc;
+ /* If inner rel is a child, extra setup work is needed */
+ if (inner_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+ {
+ /* Lookup parent->child translation data */
+ inner_appinfo = find_childrel_appendrelinfo(root, inner_rel);
+ /* Construct relids for the parent rel */
+ nominal_inner_relids = bms_make_singleton(inner_appinfo->parent_relid);
+ /* ECs will be marked with the parent's relid, not the child's */
+ nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
+ }
+ else
+ {
+ inner_appinfo = NULL;
+ nominal_inner_relids = inner_relids;
+ nominal_join_relids = join_relids;
+ }
+
foreach(lc, root->eq_classes)
{
EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc);
continue;
/* We can quickly ignore any that don't overlap the join, too */
- if (!bms_overlap(ec->ec_relids, joinrel->relids))
+ if (!bms_overlap(ec->ec_relids, nominal_join_relids))
continue;
if (!ec->ec_broken)
sublist = generate_join_implied_equalities_normal(root,
ec,
- joinrel,
- outer_rel,
- inner_rel);
+ join_relids,
+ outer_relids,
+ inner_relids);
/* Recover if we failed to generate required derived clauses */
if (ec->ec_broken)
sublist = generate_join_implied_equalities_broken(root,
ec,
- joinrel,
- outer_rel,
- inner_rel);
+ nominal_join_relids,
+ outer_relids,
+ nominal_inner_relids,
+ inner_appinfo);
result = list_concat(result, sublist);
}
static List *
generate_join_implied_equalities_normal(PlannerInfo *root,
EquivalenceClass *ec,
- RelOptInfo *joinrel,
- RelOptInfo *outer_rel,
- RelOptInfo *inner_rel)
+ Relids join_relids,
+ Relids outer_relids,
+ Relids inner_relids)
{
List *result = NIL;
List *new_members = NIL;
{
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1);
- if (cur_em->em_is_child)
- continue; /* ignore children here */
- if (!bms_is_subset(cur_em->em_relids, joinrel->relids))
- continue; /* ignore --- not computable yet */
+ /*
+ * We don't need to check explicitly for child EC members. This test
+ * against join_relids will cause them to be ignored except when
+ * considering a child inner rel, which is what we want.
+ */
+ if (!bms_is_subset(cur_em->em_relids, join_relids))
+ continue; /* not computable yet, or wrong child */
- if (bms_is_subset(cur_em->em_relids, outer_rel->relids))
+ if (bms_is_subset(cur_em->em_relids, outer_relids))
outer_members = lappend(outer_members, cur_em);
- else if (bms_is_subset(cur_em->em_relids, inner_rel->relids))
+ else if (bms_is_subset(cur_em->em_relids, inner_relids))
inner_members = lappend(inner_members, cur_em);
else
new_members = lappend(new_members, cur_em);
* generate_join_implied_equalities cleanup after failure
*
* Return any original RestrictInfos that are enforceable at this join.
+ *
+ * In the case of a child inner relation, we have to translate the
+ * original RestrictInfos from parent to child Vars.
*/
static List *
generate_join_implied_equalities_broken(PlannerInfo *root,
EquivalenceClass *ec,
- RelOptInfo *joinrel,
- RelOptInfo *outer_rel,
- RelOptInfo *inner_rel)
+ Relids nominal_join_relids,
+ Relids outer_relids,
+ Relids nominal_inner_relids,
+ AppendRelInfo *inner_appinfo)
{
List *result = NIL;
ListCell *lc;
foreach(lc, ec->ec_sources)
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc);
+ Relids clause_relids = restrictinfo->required_relids;
- if (bms_is_subset(restrictinfo->required_relids, joinrel->relids) &&
- !bms_is_subset(restrictinfo->required_relids, outer_rel->relids) &&
- !bms_is_subset(restrictinfo->required_relids, inner_rel->relids))
+ if (bms_is_subset(clause_relids, nominal_join_relids) &&
+ !bms_is_subset(clause_relids, outer_relids) &&
+ !bms_is_subset(clause_relids, nominal_inner_relids))
result = lappend(result, restrictinfo);
}
+ /*
+ * If we have to translate, just brute-force apply adjust_appendrel_attrs
+ * to all the RestrictInfos at once. This will result in returning
+ * RestrictInfos that are not listed in ec_derives, but there shouldn't
+ * be any duplication, and it's a sufficiently narrow corner case that
+ * we shouldn't sweat too much over it anyway.
+ */
+ if (inner_appinfo)
+ result = (List *) adjust_appendrel_attrs(root, (Node *) result,
+ inner_appinfo);
+
return result;
}
/*
* add_child_rel_equivalences
- * Search for EC members that reference (only) the parent_rel, and
+ * Search for EC members that reference the parent_rel, and
* add transformed members referencing the child_rel.
*
* Note that this function won't be called at all unless we have at least some
{
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
- if (cur_em->em_is_child)
- continue; /* ignore children here */
+ if (cur_em->em_is_const || cur_em->em_is_child)
+ continue; /* ignore consts and children here */
- /* Does it reference (only) parent_rel? */
- if (bms_equal(cur_em->em_relids, parent_rel->relids))
+ /* Does it reference parent_rel? */
+ if (bms_overlap(cur_em->em_relids, parent_rel->relids))
{
/* Yes, generate transformed child version */
Expr *child_expr;
+ Relids new_relids;
child_expr = (Expr *)
adjust_appendrel_attrs(root,
(Node *) cur_em->em_expr,
appinfo);
- (void) add_eq_member(cur_ec, child_expr, child_rel->relids,
+
+ /*
+ * Transform em_relids to match. Note we do *not* do
+ * pull_varnos(child_expr) here, as for example the
+ * transformation might have substituted a constant, but we
+ * don't want the child member to be marked as constant.
+ */
+ new_relids = bms_difference(cur_em->em_relids,
+ parent_rel->relids);
+ new_relids = bms_add_members(new_relids, child_rel->relids);
+
+ (void) add_eq_member(cur_ec, child_expr, new_relids,
true, cur_em->em_datatype);
}
}
/* If it's a child rel, we'll need to know what its parent is */
if (is_child_rel)
- parent_relid = get_parent_relid(root, rel);
+ parent_relid = find_childrel_appendrelinfo(root, rel)->parent_relid;
else
parent_relid = 0; /* not used, but keep compiler quiet */
return result;
}
-/*
- * get_parent_relid
- * Get the relid of a child rel's parent appendrel
- *
- * Possibly this should be somewhere else, but right now the logic is only
- * needed here.
- */
-static Index
-get_parent_relid(PlannerInfo *root, RelOptInfo *rel)
-{
- ListCell *lc;
-
- foreach(lc, root->append_rel_list)
- {
- AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
-
- if (appinfo->child_relid == rel->relid)
- return appinfo->parent_relid;
- }
- /* should have found the entry ... */
- elog(ERROR, "child rel not found in append_rel_list");
- return 0;
-}
-
/*
* have_relevant_eclass_joinclause
* Detect whether there is an EquivalenceClass that could produce
return false;
}
+
+
+/*
+ * is_redundant_derived_clause
+ * Test whether rinfo is derived from same EC as any clause in clauselist;
+ * if so, it can be presumed to represent a condition that's redundant
+ * with that member of the list.
+ */
+bool
+is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist)
+{
+ EquivalenceClass *parent_ec = rinfo->parent_ec;
+ ListCell *lc;
+
+ /* Fail if it's not a potentially-redundant clause from some EC */
+ if (parent_ec == NULL)
+ return false;
+
+ foreach(lc, clauselist)
+ {
+ RestrictInfo *otherrinfo = (RestrictInfo *) lfirst(lc);
+
+ if (otherrinfo->parent_ec == parent_ec)
+ return true;
+ }
+
+ return false;
+}
List *paths);
static PathClauseUsage *classify_index_clause_usage(Path *path,
List **clauselist);
+static Relids get_bitmap_tree_required_outer(Path *bitmapqual);
static void find_indexpath_quals(Path *bitmapqual, List **quals, List **preds);
static int find_list_position(Node *node, List **nodelist);
static bool check_index_only(RelOptInfo *rel, IndexOptInfo *index);
BitmapHeapPath *bpath;
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
- bpath = create_bitmap_heap_path(root, rel, bitmapqual, 1.0);
+ bpath = create_bitmap_heap_path(root, rel, bitmapqual, NULL, 1.0);
add_path(rel, (Path *) bpath);
}
if (bitjoinpaths != NIL)
{
Path *bitmapqual;
+ Relids required_outer;
double loop_count;
BitmapHeapPath *bpath;
bitmapqual = choose_bitmap_and(root, rel, bitjoinpaths);
- loop_count = get_loop_count(root, bitmapqual->required_outer);
- bpath = create_bitmap_heap_path(root, rel, bitmapqual, loop_count);
+ required_outer = get_bitmap_tree_required_outer(bitmapqual);
+ loop_count = get_loop_count(root, required_outer);
+ bpath = create_bitmap_heap_path(root, rel, bitmapqual,
+ required_outer, loop_count);
add_path(rel, (Path *) bpath);
}
}
{
BitmapHeapPath bpath;
+ /* Must be a simple IndexPath so that we can just copy its param_info */
+ Assert(IsA(ipath, IndexPath));
+
/* Set up a dummy BitmapHeapPath */
bpath.path.type = T_BitmapHeapPath;
bpath.path.pathtype = T_BitmapHeapScan;
bpath.path.parent = rel;
+ bpath.path.param_info = ipath->param_info;
bpath.path.pathkeys = NIL;
- bpath.path.required_outer = ipath->required_outer;
- bpath.path.param_clauses = ipath->param_clauses;
bpath.bitmapqual = ipath;
- cost_bitmap_heap_scan((Path *) &bpath, root, rel, ipath,
- get_loop_count(root, bpath.path.required_outer));
+ cost_bitmap_heap_scan(&bpath.path, root, rel,
+ bpath.path.param_info,
+ ipath,
+ get_loop_count(root, PATH_REQ_OUTER(ipath)));
return bpath.path.total_cost;
}
static Cost
bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, List *paths)
{
- BitmapAndPath *apath;
+ BitmapAndPath apath;
BitmapHeapPath bpath;
+ Relids required_outer;
- /*
- * Create a temporary BitmapAndPath. (Because it needs realistic
- * required_outer and param_clauses values, making a dummy one would
- * take more code than it's worth.)
- */
- apath = create_bitmap_and_path(root, rel, paths);
+ /* Set up a dummy BitmapAndPath */
+ apath.path.type = T_BitmapAndPath;
+ apath.path.pathtype = T_BitmapAnd;
+ apath.path.parent = rel;
+ apath.path.param_info = NULL; /* not used in bitmap trees */
+ apath.path.pathkeys = NIL;
+ apath.bitmapquals = paths;
+ cost_bitmap_and_node(&apath, root);
+
+ /* Identify required outer rels, in case it's a parameterized scan */
+ required_outer = get_bitmap_tree_required_outer((Path *) &apath);
/* Set up a dummy BitmapHeapPath */
bpath.path.type = T_BitmapHeapPath;
bpath.path.pathtype = T_BitmapHeapScan;
bpath.path.parent = rel;
+ bpath.path.param_info = get_baserel_parampathinfo(root, rel,
+ required_outer);
bpath.path.pathkeys = NIL;
- bpath.path.required_outer = apath->path.required_outer;
- bpath.path.param_clauses = apath->path.param_clauses;
- bpath.bitmapqual = (Path *) apath;
+ bpath.bitmapqual = (Path *) &apath;
/* Now we can do cost_bitmap_heap_scan */
- cost_bitmap_heap_scan((Path *) &bpath, root, rel, (Path *) apath,
- get_loop_count(root, bpath.path.required_outer));
+ cost_bitmap_heap_scan(&bpath.path, root, rel,
+ bpath.path.param_info,
+ (Path *) &apath,
+ get_loop_count(root, required_outer));
return bpath.path.total_cost;
}
}
+/*
+ * get_bitmap_tree_required_outer
+ * Find the required outer rels for a bitmap tree (index/and/or)
+ *
+ * We don't associate any particular parameterization with a BitmapAnd or
+ * BitmapOr node; however, the IndexPaths have parameterization info, in
+ * their capacity as standalone access paths. The parameterization required
+ * for the bitmap heap scan node is the union of rels referenced in the
+ * child IndexPaths.
+ */
+static Relids
+get_bitmap_tree_required_outer(Path *bitmapqual)
+{
+ Relids result = NULL;
+ ListCell *lc;
+
+ if (IsA(bitmapqual, IndexPath))
+ {
+ return bms_copy(PATH_REQ_OUTER(bitmapqual));
+ }
+ else if (IsA(bitmapqual, BitmapAndPath))
+ {
+ foreach(lc, ((BitmapAndPath *) bitmapqual)->bitmapquals)
+ {
+ result = bms_join(result,
+ get_bitmap_tree_required_outer((Path *) lfirst(lc)));
+ }
+ }
+ else if (IsA(bitmapqual, BitmapOrPath))
+ {
+ foreach(lc, ((BitmapOrPath *) bitmapqual)->bitmapquals)
+ {
+ result = bms_join(result,
+ get_bitmap_tree_required_outer((Path *) lfirst(lc)));
+ }
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
+
+ return result;
+}
+
+
/*
* find_indexpath_quals
*
IndexClauseSet *clauseset,
List **joinorclauses)
{
- Relids inner_baserels;
ListCell *lc;
- /*
- * There is no value in considering join clauses for outer joins in which
- * the indexed relation is on the outside, since there'd be no way to
- * perform such a join with a parameterized nestloop. So first, identify
- * all baserels that are on the inside of such joins.
- */
- inner_baserels = NULL;
- foreach(lc, root->join_info_list)
- {
- SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
-
- if (bms_overlap(rel->relids, sjinfo->min_lefthand))
- inner_baserels = bms_add_members(inner_baserels,
- sjinfo->min_righthand);
- /* full joins constrain both sides symmetrically */
- if (sjinfo->jointype == JOIN_FULL &&
- bms_overlap(rel->relids, sjinfo->min_righthand))
- inner_baserels = bms_add_members(inner_baserels,
- sjinfo->min_lefthand);
- }
-
- /* Now scan the rel's join clauses */
+ /* Scan the rel's join clauses */
foreach(lc, rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
- /* Ignore if it mentions anything from wrong side of an outer join */
- if (bms_overlap(rinfo->clause_relids, inner_baserels))
- continue;
-
- /*
- * Note that we ignore required_relids; that's okay because we are
- * intentionally ignoring the normal rules for placing evaluation of
- * join clauses. The whole point here is to evaluate join clauses
- * below their join, even if they would normally be delayed by
- * outer join rules.
- *
- * Instead of considering required_relids, we ignore clauses for which
- * the indexed rel is in nullable_relids; that means there's an outer
- * join below the clause and so it can't be checked at the relation
- * scan level.
- *
- * Note: unlike create_or_index_quals(), we can accept clauses that
- * are marked !is_pushed_down (ie they are themselves outer-join
- * clauses). This is OK because any path generated with these clauses
- * could only be used in the inside of a nestloop join, which will be
- * the nullable side.
- */
- if (bms_overlap(rel->relids, rinfo->nullable_relids))
+ /* Check if clause can be moved to this rel */
+ if (!join_clause_is_movable_to(rinfo, rel->relid))
continue;
/* Potentially usable, so see if it matches the index or is an OR */
/*
* We cannot use an outer path that is parameterized by the inner rel.
*/
- if (bms_overlap(outerpath->required_outer, innerrel->relids))
+ if (bms_overlap(PATH_REQ_OUTER(outerpath), innerrel->relids))
continue;
/*
* We cannot use an outer path that is parameterized by the
* inner rel.
*/
- if (bms_overlap(outerpath->required_outer, innerrel->relids))
+ if (bms_overlap(PATH_REQ_OUTER(outerpath), innerrel->relids))
continue;
foreach(lc2, innerrel->cheapest_parameterized_paths)
* We cannot use an inner path that is parameterized by
* the outer rel, either.
*/
- if (bms_overlap(innerpath->required_outer,
+ if (bms_overlap(PATH_REQ_OUTER(innerpath),
outerrel->relids))
continue;
rel->pathlist = NIL;
/* Set up the dummy path */
- add_path(rel, (Path *) create_append_path(rel, NIL));
+ add_path(rel, (Path *) create_append_path(rel, NIL, NULL));
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
* Path for a given relation generates the same number of rows. Without
* this assumption we'd not be able to optimize solely on the cost of Paths,
* but would have to take number of output rows into account as well.
- * (Perhaps someday that'd be worth doing, but it's a pretty big change...)
+ * (The parameterized-paths stuff almost fixes this, but not quite...)
*
* 'rel' is the relation entry for which quals are to be created
*
return false;
/*
- * Find potentially interesting OR joinclauses.
- *
- * We must ignore clauses for which the target rel is in nullable_relids;
- * that means there's an outer join below the clause and so it can't be
- * enforced at the relation scan level.
- *
- * We must also ignore clauses that are marked !is_pushed_down (ie they
- * are themselves outer-join clauses). It would be safe to extract an
- * index condition from such a clause if we are within the nullable rather
- * than the non-nullable side of its join, but we haven't got enough
- * context here to tell which applies. OR clauses in outer-join quals
- * aren't exactly common, so we'll let that case go unoptimized for now.
+ * Find potentially interesting OR joinclauses. We can use any joinclause
+ * that is considered safe to move to this rel by the parameterized-path
+ * machinery, even though what we are going to do with it is not exactly
+ * a parameterized path.
*/
foreach(i, rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(i);
if (restriction_is_or_clause(rinfo) &&
- rinfo->is_pushed_down &&
- !bms_is_member(rel->relid, rinfo->nullable_relids))
+ join_clause_is_movable_to(rinfo, rel->relid))
{
/*
* Use the generate_bitmap_or_paths() machinery to estimate the
continue;
if (pathkeys_contained_in(pathkeys, path->pathkeys) &&
- bms_is_subset(path->required_outer, required_outer))
+ bms_is_subset(PATH_REQ_OUTER(path), required_outer))
matched_path = path;
}
return matched_path;
continue;
if (pathkeys_contained_in(pathkeys, path->pathkeys) &&
- bms_is_subset(path->required_outer, required_outer))
+ bms_is_subset(PATH_REQ_OUTER(path), required_outer))
matched_path = path;
}
return matched_path;
BitmapHeapPath *best_path,
List *tlist, List *scan_clauses);
static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
- List **qual, List **indexqual);
+ List **qual, List **indexqual, List **indexECs);
static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
List *tlist, List *scan_clauses);
static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
*/
scan_clauses = rel->baserestrictinfo;
+ /*
+ * If this is a parameterized scan, we also need to enforce all the join
+ * clauses available from the outer relation(s).
+ *
+ * For paranoia's sake, don't modify the stored baserestrictinfo list.
+ */
+ if (best_path->param_info)
+ scan_clauses = list_concat(list_copy(scan_clauses),
+ best_path->param_info->ppi_clauses);
+
switch (best_path->pathtype)
{
case T_SeqScan:
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
+ /* Replace any outer-relation variables with nestloop params */
+ if (best_path->param_info)
+ {
+ scan_clauses = (List *)
+ replace_nestloop_params(root, (Node *) scan_clauses);
+ }
+
scan_plan = make_seqscan(tlist,
scan_clauses,
scan_relid);
*/
fixed_indexorderbys = fix_indexorderby_references(root, best_path);
- /*
- * If this is a parameterized scan, the indexclauses will contain join
- * clauses that are not present in scan_clauses (since the passed-in value
- * is just the rel's baserestrictinfo list). We must add these clauses to
- * scan_clauses to ensure they get checked. In most cases we will remove
- * the join clauses again below, but if a join clause contains a special
- * operator, we need to make sure it gets into the scan_clauses.
- *
- * Note: pointer comparison should be enough to determine RestrictInfo
- * matches.
- */
- if (best_path->path.required_outer)
- scan_clauses = list_union_ptr(scan_clauses, best_path->indexclauses);
-
/*
* The qpqual list must contain all restrictions not automatically handled
- * by the index. All the predicates in the indexquals will be checked
- * (either by the index itself, or by nodeIndexscan.c), but if there are
- * any "special" operators involved then they must be included in qpqual.
- * The upshot is that qpqual must contain scan_clauses minus whatever
- * appears in indexquals.
+ * by the index, other than pseudoconstant clauses which will be handled
+ * by a separate gating plan node. All the predicates in the indexquals
+ * will be checked (either by the index itself, or by nodeIndexscan.c),
+ * but if there are any "special" operators involved then they must be
+ * included in qpqual. The upshot is that qpqual must contain
+ * scan_clauses minus whatever appears in indexquals.
*
* In normal cases simple pointer equality checks will be enough to spot
- * duplicate RestrictInfos, so we try that first. In some situations
- * (particularly with OR'd index conditions) we may have scan_clauses that
- * are not equal to, but are logically implied by, the index quals; so we
- * also try a predicate_implied_by() check to see if we can discard quals
- * that way. (predicate_implied_by assumes its first input contains only
- * immutable functions, so we have to check that.)
+ * duplicate RestrictInfos, so we try that first.
+ *
+ * Another common case is that a scan_clauses entry is generated from the
+ * same EquivalenceClass as some indexqual, and is therefore redundant
+ * with it, though not equal. (This happens when indxpath.c prefers a
+ * different derived equality than what generate_join_implied_equalities
+ * picked for a parameterized scan's ppi_clauses.)
+ *
+ * In some situations (particularly with OR'd index conditions) we may
+ * have scan_clauses that are not equal to, but are logically implied by,
+ * the index quals; so we also try a predicate_implied_by() check to see
+ * if we can discard quals that way. (predicate_implied_by assumes its
+ * first input contains only immutable functions, so we have to check
+ * that.)
*
* We can also discard quals that are implied by a partial index's
* predicate, but only in a plain SELECT; when scanning a target relation
if (rinfo->pseudoconstant)
continue; /* we may drop pseudoconstants here */
if (list_member_ptr(indexquals, rinfo))
- continue;
+ continue; /* simple duplicate */
+ if (is_redundant_derived_clause(rinfo, indexquals))
+ continue; /* derived from same EquivalenceClass */
if (!contain_mutable_functions((Node *) rinfo->clause))
{
List *clausel = list_make1(rinfo->clause);
if (predicate_implied_by(clausel, indexquals))
- continue;
+ continue; /* provably implied by indexquals */
if (best_path->indexinfo->indpred)
{
if (baserelid != root->parse->resultRelation &&
get_parse_rowmark(root->parse, baserelid) == NULL)
if (predicate_implied_by(clausel,
best_path->indexinfo->indpred))
- continue;
+ continue; /* implied by index predicate */
}
}
qpqual = lappend(qpqual, rinfo);
* it'd break the comparisons to predicates above ... (or would it? Those
* wouldn't have outer refs)
*/
- if (best_path->path.required_outer)
+ if (best_path->path.param_info)
{
stripped_indexquals = (List *)
replace_nestloop_params(root, (Node *) stripped_indexquals);
Plan *bitmapqualplan;
List *bitmapqualorig;
List *indexquals;
+ List *indexECs;
List *qpqual;
ListCell *l;
BitmapHeapScan *scan_plan;
/* Process the bitmapqual tree into a Plan tree and qual lists */
bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual,
- &bitmapqualorig, &indexquals);
-
- /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
- scan_clauses = extract_actual_clauses(scan_clauses, false);
-
- /*
- * If this is a parameterized scan, the indexclauses will contain join clauses
- * that are not present in scan_clauses (since the passed-in value is just
- * the rel's baserestrictinfo list). We must add these clauses to
- * scan_clauses to ensure they get checked. In most cases we will remove
- * the join clauses again below, but if a join clause contains a special
- * operator, we need to make sure it gets into the scan_clauses.
- */
- if (best_path->path.required_outer)
- {
- scan_clauses = list_concat_unique(scan_clauses, bitmapqualorig);
- }
+ &bitmapqualorig, &indexquals,
+ &indexECs);
/*
* The qpqual list must contain all restrictions not automatically handled
- * by the index. All the predicates in the indexquals will be checked
- * (either by the index itself, or by nodeBitmapHeapscan.c), but if there
- * are any "special" operators involved then they must be added to qpqual.
- * The upshot is that qpqual must contain scan_clauses minus whatever
- * appears in indexquals.
+ * by the index, other than pseudoconstant clauses which will be handled
+ * by a separate gating plan node. All the predicates in the indexquals
+ * will be checked (either by the index itself, or by
+ * nodeBitmapHeapscan.c), but if there are any "special" operators
+ * involved then they must be added to qpqual. The upshot is that qpqual
+ * must contain scan_clauses minus whatever appears in indexquals.
+ *
+ * This loop is similar to the comparable code in create_indexscan_plan(),
+ * but with some differences because it has to compare the scan clauses to
+ * stripped (no RestrictInfos) indexquals. See comments there for more
+ * info.
*
* In normal cases simple equal() checks will be enough to spot duplicate
- * clauses, so we try that first. In some situations (particularly with
- * OR'd index conditions) we may have scan_clauses that are not equal to,
- * but are logically implied by, the index quals; so we also try a
- * predicate_implied_by() check to see if we can discard quals that way.
- * (predicate_implied_by assumes its first input contains only immutable
- * functions, so we have to check that.)
+ * clauses, so we try that first. We next see if the scan clause is
+ * redundant with any top-level indexqual by virtue of being generated
+ * from the same EC. After that, try predicate_implied_by().
*
* Unlike create_indexscan_plan(), we need take no special thought here
* for partial index predicates; this is because the predicate conditions
* are already listed in bitmapqualorig and indexquals. Bitmap scans have
* to do it that way because predicate conditions need to be rechecked if
- * the scan becomes lossy.
+ * the scan becomes lossy, so they have to be included in bitmapqualorig.
*/
qpqual = NIL;
foreach(l, scan_clauses)
{
- Node *clause = (Node *) lfirst(l);
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
+ Node *clause = (Node *) rinfo->clause;
+ Assert(IsA(rinfo, RestrictInfo));
+ if (rinfo->pseudoconstant)
+ continue; /* we may drop pseudoconstants here */
if (list_member(indexquals, clause))
- continue;
+ continue; /* simple duplicate */
+ if (rinfo->parent_ec && list_member_ptr(indexECs, rinfo->parent_ec))
+ continue; /* derived from same EquivalenceClass */
if (!contain_mutable_functions(clause))
{
List *clausel = list_make1(clause);
if (predicate_implied_by(clausel, indexquals))
- continue;
+ continue; /* provably implied by indexquals */
}
- qpqual = lappend(qpqual, clause);
+ qpqual = lappend(qpqual, rinfo);
}
/* Sort clauses into best execution order */
qpqual = order_qual_clauses(root, qpqual);
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ qpqual = extract_actual_clauses(qpqual, false);
+
/*
* When dealing with special operators, we will at this point have
* duplicate clauses in qpqual and bitmapqualorig. We may as well drop
*/
bitmapqualorig = list_difference_ptr(bitmapqualorig, qpqual);
+ /*
+ * We have to replace any outer-relation variables with nestloop params in
+ * the qpqual and bitmapqualorig expressions. (This was already done for
+ * expressions attached to plan nodes in the bitmapqualplan tree.)
+ */
+ if (best_path->path.param_info)
+ {
+ qpqual = (List *)
+ replace_nestloop_params(root, (Node *) qpqual);
+ bitmapqualorig = (List *)
+ replace_nestloop_params(root, (Node *) bitmapqualorig);
+ }
+
/* Finally ready to build the plan node */
scan_plan = make_bitmap_heapscan(tlist,
qpqual,
* predicates, because we have to recheck predicates as well as index
* conditions if the bitmap scan becomes lossy.
*
+ * In addition, we return a list of EquivalenceClass pointers for all the
+ * top-level indexquals that were possibly-redundantly derived from ECs.
+ * This allows removal of scan_clauses that are redundant with such quals.
+ * (We do not attempt to detect such redundancies for quals that are within
+ * OR subtrees. This could be done in a less hacky way if we returned the
+ * indexquals in RestrictInfo form, but that would be slower and still pretty
+ * messy, since we'd have to build new RestrictInfos in many cases.)
+ *
* Note: if you find yourself changing this, you probably need to change
* make_restrictinfo_from_bitmapqual too.
*/
static Plan *
create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
- List **qual, List **indexqual)
+ List **qual, List **indexqual, List **indexECs)
{
Plan *plan;
List *subplans = NIL;
List *subquals = NIL;
List *subindexquals = NIL;
+ List *subindexECs = NIL;
ListCell *l;
/*
Plan *subplan;
List *subqual;
List *subindexqual;
+ List *subindexEC;
subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
- &subqual, &subindexqual);
+ &subqual, &subindexqual,
+ &subindexEC);
subplans = lappend(subplans, subplan);
subquals = list_concat_unique(subquals, subqual);
subindexquals = list_concat_unique(subindexquals, subindexqual);
+ /* Duplicates in indexECs aren't worth getting rid of */
+ subindexECs = list_concat(subindexECs, subindexEC);
}
plan = (Plan *) make_bitmap_and(subplans);
plan->startup_cost = apath->path.startup_cost;
plan->plan_width = 0; /* meaningless */
*qual = subquals;
*indexqual = subindexquals;
+ *indexECs = subindexECs;
}
else if (IsA(bitmapqual, BitmapOrPath))
{
Plan *subplan;
List *subqual;
List *subindexqual;
+ List *subindexEC;
subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
- &subqual, &subindexqual);
+ &subqual, &subindexqual,
+ &subindexEC);
subplans = lappend(subplans, subplan);
if (subqual == NIL)
const_true_subqual = true;
*indexqual = subindexquals;
else
*indexqual = list_make1(make_orclause(subindexquals));
+ *indexECs = NIL;
}
else if (IsA(bitmapqual, IndexPath))
{
IndexPath *ipath = (IndexPath *) bitmapqual;
IndexScan *iscan;
+ List *subindexECs;
ListCell *l;
/* Use the regular indexscan plan build machinery... */
*indexqual = lappend(*indexqual, pred);
}
}
-
- /*
- * Replace outer-relation variables with nestloop params, but only
- * after doing the above comparisons to index predicates.
- */
- if (ipath->path.required_outer)
+ subindexECs = NIL;
+ foreach(l, ipath->indexquals)
{
- *qual = (List *)
- replace_nestloop_params(root, (Node *) *qual);
- *indexqual = (List *)
- replace_nestloop_params(root, (Node *) *indexqual);
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
+
+ if (rinfo->parent_ec)
+ subindexECs = lappend(subindexECs, rinfo->parent_ec);
}
+ *indexECs = subindexECs;
}
else
{
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
+ /* Replace any outer-relation variables with nestloop params */
+ if (best_path->param_info)
+ {
+ scan_clauses = (List *)
+ replace_nestloop_params(root, (Node *) scan_clauses);
+ }
+
scan_plan = make_subqueryscan(tlist,
scan_clauses,
scan_relid,
* from join clauses, so doing this beforehand on the scan_clauses
* wouldn't work.)
*/
- if (best_path->path.required_outer)
+ if (best_path->path.param_info)
{
scan_plan->scan.plan.qual = (List *)
replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual);
ListCell *prev;
ListCell *next;
- /*
- * If the inner path is parameterized, it might have already used some of
- * the join quals, in which case we don't have to check them again at the
- * join node. Remove any join quals that are redundant.
- */
- joinrestrictclauses =
- select_nonredundant_join_clauses(joinrestrictclauses,
- best_path->innerjoinpath->param_clauses);
-
/* Sort join qual clauses into best execution order */
joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses);
otherclauses = NIL;
}
+ /* Replace any outer-relation variables with nestloop params */
+ if (best_path->path.param_info)
+ {
+ joinclauses = (List *)
+ replace_nestloop_params(root, (Node *) joinclauses);
+ otherclauses = (List *)
+ replace_nestloop_params(root, (Node *) otherclauses);
+ }
+
/*
* Identify any nestloop parameters that should be supplied by this join
* node, and move them from root->curOuterParams to the nestParams list.
mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
joinclauses = list_difference(joinclauses, mergeclauses);
+ /*
+ * Replace any outer-relation variables with nestloop params. There
+ * should not be any in the mergeclauses.
+ */
+ if (best_path->jpath.path.param_info)
+ {
+ joinclauses = (List *)
+ replace_nestloop_params(root, (Node *) joinclauses);
+ otherclauses = (List *)
+ replace_nestloop_params(root, (Node *) otherclauses);
+ }
+
/*
* Rearrange mergeclauses, if needed, so that the outer variable is always
* on the left; mark the mergeclause restrictinfos with correct
hashclauses = get_actual_clauses(best_path->path_hashclauses);
joinclauses = list_difference(joinclauses, hashclauses);
+ /*
+ * Replace any outer-relation variables with nestloop params. There
+ * should not be any in the hashclauses.
+ */
+ if (best_path->jpath.path.param_info)
+ {
+ joinclauses = (List *)
+ replace_nestloop_params(root, (Node *) joinclauses);
+ otherclauses = (List *)
+ replace_nestloop_params(root, (Node *) otherclauses);
+ }
+
/*
* Rearrange hashclauses, if needed, so that the outer variable is always
* on the left.
outerjoin_delayed,
pseudoconstant,
relids,
+ outerjoin_nonnullable,
nullable_relids);
/*
false, /* outerjoin_delayed */
false, /* pseudoconstant */
qualscope, /* required_relids */
+ NULL, /* outer_relids */
NULL); /* nullable_relids */
/* Set mergejoinability/hashjoinability flags */
comparisonCost = 2.0 * (indexExprCost.startup + indexExprCost.per_tuple);
/* Estimate the cost of seq scan + sort */
- seqScanPath = create_seqscan_path(root, rel);
+ seqScanPath = create_seqscan_path(root, rel, NULL);
cost_sort(&seqScanAndSortPath, root, NIL,
seqScanPath->total_cost, rel->tuples, rel->width,
comparisonCost, maintenance_work_mem, -1.0);
newinfo->required_relids = adjust_relid_set(oldinfo->required_relids,
appinfo->parent_relid,
appinfo->child_relid);
+ newinfo->outer_relids = adjust_relid_set(oldinfo->outer_relids,
+ appinfo->parent_relid,
+ appinfo->child_relid);
newinfo->nullable_relids = adjust_relid_set(oldinfo->nullable_relids,
appinfo->parent_relid,
appinfo->child_relid);
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
+#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
int cmp;
/* We only consider unparameterized paths in this step */
- if (path->required_outer)
+ if (path->param_info)
{
have_parameterized_paths = true;
continue;
{
Path *path = (Path *) lfirst(p);
- if (path->required_outer)
+ if (path->param_info)
add_parameterized_path(parent_rel, path);
}
}
* add_path
* 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
- * that have the same or superset required_outer rels.
+ * A path is worthy if it has a better sort order (better pathkeys) or
+ * cheaper cost (on either dimension), or generates fewer rows, than any
+ * existing path that has the same or superset parameterization rels.
*
* We also remove from the rel's pathlist any old paths that are dominated
* by new_path --- that is, new_path is cheaper, at least as well ordered,
- * and requires no outer rels not required by old path.
+ * generates no more rows, and requires no outer rels not required by the
+ * old path.
+ *
+ * In most cases, a path with a superset parameterization will generate
+ * fewer rows (since it has more join clauses to apply), so that those two
+ * figures of merit move in opposite directions; this means that a path of
+ * one parameterization can seldom dominate a path of another. But such
+ * cases do arise, so we make the full set of checks anyway.
*
* There is one policy decision embedded in this function, along with its
* sibling add_path_precheck: we treat all parameterized paths as having
CHECK_FOR_INTERRUPTS();
/* Pretend parameterized paths have no pathkeys, per comment above */
- new_path_pathkeys = new_path->required_outer ? NIL : new_path->pathkeys;
+ new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
/*
* Loop to check proposed new path against old paths. Note it is possible
* If the two paths compare differently for startup and total cost,
* 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.)
+ * other comparisons. Row count is checked last. (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. As
+ * explained above, row count very seldom makes a difference, so even
+ * though it's cheap to compare there's not much point in checking it
+ * earlier.)
*/
if (costcmp != COSTS_DIFFERENT)
{
/* Similarly check to see if either dominates on pathkeys */
List *old_path_pathkeys;
- old_path_pathkeys = old_path->required_outer ? NIL : old_path->pathkeys;
+ old_path_pathkeys = old_path->param_info ? NIL : old_path->pathkeys;
keyscmp = compare_pathkeys(new_path_pathkeys,
old_path_pathkeys);
if (keyscmp != PATHKEYS_DIFFERENT)
switch (costcmp)
{
case COSTS_EQUAL:
- outercmp = bms_subset_compare(new_path->required_outer,
- old_path->required_outer);
+ outercmp = bms_subset_compare(PATH_REQ_OUTER(new_path),
+ PATH_REQ_OUTER(old_path));
if (keyscmp == PATHKEYS_BETTER1)
{
- if (outercmp == BMS_EQUAL ||
- outercmp == BMS_SUBSET1)
+ if ((outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET1) &&
+ new_path->rows <= old_path->rows)
remove_old = true; /* new dominates old */
}
else if (keyscmp == PATHKEYS_BETTER2)
{
- if (outercmp == BMS_EQUAL ||
- outercmp == BMS_SUBSET2)
+ if ((outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET2) &&
+ new_path->rows >= old_path->rows)
accept_new = false; /* old dominates new */
}
else /* keyscmp == PATHKEYS_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.
+ * the same cost, so keep just one; to decide
+ * which, first check rows and then do an
+ * exact cost comparison.
*/
- if (compare_path_costs(new_path, old_path,
+ if (new_path->rows < old_path->rows)
+ remove_old = true; /* new dominates old */
+ else if (new_path->rows > old_path->rows)
+ accept_new = false; /* old dominates new */
+ else 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)
+ else if (outercmp == BMS_SUBSET1 &&
+ new_path->rows <= old_path->rows)
remove_old = true; /* new dominates old */
- else if (outercmp == BMS_SUBSET2)
+ else if (outercmp == BMS_SUBSET2 &&
+ new_path->rows >= old_path->rows)
accept_new = false; /* old dominates new */
/* else different parameterizations, keep both */
}
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)
+ outercmp = bms_subset_compare(PATH_REQ_OUTER(new_path),
+ PATH_REQ_OUTER(old_path));
+ if ((outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET1) &&
+ new_path->rows <= old_path->rows)
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)
+ outercmp = bms_subset_compare(PATH_REQ_OUTER(new_path),
+ PATH_REQ_OUTER(old_path));
+ if ((outercmp == BMS_EQUAL ||
+ outercmp == BMS_SUBSET2) &&
+ new_path->rows >= old_path->rows)
accept_new = false; /* old dominates new */
}
break;
* We assume we know the path's pathkeys and parameterization accurately,
* and have lower bounds for its costs.
*
+ * Note that we do not know the path's rowcount, since getting an estimate for
+ * that is too expensive to do before prechecking. We assume here that paths
+ * of a superset parameterization will generate fewer rows; if that holds,
+ * then paths with different parameterizations cannot dominate each other
+ * and so we can simply ignore existing paths of another parameterization.
+ * (In the infrequent cases where that rule of thumb fails, add_path will
+ * get rid of the inferior path.)
+ *
* At the time this is called, we haven't actually built a Path structure,
* so the required information has to be passed piecemeal.
*/
List *new_path_pathkeys;
ListCell *p1;
- /* Pretend parameterized paths have no pathkeys, per comment above */
+ /* Pretend parameterized paths have no pathkeys, per add_path comment */
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.
+ * We are looking for an old_path with the same parameterization (and
+ * by assumption the same rowcount) that dominates the new path on
+ * pathkeys as well as both cost 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
{
List *old_path_pathkeys;
- old_path_pathkeys = old_path->required_outer ? NIL : old_path->pathkeys;
+ old_path_pathkeys = old_path->param_info ? 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)
+ if (bms_equal(required_outer, PATH_REQ_OUTER(old_path)))
+ {
+ /* Found an old path that dominates the new one */
return false;
+ }
}
}
}
* 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.
+ * This is essentially a cut-down form of add_path(): we do not care
+ * about startup cost or sort ordering, only total cost, rowcount, and
+ * parameterization. Also, we must 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.
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);
+ outercmp = bms_subset_compare(PATH_REQ_OUTER(new_path),
+ PATH_REQ_OUTER(old_path));
if (outercmp != BMS_DIFFERENT)
{
if (costcmp < 0)
{
- if (outercmp != BMS_SUBSET2)
+ if (outercmp != BMS_SUBSET2 &&
+ new_path->rows <= old_path->rows)
remove_old = true; /* new dominates old */
}
else if (costcmp > 0)
{
- if (outercmp != BMS_SUBSET1)
+ if (outercmp != BMS_SUBSET1 &&
+ new_path->rows >= old_path->rows)
accept_new = false; /* old dominates new */
}
- else if (outercmp == BMS_SUBSET1)
+ else if (outercmp == BMS_SUBSET1 &&
+ new_path->rows <= old_path->rows)
remove_old = true; /* new dominates old */
- else if (outercmp == BMS_SUBSET2)
+ else if (outercmp == BMS_SUBSET2 &&
+ new_path->rows >= old_path->rows)
accept_new = false; /* old dominates new */
+ else if (new_path->rows < old_path->rows)
+ remove_old = true; /* new dominates old */
else
{
- /* Same cost and outer rels, arbitrarily keep the old */
+ /* Same cost, rows, and param rels; arbitrarily keep old */
accept_new = false; /* old equals or dominates new */
}
}
* pathnode.
*/
Path *
-create_seqscan_path(PlannerInfo *root, RelOptInfo *rel)
+create_seqscan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer)
{
Path *pathnode = makeNode(Path);
pathnode->pathtype = T_SeqScan;
pathnode->parent = rel;
+ pathnode->param_info = get_baserel_parampathinfo(root, rel,
+ required_outer);
pathnode->pathkeys = NIL; /* seqscan has unordered result */
- pathnode->required_outer = NULL;
- pathnode->param_clauses = NIL;
- cost_seqscan(pathnode, root, rel);
+ cost_seqscan(pathnode, root, rel, pathnode->param_info);
return pathnode;
}
* for an ordered index, or NoMovementScanDirection for
* an unordered index.
* 'indexonly' is true if an index-only scan is wanted.
- * 'required_outer' is the set of outer relids referenced in indexclauses.
+ * 'required_outer' is the set of outer relids for a parameterized path.
* 'loop_count' is the number of repetitions of the indexscan to factor into
* estimates of caching behavior.
*
pathnode->path.pathtype = indexonly ? T_IndexOnlyScan : T_IndexScan;
pathnode->path.parent = rel;
+ pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
+ required_outer);
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);
-
- &nb