Revise parameterized-path mechanism to fix assorted issues.

This patch adjusts the treatment of parameterized paths so that all paths
with the same parameterization (same set of required outer rels) for the
same relation will have the same rowcount estimate.  We cache the rowcount
estimates to ensure that property, and hopefully save a few cycles too.
Doing this makes it practical for add_path_precheck to operate without
a rowcount estimate: it need only assume that paths with different
parameterizations never dominate each other, which is close enough to
true anyway for coarse filtering, because normally a more-parameterized
path should yield fewer rows thanks to having more join clauses to apply.

In add_path, we do the full nine yards of comparing rowcount estimates
along with everything else, so that we can discard parameterized paths that
don't actually have an advantage.  This fixes some issues I'd found with
add_path rejecting parameterized paths on the grounds that they were more
expensive than not-parameterized ones, even though they yielded many fewer
rows and hence would be cheaper once subsequent joining was considered.

To make the same-rowcounts assumption valid, we have to require that any
parameterized path enforce *all* join clauses that could be obtained from
the particular set of outer rels, even if not all of them are useful for
indexing.  This is required at both base scans and joins.  It's a good
thing anyway since the net impact is that join quals are checked at the
lowest practical level in the join tree.  Hence, discard the original
rather ad-hoc mechanism for choosing parameterization joinquals, and build
a better one that has a more principled rule for when clauses can be moved.
The original rule was actually buggy anyway for lack of knowledge about
which relations are part of an outer join's outer side; getting this right
requires adding an outer_relids field to RestrictInfo.
This commit is contained in:
Tom Lane 2012-04-19 15:52:46 -04:00
parent cd1f4db4ae
commit 5b7b5518d0
30 changed files with 1793 additions and 782 deletions

View File

@ -468,7 +468,6 @@ fileGetForeignPaths(PlannerInfo *root,
total_cost,
NIL, /* no pathkeys */
NULL, /* no outer rel either */
NIL,
NIL)); /* no fdw_private data */
/*

View File

@ -1844,6 +1844,7 @@ _copyRestrictInfo(const RestrictInfo *from)
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);

View File

@ -815,6 +815,7 @@ _equalRestrictInfo(const RestrictInfo *a, const RestrictInfo *b)
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);
/*

View File

@ -1461,6 +1461,9 @@ _outFromExpr(StringInfo str, const FromExpr *node)
*
* 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)
@ -1468,12 +1471,15 @@ _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);
}
/*
@ -1727,6 +1733,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
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);
@ -1817,6 +1824,16 @@ _outPathKey(StringInfo str, const PathKey *node)
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)
{
@ -1830,6 +1847,7 @@ _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);
@ -3001,6 +3019,9 @@ _outNode(StringInfo str, const void *obj)
case T_PathKey:
_outPathKey(str, obj);
break;
case T_ParamPathInfo:
_outParamPathInfo(str, obj);
break;
case T_RestrictInfo:
_outRestrictInfo(str, obj);
break;

View File

@ -751,6 +751,32 @@ possible join types. So we will form a join path representing the query
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
@ -763,14 +789,14 @@ a nestloop that provides parameters to the lower join's inputs). While we
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

View File

@ -67,8 +67,7 @@ static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
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,
@ -375,7 +374,7 @@ static void
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);
@ -717,7 +716,7 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *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)
@ -777,25 +776,31 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
* (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 */
@ -812,13 +817,24 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
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 */
@ -830,18 +846,28 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
* 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;
@ -864,30 +890,22 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
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);
}
@ -909,12 +927,14 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
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));
}
}
@ -958,7 +978,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
/* 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);
@ -1112,7 +1132,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *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);

View File

@ -77,6 +77,7 @@
#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"
@ -125,12 +126,12 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
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,
@ -165,22 +166,29 @@ clamp_row_est(double nrows)
/*
* 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;
@ -196,8 +204,10 @@ cost_seqscan(Path *path, PlannerInfo *root,
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;
@ -251,46 +261,19 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
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)
@ -447,9 +430,9 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
*
* 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
@ -614,6 +597,7 @@ get_indexpath_pages(Path *bitmapqual)
* 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
@ -623,12 +607,14 @@ get_indexpath_pages(Path *bitmapqual)
*/
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;
@ -642,33 +628,11 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
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;
@ -743,10 +707,13 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
* 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;
@ -978,20 +945,28 @@ cost_tidscan(Path *path, PlannerInfo *root,
/*
* 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
@ -1001,8 +976,10 @@ cost_subqueryscan(Path *path, RelOptInfo *baserel)
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;
@ -1764,33 +1741,14 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
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
@ -1822,7 +1780,7 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
* 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;
@ -1849,7 +1807,7 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
}
/* 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;
@ -2141,9 +2099,11 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
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
@ -2513,9 +2473,11 @@ final_cost_hashjoin(PlannerInfo *root, HashPath *path,
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
@ -3125,6 +3087,35 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
(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
@ -3257,34 +3248,70 @@ compute_semi_anti_join_factors(PlannerInfo *root,
* 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 (pathtype == T_IndexScan ||
pathtype == T_IndexOnlyScan ||
pathtype == T_BitmapHeapScan)
{
if (path->joinrestrictinfo != NIL)
{
/* OK if all those clauses were found to be redundant */
return (joinclauses == NIL);
}
else
{
/* a clauseless join does NOT qualify */
return false;
}
}
else
{
/*
* 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.
*/
/* 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;
/* Find the indexclauses list for the inner scan */
switch (innerpath->pathtype)
{
case T_IndexScan:
case T_IndexOnlyScan:
indexclauses = ((IndexPath *) innerpath)->indexclauses;
break;
case T_BitmapHeapScan:
{
/* 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;
}
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;
}
/*
* 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)
{
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;
}
@ -3387,6 +3414,42 @@ set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
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.
@ -3402,7 +3465,9 @@ set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
* 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.
@ -3422,39 +3487,53 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
}
/*
* set_joinpath_size_estimate
* Set the rows estimate for the given join path.
* get_parameterized_joinrel_size
* Make a size estimate for a parameterized scan of a join relation.
*
* 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.
* '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,

View File

@ -21,6 +21,7 @@
#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"
@ -39,14 +40,15 @@ static void generate_base_implied_equalities_broken(PlannerInfo *root,
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,
@ -59,7 +61,6 @@ static bool reconsider_outer_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);
/*
@ -879,7 +880,12 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
* 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,
@ -891,7 +897,8 @@ 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);
}
}
@ -905,14 +912,26 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
* 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:
@ -920,16 +939,41 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
* 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);
@ -944,23 +988,24 @@ generate_join_implied_equalities(PlannerInfo *root,
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);
}
@ -974,9 +1019,9 @@ generate_join_implied_equalities(PlannerInfo *root,
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;
@ -997,14 +1042,17 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
{
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);
@ -1140,13 +1188,17 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
* 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;
@ -1154,13 +1206,25 @@ generate_join_implied_equalities_broken(PlannerInfo *root,
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;
}
@ -1783,7 +1847,7 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2)
/*
* 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
@ -1821,20 +1885,32 @@ add_child_rel_equivalences(PlannerInfo *root,
{
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);
}
}
@ -1907,7 +1983,7 @@ generate_implied_equalities_for_indexcol(PlannerInfo *root,
/* 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 */
@ -2008,30 +2084,6 @@ generate_implied_equalities_for_indexcol(PlannerInfo *root,
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
@ -2174,3 +2226,31 @@ eclass_useful_for_merging(EquivalenceClass *eclass,
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;
}

View File

@ -111,6 +111,7 @@ static Cost bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel,
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);
@ -303,7 +304,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
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);
}
@ -318,12 +319,15 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
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);
}
}
@ -1320,17 +1324,21 @@ bitmap_scan_cost_est(PlannerInfo *root, RelOptInfo *rel, Path *ipath)
{
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;
}
@ -1342,28 +1350,36 @@ bitmap_scan_cost_est(PlannerInfo *root, RelOptInfo *rel, Path *ipath)
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;
}
@ -1420,6 +1436,49 @@ classify_index_clause_usage(Path *path, List **clauselist)
}
/*
* 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
*
@ -1661,58 +1720,15 @@ match_join_clauses_to_index(PlannerInfo *root,
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 */

View File

@ -728,7 +728,7 @@ match_unsorted_outer(PlannerInfo *root,
/*
* 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;
/*
@ -1172,7 +1172,7 @@ hash_inner_and_outer(PlannerInfo *root,
* 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)
@ -1183,7 +1183,7 @@ hash_inner_and_outer(PlannerInfo *root,
* 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;

View File

@ -930,7 +930,7 @@ mark_dummy_rel(RelOptInfo *rel)
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);

View File

@ -67,7 +67,7 @@
* 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
*
@ -93,26 +93,17 @@ create_or_index_quals(PlannerInfo *root, RelOptInfo *rel)
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

View File

@ -439,7 +439,7 @@ get_cheapest_path_for_pathkeys(List *paths, List *pathkeys,
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;
@ -481,7 +481,7 @@ get_cheapest_fractional_path_for_pathkeys(List *paths,
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;

View File

@ -60,7 +60,7 @@ static BitmapHeapScan *create_bitmap_scan_plan(PlannerInfo *root,
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,
@ -305,6 +305,16 @@ create_scan_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:
@ -1060,6 +1070,13 @@ create_seqscan_plan(PlannerInfo *root, Path *best_path,
/* 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);
@ -1118,35 +1135,30 @@ create_indexscan_plan(PlannerInfo *root,
*/
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
@ -1162,20 +1174,22 @@ create_indexscan_plan(PlannerInfo *root,
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);
@ -1196,7 +1210,7 @@ create_indexscan_plan(PlannerInfo *root,
* 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);
@ -1247,6 +1261,7 @@ create_bitmap_scan_plan(PlannerInfo *root,
Plan *bitmapqualplan;
List *bitmapqualorig;
List *indexquals;
List *indexECs;
List *qpqual;
ListCell *l;
BitmapHeapScan *scan_plan;
@ -1257,66 +1272,63 @@ create_bitmap_scan_plan(PlannerInfo *root,
/* 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
@ -1325,6 +1337,19 @@ create_bitmap_scan_plan(PlannerInfo *root,
*/
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,
@ -1349,12 +1374,20 @@ create_bitmap_scan_plan(PlannerInfo *root,
* 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;
@ -1364,6 +1397,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
List *subplans = NIL;
List *subquals = NIL;
List *subindexquals = NIL;
List *subindexECs = NIL;
ListCell *l;
/*
@ -1378,12 +1412,16 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
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;
@ -1393,6 +1431,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
plan->plan_width = 0; /* meaningless */
*qual = subquals;
*indexqual = subindexquals;
*indexECs = subindexECs;
}
else if (IsA(bitmapqual, BitmapOrPath))
{
@ -1418,9 +1457,11 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
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;
@ -1469,11 +1510,13 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
*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... */
@ -1508,18 +1551,15 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
*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
{
@ -1594,6 +1634,13 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
/* 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,
@ -1859,7 +1906,7 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
* 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);
@ -1909,15 +1956,6 @@ create_nestloop_plan(PlannerInfo *root,
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);
@ -1935,6 +1973,15 @@ create_nestloop_plan(PlannerInfo *root,
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.
@ -2031,6 +2078,18 @@ create_mergejoin_plan(PlannerInfo *root,
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
@ -2309,6 +2368,18 @@ create_hashjoin_plan(PlannerInfo *root,
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.

View File

@ -1017,6 +1017,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
outerjoin_delayed,
pseudoconstant,
relids,
outerjoin_nonnullable,
nullable_relids);
/*
@ -1466,6 +1467,7 @@ build_implied_join_equality(Oid opno,
false, /* outerjoin_delayed */
false, /* pseudoconstant */
qualscope, /* required_relids */
NULL, /* outer_relids */
NULL); /* nullable_relids */
/* Set mergejoinability/hashjoinability flags */

View File

@ -3288,7 +3288,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
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);

View File

@ -1778,6 +1778,9 @@ adjust_appendrel_attrs_mutator(Node *node,
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);

View File

@ -22,6 +22,7 @@
#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"
@ -213,7 +214,7 @@ set_cheapest(RelOptInfo *parent_rel)
int cmp;
/* We only consider unparameterized paths in this step */
if (path->required_outer)
if (path->param_info)
{
have_parameterized_paths = true;
continue;
@ -263,7 +264,7 @@ set_cheapest(RelOptInfo *parent_rel)
{
Path *path = (Path *) lfirst(p);
if (path->required_outer)
if (path->param_info)
add_parameterized_path(parent_rel, path);
}
}
@ -273,13 +274,20 @@ set_cheapest(RelOptInfo *parent_rel)
* 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
@ -325,7 +333,7 @@ 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;
new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
/*
* Loop to check proposed new path against old paths. Note it is possible
@ -352,16 +360,19 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
* 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)
@ -369,18 +380,20 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
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 */
@ -389,19 +402,25 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
{
/*
* 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 */
}
@ -409,20 +428,22 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
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;
@ -491,6 +512,14 @@ add_path(RelOptInfo *parent_rel, Path *new_path)
* 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.
*/
@ -502,18 +531,19 @@ add_path_precheck(RelOptInfo *parent_rel,
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
@ -525,17 +555,17 @@ add_path_precheck(RelOptInfo *parent_rel,
{
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;
}
}
}
}
@ -559,10 +589,10 @@ add_path_precheck(RelOptInfo *parent_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.
* 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.
@ -598,27 +628,33 @@ add_parameterized_path(RelOptInfo *parent_rel, Path *new_path)
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 */
}
}
@ -675,17 +711,17 @@ add_parameterized_path(RelOptInfo *parent_rel, Path *new_path)
* 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;
}
@ -708,7 +744,7 @@ 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.
* '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.
*
@ -734,25 +770,9 @@ create_index_path(PlannerInfo *root,
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);
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,
@ -777,6 +797,7 @@ create_index_path(PlannerInfo *root,
* Creates a path node for a bitmap scan.
*
* 'bitmapqual' is a tree of IndexPath, BitmapAndPath, and BitmapOrPath nodes.
* '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.
*
@ -787,19 +808,22 @@ BitmapHeapPath *
create_bitmap_heap_path(PlannerInfo *root,
RelOptInfo *rel,
Path *bitmapqual,
Relids required_outer,
double loop_count)
{
BitmapHeapPath *pathnode = makeNode(BitmapHeapPath);
pathnode->path.pathtype = T_BitmapHeapScan;
pathnode->path.parent = rel;
pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
required_outer);
pathnode->path.pathkeys = NIL; /* always unordered */
pathnode->path.required_outer = bitmapqual->required_outer;
pathnode->path.param_clauses = bitmapqual->param_clauses;
pathnode->bitmapqual = bitmapqual;
cost_bitmap_heap_scan(&pathnode->path, root, rel, bitmapqual, loop_count);
cost_bitmap_heap_scan(&pathnode->path, root, rel,
pathnode->path.param_info,
bitmapqual, loop_count);
return pathnode;
}
@ -814,29 +838,14 @@ 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.param_info = NULL; /* not used in bitmap trees */
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);
@ -853,29 +862,14 @@ 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.param_info = NULL; /* not used in bitmap trees */
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);
@ -893,9 +887,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->path.param_info = NULL; /* never parameterized at present */
pathnode->path.pathkeys = NIL; /* always unordered */
pathnode->tidquals = tidquals;
@ -912,17 +905,17 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals)
* Note that we must handle subpaths = NIL, representing a dummy access path.
*/
AppendPath *
create_append_path(RelOptInfo *rel, List *subpaths)
create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer)
{
AppendPath *pathnode = makeNode(AppendPath);
ListCell *l;
pathnode->path.pathtype = T_Append;
pathnode->path.parent = rel;
pathnode->path.param_info = get_appendrel_parampathinfo(rel,
required_outer);
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;
/*
@ -932,18 +925,6 @@ create_append_path(RelOptInfo *rel, List *subpaths)
* 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().
*
* 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;
@ -958,9 +939,8 @@ create_append_path(RelOptInfo *rel, List *subpaths)
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);
/* All child paths must have same parameterization */
Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
}
return pathnode;
@ -975,7 +955,8 @@ MergeAppendPath *
create_merge_append_path(PlannerInfo *root,
RelOptInfo *rel,
List *subpaths,
List *pathkeys)
List *pathkeys,
Relids required_outer)
{
MergeAppendPath *pathnode = makeNode(MergeAppendPath);
Cost input_startup_cost;
@ -984,46 +965,22 @@ create_merge_append_path(PlannerInfo *root,
pathnode->path.pathtype = T_MergeAppend;
pathnode->path.parent = rel;
pathnode->path.param_info = get_appendrel_parampathinfo(rel,
required_outer);
pathnode->path.pathkeys = pathkeys;
pathnode->path.required_outer = NULL; /* updated below */
pathnode->path.param_clauses = NIL; /* XXX see below */
pathnode->subpaths = subpaths;
/*
* Apply query-wide LIMIT if known and path is for sole base relation.
* Finding out the latter at this low level is a bit klugy.
* (Handling this at this low level is a bit klugy.)
*/
pathnode->limit_tuples = root->limit_tuples;
if (pathnode->limit_tuples >= 0)
{
Index rti;
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
if (brel == NULL)
continue;
/* ignore RTEs that are "other rels" */
if (brel->reloptkind != RELOPT_BASEREL)
continue;
if (brel != rel)
{
/* Oops, it's a join query */
pathnode->limit_tuples = -1.0;
break;
}
}
}
if (bms_equal(rel->relids, root->all_baserels))
pathnode->limit_tuples = root->limit_tuples;
else
pathnode->limit_tuples = -1.0;
/*
* 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.
* Add up the sizes and costs of the input paths.
*/
pathnode->path.rows = 0;
input_startup_cost = 0;
@ -1058,9 +1015,8 @@ create_merge_append_path(PlannerInfo *root,
input_total_cost += sort_path.total_cost;
}
pathnode->path.required_outer =
bms_add_members(pathnode->path.required_outer,
subpath->required_outer);
/* All child paths must have same parameterization */
Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
}
/* Now we can compute total costs of the MergeAppend */
@ -1084,9 +1040,8 @@ create_result_path(List *quals)
pathnode->path.pathtype = T_Result;
pathnode->path.parent = NULL;
pathnode->path.param_info = NULL;
pathnode->path.pathkeys = NIL;
pathnode->path.required_outer = NULL;
pathnode->path.param_clauses = NIL;
pathnode->quals = quals;
/* Hardly worth defining a cost_result() function ... just do it */
@ -1114,11 +1069,12 @@ create_material_path(RelOptInfo *rel, Path *subpath)
{
MaterialPath *pathnode = makeNode(MaterialPath);
Assert(subpath->parent == rel);
pathnode->path.pathtype = T_Material;
pathnode->path.parent = rel;
pathnode->path.param_info = subpath->param_info;
pathnode->path.pathkeys = subpath->pathkeys;
pathnode->path.required_outer = subpath->required_outer;
pathnode->path.param_clauses = subpath->param_clauses;
pathnode->subpath = subpath;
@ -1159,6 +1115,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
/* Caller made a mistake if subpath isn't cheapest_total ... */
Assert(subpath == rel->cheapest_total_path);
Assert(subpath->parent == rel);
/* ... or if SpecialJoinInfo is the wrong one */
Assert(sjinfo->jointype == JOIN_SEMI);
Assert(bms_equal(rel->relids, sjinfo->syn_righthand));
@ -1325,14 +1282,13 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
pathnode->path.pathtype = T_Unique;
pathnode->path.parent = rel;
pathnode->path.param_info = subpath->param_info;
/*
* Assume the output is unsorted, since we don't necessarily have pathkeys
* 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;
@ -1661,17 +1617,18 @@ distinct_col_search(int colno, List *colnos, List *opids)
* returning the pathnode.
*/
Path *
create_subqueryscan_path(RelOptInfo *rel, List *pathkeys)
create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
List *pathkeys, Relids required_outer)
{
Path *pathnode = makeNode(Path);
pathnode->pathtype = T_SubqueryScan;
pathnode->parent = rel;
pathnode->param_info = get_baserel_parampathinfo(root, rel,
required_outer);
pathnode->pathkeys = pathkeys;
pathnode->required_outer = NULL;
pathnode->param_clauses = NIL;
cost_subqueryscan(pathnode, rel);
cost_subqueryscan(pathnode, root, rel, pathnode->param_info);
return pathnode;
}
@ -1688,9 +1645,8 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_FunctionScan;
pathnode->parent = rel;
pathnode->param_info = NULL; /* never parameterized at present */
pathnode->pathkeys = NIL; /* for now, assume unordered result */
pathnode->required_outer = NULL;
pathnode->param_clauses = NIL;
cost_functionscan(pathnode, root, rel);
@ -1709,9 +1665,8 @@ create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_ValuesScan;
pathnode->parent = rel;
pathnode->param_info = NULL; /* never parameterized at present */
pathnode->pathkeys = NIL; /* result is always unordered */
pathnode->required_outer = NULL;
pathnode->param_clauses = NIL;
cost_valuesscan(pathnode, root, rel);
@ -1730,9 +1685,8 @@ create_ctescan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_CteScan;
pathnode->parent = rel;
pathnode->param_info = NULL; /* never parameterized at present */
pathnode->pathkeys = NIL; /* XXX for now, result is always unordered */
pathnode->required_outer = NULL;
pathnode->param_clauses = NIL;
cost_ctescan(pathnode, root, rel);
@ -1751,9 +1705,8 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel)
pathnode->pathtype = T_WorkTableScan;
pathnode->parent = rel;
pathnode->param_info = NULL; /* never parameterized at present */
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);
@ -1775,19 +1728,19 @@ ForeignPath *
create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
double rows, Cost startup_cost, Cost total_cost,
List *pathkeys,
Relids required_outer, List *param_clauses,
Relids required_outer,
List *fdw_private)
{
ForeignPath *pathnode = makeNode(ForeignPath);
pathnode->path.pathtype = T_ForeignScan;
pathnode->path.parent = rel;
pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
required_outer);
pathnode->path.rows = rows;
pathnode->path.startup_cost = startup_cost;
pathnode->path.total_cost = total_cost;
pathnode->path.pathkeys = pathkeys;
pathnode->path.required_outer = required_outer;
pathnode->path.param_clauses = param_clauses;
pathnode->fdw_private = fdw_private;
@ -1803,17 +1756,17 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids
calc_nestloop_required_outer(Path *outer_path, Path *inner_path)
{
Relids outer_paramrels = PATH_REQ_OUTER(outer_path);
Relids inner_paramrels = PATH_REQ_OUTER(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));
Assert(!bms_overlap(outer_paramrels, inner_path->parent->relids));
/* easy case if inner path is not parameterized */
if (!inner_path->required_outer)
return bms_copy(outer_path->required_outer);
if (!inner_paramrels)
return bms_copy(outer_paramrels);
/* else, form the union ... */
required_outer = bms_union(outer_path->required_outer,
inner_path->required_outer);
required_outer = bms_union(outer_paramrels, inner_paramrels);
/* ... and remove any mention of now-satisfied outer rels */
required_outer = bms_del_members(required_outer,
outer_path->parent->relids);
@ -1835,16 +1788,15 @@ calc_nestloop_required_outer(Path *outer_path, Path *inner_path)
Relids
calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path)
{
Relids outer_paramrels = PATH_REQ_OUTER(outer_path);
Relids inner_paramrels = PATH_REQ_OUTER(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));
Assert(!bms_overlap(outer_paramrels, inner_path->parent->relids));
Assert(!bms_overlap(inner_paramrels, outer_path->parent->relids));
/* form the union ... */
required_outer = bms_union(outer_path->required_outer,
inner_path->required_outer);
required_outer = bms_union(outer_paramrels, inner_paramrels);
/* we do not need an explicit test for empty; bms_union gets it right */
return required_outer;
}
@ -1881,30 +1833,45 @@ create_nestloop_path(PlannerInfo *root,
Relids required_outer)
{
NestPath *pathnode = makeNode(NestPath);
Relids inner_req_outer = PATH_REQ_OUTER(inner_path);
/*
* If the inner path is parameterized by the outer, we must drop any
* restrict_clauses that are due to be moved into the inner path. We
* have to do this now, rather than postpone the work till createplan
* time, because the restrict_clauses list can affect the size and cost
* estimates for this path.
*/
if (bms_overlap(inner_req_outer, outer_path->parent->relids))
{
Relids inner_and_outer = bms_union(inner_path->parent->relids,
inner_req_outer);
List *jclauses = NIL;
ListCell *lc;
foreach(lc, restrict_clauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (!join_clause_is_movable_into(rinfo,
inner_path->parent->relids,
inner_and_outer))
jclauses = lappend(jclauses, rinfo);
}
restrict_clauses = jclauses;
}
pathnode->path.pathtype = T_NestLoop;
pathnode->path.parent = joinrel;
pathnode->path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
outer_path,
inner_path,
sjinfo,
required_outer,
&restrict_clauses);
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;
@ -1953,11 +1920,15 @@ create_mergejoin_path(PlannerInfo *root,
pathnode->jpath.path.pathtype = T_MergeJoin;
pathnode->jpath.path.parent = joinrel;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
outer_path,
inner_path,
sjinfo,
required_outer,
&restrict_clauses);
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;
@ -2005,6 +1976,14 @@ create_hashjoin_path(PlannerInfo *root,
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
outer_path,
inner_path,
sjinfo,
required_outer,
&restrict_clauses);
/*
* A hashjoin never has pathkeys, since its output ordering is
@ -2018,10 +1997,6 @@ 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;
@ -2033,3 +2008,71 @@ create_hashjoin_path(PlannerInfo *root,
return pathnode;
}
/*
* reparameterize_path
* Attempt to modify a Path to have greater parameterization
*
* We use this to attempt to bring all child paths of an appendrel to the
* same parameterization level, ensuring that they all enforce the same set
* of join quals (and thus that that parameterization can be attributed to
* an append path built from such paths). Currently, only a few path types
* are supported here, though more could be added at need. We return NULL
* if we can't reparameterize the given path.
*
* Note: we intentionally do not pass created paths to add_path(); it would
* possibly try to delete them on the grounds of being cost-inferior to the
* paths they were made from, and we don't want that. Paths made here are
* not necessarily of general-purpose usefulness, but they can be useful
* as members of an append path.
*/
Path *
reparameterize_path(PlannerInfo *root, Path *path,
Relids required_outer,
double loop_count)
{
RelOptInfo *rel = path->parent;
/* Can only increase, not decrease, path's parameterization */
if (!bms_is_subset(PATH_REQ_OUTER(path), required_outer))
return NULL;
switch (path->pathtype)
{
case T_SeqScan:
return create_seqscan_path(root, rel, required_outer);
case T_IndexScan:
case T_IndexOnlyScan:
{
IndexPath *ipath = (IndexPath *) path;
IndexPath *newpath = makeNode(IndexPath);
/*
* We can't use create_index_path directly, and would not want to
* because it would re-compute the indexqual conditions which is
* wasted effort. Instead we hack things a bit: flat-copy the
* path node, revise its param_info, and redo the cost estimate.
*/
memcpy(newpath, ipath, sizeof(IndexPath));
newpath->path.param_info =
get_baserel_parampathinfo(root, rel, required_outer);
cost_index(newpath, root, loop_count);
return (Path *) newpath;
}
case T_BitmapHeapScan:
{
BitmapHeapPath *bpath = (BitmapHeapPath *) path;
return (Path *) create_bitmap_heap_path(root,
rel,
bpath->bitmapqual,
required_outer,
loop_count);
}
case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys,
required_outer);
default:
break;
}
return NULL;
}

View File

@ -19,6 +19,7 @@
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
#include "optimizer/restrictinfo.h"
#include "utils/hsearch.h"
@ -100,6 +101,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->width = 0;
rel->reltargetlist = NIL;
rel->pathlist = NIL;
rel->ppilist = NIL;
rel->cheapest_startup_path = NULL;
rel->cheapest_total_path = NULL;
rel->cheapest_unique_path = NULL;
@ -352,6 +354,7 @@ build_join_rel(PlannerInfo *root,
joinrel->width = 0;
joinrel->reltargetlist = NIL;
joinrel->pathlist = NIL;
joinrel->ppilist = NIL;
joinrel->cheapest_startup_path = NULL;
joinrel->cheapest_total_path = NULL;
joinrel->cheapest_unique_path = NULL;
@ -574,8 +577,8 @@ build_joinrel_restrictlist(PlannerInfo *root,
*/
result = list_concat(result,
generate_join_implied_equalities(root,
joinrel,
outer_rel,
joinrel->relids,
outer_rel->relids,
inner_rel));
return result;
@ -667,3 +670,296 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
return new_joininfo;
}
/*
* find_childrel_appendrelinfo
* Get the AppendRelInfo associated with an appendrel child rel.
*
* This search could be eliminated by storing a link in child RelOptInfos,
* but for now it doesn't seem performance-critical.
*/
AppendRelInfo *
find_childrel_appendrelinfo(PlannerInfo *root, RelOptInfo *rel)
{
Index relid = rel->relid;
ListCell *lc;
/* Should only be called on child rels */
Assert(rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
foreach(lc, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
if (appinfo->child_relid == relid)
return appinfo;
}
/* should have found the entry ... */
elog(ERROR, "child rel %d not found in append_rel_list", relid);
return NULL; /* not reached */
}
/*
* get_baserel_parampathinfo
* Get the ParamPathInfo for a parameterized path for a base relation,
* constructing one if we don't have one already.
*
* This centralizes estimating the rowcounts for parameterized paths.
* We need to cache those to be sure we use the same rowcount for all paths
* of the same parameterization for a given rel. This is also a convenient
* place to determine which movable join clauses the parameterized path will
* be responsible for evaluating.
*/
ParamPathInfo *
get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
Relids required_outer)
{
ParamPathInfo *ppi;
Relids joinrelids;
List *pclauses;
double rows;
ListCell *lc;
/* Unparameterized paths have no ParamPathInfo */
if (bms_is_empty(required_outer))
return NULL;
Assert(!bms_overlap(baserel->relids, required_outer));
/* If we already have a PPI for this parameterization, just return it */
foreach(lc, baserel->ppilist)
{
ppi = (ParamPathInfo *) lfirst(lc);
if (bms_equal(ppi->ppi_req_outer, required_outer))
return ppi;
}
/*
* Identify all joinclauses that are movable to this base rel given this
* parameterization.
*/
joinrelids = bms_union(baserel->relids, required_outer);
pclauses = NIL;
foreach(lc, baserel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (join_clause_is_movable_into(rinfo,
baserel->relids,
joinrelids))
pclauses = lappend(pclauses, rinfo);
}
/*
* Add in joinclauses generated by EquivalenceClasses, too. (These
* necessarily satisfy join_clause_is_movable_into.)
*/
pclauses = list_concat(pclauses,
generate_join_implied_equalities(root,
joinrelids,
required_outer,
baserel));
/* Estimate the number of rows returned by the parameterized scan */
rows = get_parameterized_baserel_size(root, baserel, pclauses);
/* And now we can build the ParamPathInfo */
ppi = makeNode(ParamPathInfo);
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = rows;
ppi->ppi_clauses = pclauses;
baserel->ppilist = lappend(baserel->ppilist, ppi);
return ppi;
}
/*
* get_joinrel_parampathinfo
* Get the ParamPathInfo for a parameterized path for a join relation,
* constructing one if we don't have one already.
*
* This centralizes estimating the rowcounts for parameterized paths.
* We need to cache those to be sure we use the same rowcount for all paths
* of the same parameterization for a given rel. This is also a convenient
* place to determine which movable join clauses the parameterized path will
* be responsible for evaluating.
*
* outer_path and inner_path are a pair of input paths that can be used to
* construct the join, and restrict_clauses is the list of regular join
* clauses (including clauses derived from EquivalenceClasses) that must be
* applied at the join node when using these inputs.
*
* Unlike the situation for base rels, the set of movable join clauses to be
* enforced at a join varies with the selected pair of input paths, so we
* must calculate that and pass it back, even if we already have a matching
* ParamPathInfo. We handle this by adding any clauses moved down to this
* join to *restrict_clauses, which is an in/out parameter. (The addition
* is done in such a way as to not modify the passed-in List structure.)
*
* Note: when considering a nestloop join, the caller must have removed from
* restrict_clauses any movable clauses that are themselves scheduled to be
* pushed into the right-hand path. We do not do that here since it's
* unnecessary for other join types.
*/
ParamPathInfo *
get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
SpecialJoinInfo *sjinfo,
Relids required_outer,
List **restrict_clauses)
{
ParamPathInfo *ppi;
Relids join_and_req;
Relids outer_and_req;
Relids inner_and_req;
List *pclauses;
List *eclauses;
double rows;
ListCell *lc;
/* Unparameterized paths have no ParamPathInfo or extra join clauses */
if (bms_is_empty(required_outer))
return NULL;
Assert(!bms_overlap(joinrel->relids, required_outer));
/*
* Identify all joinclauses that are movable to this join rel given this
* parameterization. These are the clauses that are movable into this
* join, but not movable into either input path. Treat an unparameterized
* input path as not accepting parameterized clauses (because it won't,
* per the shortcut exit above), even though the joinclause movement rules
* might allow the same clauses to be moved into a parameterized path for
* that rel.
*/
join_and_req = bms_union(joinrel->relids, required_outer);
if (outer_path->param_info)
outer_and_req = bms_union(outer_path->parent->relids,
PATH_REQ_OUTER(outer_path));
else
outer_and_req = NULL; /* outer path does not accept parameters */
if (inner_path->param_info)
inner_and_req = bms_union(inner_path->parent->relids,
PATH_REQ_OUTER(inner_path));
else
inner_and_req = NULL; /* inner path does not accept parameters */
pclauses = NIL;
foreach(lc, joinrel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (join_clause_is_movable_into(rinfo,
joinrel->relids,
join_and_req) &&
!join_clause_is_movable_into(rinfo,
outer_path->parent->relids,
outer_and_req) &&
!join_clause_is_movable_into(rinfo,
inner_path->parent->relids,
inner_and_req))
pclauses = lappend(pclauses, rinfo);
}
/* Consider joinclauses generated by EquivalenceClasses, too */
eclauses = generate_join_implied_equalities(root,
join_and_req,
required_outer,
joinrel);
/* We only want ones that aren't movable to lower levels */
foreach(lc, eclauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
Assert(join_clause_is_movable_into(rinfo,
joinrel->relids,
join_and_req));
if (!join_clause_is_movable_into(rinfo,
outer_path->parent->relids,
outer_and_req) &&
!join_clause_is_movable_into(rinfo,
inner_path->parent->relids,
inner_and_req))
pclauses = lappend(pclauses, rinfo);
}
/*
* Now, attach the identified moved-down clauses to the caller's
* restrict_clauses list. By using list_concat in this order, we leave
* the original list structure of restrict_clauses undamaged.
*/
*restrict_clauses = list_concat(pclauses, *restrict_clauses);
/* If we already have a PPI for this parameterization, just return it */
foreach(lc, joinrel->ppilist)
{
ppi = (ParamPathInfo *) lfirst(lc);
if (bms_equal(ppi->ppi_req_outer, required_outer))
return ppi;
}
/* Estimate the number of rows returned by the parameterized join */
rows = get_parameterized_joinrel_size(root, joinrel,
outer_path->rows,
inner_path->rows,
sjinfo,
*restrict_clauses);
/*
* And now we can build the ParamPathInfo. No point in saving the
* input-pair-dependent clause list, though.
*
* Note: in GEQO mode, we'll be called in a temporary memory context, but
* the joinrel structure is there too, so no problem.
*/
ppi = makeNode(ParamPathInfo);
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = rows;
ppi->ppi_clauses = NIL;
joinrel->ppilist = lappend(joinrel->ppilist, ppi);
return ppi;
}
/*
* get_appendrel_parampathinfo
* Get the ParamPathInfo for a parameterized path for an append relation.
*
* For an append relation, the rowcount estimate will just be the sum of
* the estimates for its children. However, we still need a ParamPathInfo
* to flag the fact that the path requires parameters. So this just creates
* a suitable struct with zero ppi_rows (and no ppi_clauses either, since
* the Append node isn't responsible for checking quals).
*/
ParamPathInfo *
get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
{
ParamPathInfo *ppi;
ListCell *lc;
/* Unparameterized paths have no ParamPathInfo */
if (bms_is_empty(required_outer))
return NULL;
Assert(!bms_overlap(appendrel->relids, required_outer));
/* If we already have a PPI for this parameterization, just return it */
foreach(lc, appendrel->ppilist)
{
ppi = (ParamPathInfo *) lfirst(lc);
if (bms_equal(ppi->ppi_req_outer, required_outer))
return ppi;
}
/* Else build the ParamPathInfo */
ppi = makeNode(ParamPathInfo);
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = 0;
ppi->ppi_clauses = NIL;
appendrel->ppilist = lappend(appendrel->ppilist, ppi);
return ppi;
}

View File

@ -26,15 +26,15 @@ static RestrictInfo *make_restrictinfo_internal(Expr *clause,
bool outerjoin_delayed,
bool pseudoconstant,
Relids required_relids,
Relids outer_relids,
Relids nullable_relids);
static Expr *make_sub_restrictinfos(Expr *clause,
bool is_pushed_down,
bool outerjoin_delayed,
bool pseudoconstant,
Relids required_relids,
Relids outer_relids,
Relids nullable_relids);
static bool join_clause_is_redundant(RestrictInfo *rinfo,
List *reference_list);
/*
@ -43,9 +43,10 @@ static bool join_clause_is_redundant(RestrictInfo *rinfo,
* Build a RestrictInfo node containing the given subexpression.
*
* The is_pushed_down, outerjoin_delayed, and pseudoconstant flags for the
* RestrictInfo must be supplied by the caller, as well as the correct value
* for nullable_relids. required_relids can be NULL, in which case it
* defaults to the actual clause contents (i.e., clause_relids).
* RestrictInfo must be supplied by the caller, as well as the correct values
* for outer_relids and nullable_relids.
* required_relids can be NULL, in which case it defaults to the actual clause
* contents (i.e., clause_relids).
*
* We initialize fields that depend only on the given subexpression, leaving
* others that depend on context (or may never be needed at all) to be filled
@ -57,6 +58,7 @@ make_restrictinfo(Expr *clause,
bool outerjoin_delayed,
bool pseudoconstant,
Relids required_relids,
Relids outer_relids,
Relids nullable_relids)
{
/*
@ -69,6 +71,7 @@ make_restrictinfo(Expr *clause,
outerjoin_delayed,
pseudoconstant,
required_relids,
outer_relids,
nullable_relids);
/* Shouldn't be an AND clause, else AND/OR flattening messed up */
@ -80,6 +83,7 @@ make_restrictinfo(Expr *clause,
outerjoin_delayed,
pseudoconstant,
required_relids,
outer_relids,
nullable_relids);
}
@ -94,8 +98,8 @@ make_restrictinfo(Expr *clause,
* RestrictInfos.
*
* The caller must pass is_pushed_down, but we assume outerjoin_delayed
* and pseudoconstant are false and nullable_relids is NULL (no other
* kind of qual should ever get into a bitmapqual).
* and pseudoconstant are false while outer_relids and nullable_relids
* are NULL (no other kind of qual should ever get into a bitmapqual).
*
* If include_predicates is true, we add any partial index predicates to
* the explicit index quals. When this is not true, we return a condition
@ -227,6 +231,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual,
false,
false,
NULL,
NULL,
NULL));
}
}
@ -254,6 +259,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual,
false,
false,
NULL,
NULL,
NULL));
}
}
@ -275,8 +281,9 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual,
* representation after doing transformations of a list of clauses.
*
* We assume that the clauses are relation-level restrictions and therefore
* we don't have to worry about is_pushed_down, outerjoin_delayed, or
* nullable_relids (these can be assumed true, false, and NULL, respectively).
* we don't have to worry about is_pushed_down, outerjoin_delayed,
* outer_relids, and nullable_relids (these can be assumed true, false,
* NULL, and NULL, respectively).
* We do take care to recognize pseudoconstant clauses properly.
*/
List *
@ -312,6 +319,7 @@ make_restrictinfos_from_actual_clauses(PlannerInfo *root,
false,
pseudoconstant,
NULL,
NULL,
NULL);
result = lappend(result, rinfo);
}
@ -330,6 +338,7 @@ make_restrictinfo_internal(Expr *clause,
bool outerjoin_delayed,
bool pseudoconstant,
Relids required_relids,
Relids outer_relids,
Relids nullable_relids)
{
RestrictInfo *restrictinfo = makeNode(RestrictInfo);
@ -340,6 +349,7 @@ make_restrictinfo_internal(Expr *clause,
restrictinfo->outerjoin_delayed = outerjoin_delayed;
restrictinfo->pseudoconstant = pseudoconstant;
restrictinfo->can_join = false; /* may get set below */
restrictinfo->outer_relids = outer_relids;
restrictinfo->nullable_relids = nullable_relids;
/*
@ -427,7 +437,7 @@ make_restrictinfo_internal(Expr *clause,
*
* The same is_pushed_down, outerjoin_delayed, and pseudoconstant flag
* values can be applied to all RestrictInfo nodes in the result. Likewise
* for nullable_relids.
* for outer_relids and nullable_relids.
*
* The given required_relids are attached to our top-level output,
* but any OR-clause constituents are allowed to default to just the
@ -439,6 +449,7 @@ make_sub_restrictinfos(Expr *clause,
bool outerjoin_delayed,
bool pseudoconstant,
Relids required_relids,
Relids outer_relids,
Relids nullable_relids)
{
if (or_clause((Node *) clause))
@ -453,6 +464,7 @@ make_sub_restrictinfos(Expr *clause,
outerjoin_delayed,
pseudoconstant,
NULL,
outer_relids,
nullable_relids));
return (Expr *) make_restrictinfo_internal(clause,
make_orclause(orlist),
@ -460,6 +472,7 @@ make_sub_restrictinfos(Expr *clause,
outerjoin_delayed,
pseudoconstant,
required_relids,
outer_relids,
nullable_relids);
}
else if (and_clause((Node *) clause))
@ -474,6 +487,7 @@ make_sub_restrictinfos(Expr *clause,
outerjoin_delayed,
pseudoconstant,
required_relids,
outer_relids,
nullable_relids));
return make_andclause(andlist);
}
@ -484,6 +498,7 @@ make_sub_restrictinfos(Expr *clause,
outerjoin_delayed,
pseudoconstant,
required_relids,
outer_relids,
nullable_relids);
}
@ -620,72 +635,90 @@ extract_actual_join_clauses(List *restrictinfo_list,
/*
* select_nonredundant_join_clauses
* Select the members of restrictinfo_list that are not redundant with
* any member of reference_list.
* join_clause_is_movable_to
* Test whether a join clause is a safe candidate for parameterization
* of a scan on the specified base relation.
*
* Given a list of RestrictInfo clauses that are to be applied in a join,
* select the ones that are not redundant with any clause that's listed in
* reference_list. This is used, for example, to avoid checking joinclauses
* again at a nestloop join when they've already been enforced by a
* parameterized inner path.
* A movable join clause is one that can safely be evaluated at a rel below
* its normal semantic level (ie, its required_relids), if the values of
* variables that it would need from other rels are provided.
*
* "Redundant" means either equal() or derived from the same EquivalenceClass.
* We have to check the latter because indxpath.c may select different derived
* clauses than were selected by generate_join_implied_equalities().
* We insist that the clause actually reference the target relation; this
* prevents undesirable movement of degenerate join clauses, and ensures
* that there is a unique place that a clause can be moved down to.
*
* Note that we are *not* checking for local redundancies within the given
* restrictinfo_list; that should have been handled elsewhere.
* We cannot move an outer-join clause into the non-nullable side of its
* outer join, as that would change the results (rows would be suppressed
* rather than being null-extended).
*
* And the target relation must not be in the clause's nullable_relids, i.e.,
* there must not be an outer join below the clause that would null the Vars
* coming from the target relation. Otherwise the clause might give results
* different from what it would give at its normal semantic level.
*/
List *
select_nonredundant_join_clauses(List *restrictinfo_list,
List *reference_list)
bool
join_clause_is_movable_to(RestrictInfo *rinfo, Index baserelid)
{
List *result = NIL;
ListCell *item;
/* Clause must physically reference target rel */
if (!bms_is_member(baserelid, rinfo->clause_relids))
return false;
/* Quick out if nothing could be removed */
if (reference_list == NIL)
return restrictinfo_list;
/* Cannot move an outer-join clause into the join's outer side */
if (bms_is_member(baserelid, rinfo->outer_relids))
return false;
foreach(item, restrictinfo_list)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(item);
/* Target rel must not be nullable below the clause */
if (bms_is_member(baserelid, rinfo->nullable_relids))
return false;
/* drop it if redundant with any reference clause */
if (join_clause_is_redundant(rinfo, reference_list))
continue;
/* otherwise, add it to result list */
result = lappend(result, rinfo);
}
return result;
return true;
}
/*
* join_clause_is_redundant
* Test whether rinfo is redundant with any clause in reference_list.
* join_clause_is_movable_into
* Test whether a join clause is movable and can be evaluated within
* the current join context.
*
* currentrelids: the relids of the proposed evaluation location
* current_and_outer: the union of currentrelids and the required_outer
* relids (parameterization's outer relations)
*
* The API would be a bit clearer if we passed the current relids and the
* outer relids separately and did bms_union internally; but since most
* callers need to apply this function to multiple clauses, we make the
* caller perform the union.
*
* Obviously, the clause must only refer to Vars available from the current
* relation plus the outer rels. We also check that it does reference at
* least one current Var, ensuring that the clause will be pushed down to
* a unique place in a parameterized join tree. And we check that we're
* not pushing the clause into its outer-join outer side, nor down into
* a lower outer join's inner side.
*
* Note: get_joinrel_parampathinfo depends on the fact that if
* current_and_outer is NULL, this function will always return false
* (since one or the other of the first two tests must fail).
*/
static bool
join_clause_is_redundant(RestrictInfo *rinfo,
List *reference_list)
bool
join_clause_is_movable_into(RestrictInfo *rinfo,
Relids currentrelids,
Relids current_and_outer)
{
ListCell *refitem;
/* Clause must be evaluatable given available context */
if (!bms_is_subset(rinfo->clause_relids, current_and_outer))
return false;
foreach(refitem, reference_list)
{
RestrictInfo *refrinfo = (RestrictInfo *) lfirst(refitem);
/* Clause must physically reference target rel(s) */
if (!bms_overlap(currentrelids, rinfo->clause_relids))
return false;
/* always consider exact duplicates redundant */
if (equal(rinfo, refrinfo))
return true;
/* Cannot move an outer-join clause into the join's outer side */
if (bms_overlap(currentrelids, rinfo->outer_relids))
return false;
/* check if derived from same EquivalenceClass */
if (rinfo->parent_ec != NULL &&
rinfo->parent_ec == refrinfo->parent_ec)
return true;
}
/* Target rel(s) must not be nullable below the clause */
if (bms_overlap(currentrelids, rinfo->nullable_relids))
return false;
return false;
return true;
}

View File

@ -212,6 +212,7 @@ typedef enum NodeTag
T_PlannerGlobal,
T_RelOptInfo,
T_IndexOptInfo,
T_ParamPathInfo,
T_Path,
T_IndexPath,
T_BitmapHeapPath,

View File

@ -304,6 +304,7 @@ typedef struct PlannerInfo
* ConvertRowtypeExpr representing a whole-row Var.
* pathlist - List of Path nodes, one for each potentially useful
* method of generating the relation
* ppilist - ParamPathInfo nodes for parameterized Paths, if any
* cheapest_startup_path - the pathlist member with lowest startup cost
* (regardless of its ordering; but must be
* unparameterized)
@ -400,6 +401,7 @@ typedef struct RelOptInfo
/* materialization information */
List *reltargetlist; /* Vars to be output by scan of relation */
List *pathlist; /* Path structures */
List *ppilist; /* ParamPathInfos used in pathlist */
struct Path *cheapest_startup_path;
struct Path *cheapest_total_path;
struct Path *cheapest_unique_path;
@ -628,6 +630,31 @@ typedef struct PathKey
bool pk_nulls_first; /* do NULLs come before normal values? */
} PathKey;
/*
* ParamPathInfo
*
* All parameterized paths for a given relation with given required outer rels
* link to a single ParamPathInfo, which stores common information such as
* the estimated rowcount for this parameterization. We do this partly to
* avoid recalculations, but mostly to ensure that the estimated rowcount
* is in fact the same for every such path.
*
* Note: ppi_clauses is only used in ParamPathInfos for base relation paths;
* in join cases it's NIL because the set of relevant clauses varies depending
* on how the join is formed. The relevant clauses will appear in each
* parameterized join path's joinrestrictinfo list, instead.
*/
typedef struct ParamPathInfo
{
NodeTag type;
Relids ppi_req_outer; /* rels supplying parameters used by path */
double ppi_rows; /* estimated number of result tuples */
List *ppi_clauses; /* join clauses available from outer rels */
} ParamPathInfo;
/*
* Type "Path" is used as-is for sequential-scan paths, as well as some other
* simple plan types that we don't need any extra information in the path for.
@ -638,25 +665,19 @@ typedef struct PathKey
* the same Path type for multiple Plan types when there is no need to
* distinguish the Plan type during path processing.
*
* "param_info", if not NULL, links to a ParamPathInfo that identifies outer
* relation(s) that provide parameter values to each scan of this path.
* That means this path can only be joined to those rels by means of nestloop
* joins with this path on the inside. Also note that a parameterized path
* is responsible for testing all "movable" joinclauses involving this rel
* and the specified outer rel(s).
*
* "rows" is the same as parent->rows in simple paths, but in parameterized
* paths and UniquePaths it can be less than parent->rows, reflecting the
* fact that we've filtered by extra join conditions or removed duplicates.
*
* "pathkeys" is a List of PathKey nodes (see above), describing the sort
* ordering of the path's output rows.
*
* "required_outer", if not NULL, contains the relids of one or more relations
* that must provide parameter values to each scan of this path, because the
* path relies on join clauses using those rels. That means this path can only
* be joined to those rels by means of nestloop joins with this path on the
* inside. Note: for a normal unparameterized path, required_outer must be
* NULL, not an empty-but-not-null Bitmapset.
*
* "param_clauses" is a List of RestrictInfo nodes, containing the join
* clauses used by a parameterized path. Ideally param_clauses should be NIL
* if and only if required_outer is NULL. XXX for the moment, however, we do
* not compute param_clauses for Append and MergeAppend paths, so the list
* is inaccurate in those paths and possibly paths above them.
*/
typedef struct Path
{
@ -665,6 +686,7 @@ typedef struct Path
NodeTag pathtype; /* tag identifying scan/join method */
RelOptInfo *parent; /* the relation this path can build */
ParamPathInfo *param_info; /* parameterization info, or NULL if none */
/* estimated size/costs for path (see costsize.c for more info) */
double rows; /* estimated number of result tuples */
@ -672,11 +694,13 @@ typedef struct Path
Cost total_cost; /* total cost (assuming all tuples fetched) */
List *pathkeys; /* sort ordering of path's output */
Relids required_outer; /* rels supplying parameters used by path */
List *param_clauses; /* join clauses that use such parameters */
/* pathkeys is a List of PathKey nodes; see above */
} Path;
/* Macro for extracting a path's parameterization relids; beware double eval */
#define PATH_REQ_OUTER(path) \
((path)->param_info ? (path)->param_info->ppi_req_outer : (Relids) NULL)
/*----------
* IndexPath represents an index scan over a single index.
*
@ -924,8 +948,9 @@ typedef struct JoinPath
List *joinrestrictinfo; /* RestrictInfos to apply to join */
/*
* See the notes for RelOptInfo to understand why joinrestrictinfo is
* needed in JoinPath, and can't be merged into the parent RelOptInfo.
* See the notes for RelOptInfo and ParamPathInfo to understand why
* joinrestrictinfo is needed in JoinPath, and can't be merged into the
* parent RelOptInfo.
*/
} JoinPath;
@ -1061,13 +1086,22 @@ typedef struct HashPath
* RestrictInfo nodes also contain an outerjoin_delayed flag, which is true
* if the clause's applicability must be delayed due to any outer joins
* appearing below it (ie, it has to be postponed to some join level higher
* than the set of relations it actually references). There is also a
* nullable_relids field, which is the set of rels it references that can be
* forced null by some outer join below the clause. outerjoin_delayed = true
* is subtly different from nullable_relids != NULL: a clause might reference
* some nullable rels and yet not be outerjoin_delayed because it also
* references all the other rels of the outer join(s). A clause that is not
* outerjoin_delayed can be enforced anywhere it is computable.
* than the set of relations it actually references).
*
* There is also an outer_relids field, which is NULL except for outer join
* clauses; for those, it is the set of relids on the outer side of the
* clause's outer join. (These are rels that the clause cannot be applied to
* in parameterized scans, since pushing it into the join's outer side would
* lead to wrong answers.)
*
* There is also a nullable_relids field, which is the set of rels the clause
* references that can be forced null by some outer join below the clause.
*
* outerjoin_delayed = true is subtly different from nullable_relids != NULL:
* a clause might reference some nullable rels and yet not be
* outerjoin_delayed because it also references all the other rels of the
* outer join(s). A clause that is not outerjoin_delayed can be enforced
* anywhere it is computable.
*
* In general, the referenced clause might be arbitrarily complex. The
* kinds of clauses we can handle as indexscan quals, mergejoin clauses,
@ -1129,6 +1163,9 @@ typedef struct RestrictInfo
/* The set of relids required to evaluate the clause: */
Relids required_relids;
/* If an outer-join clause, the outer-side relations, else NULL: */
Relids outer_relids;
/* The relids used in the clause that are nullable by lower outer joins: */
Relids nullable_relids;

View File

@ -66,17 +66,20 @@ extern int constraint_exclusion;
extern double clamp_row_est(double nrows);
extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
double index_pages, PlannerInfo *root);
extern void cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *baserel);
extern void cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *param_info);
extern void cost_index(IndexPath *path, PlannerInfo *root,
double loop_count);
extern void cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *param_info,
Path *bitmapqual, double loop_count);
extern void cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root);
extern void cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root);
extern void cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec);
extern void cost_tidscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, List *tidquals);
extern void cost_subqueryscan(Path *path, RelOptInfo *baserel);
extern void cost_subqueryscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_functionscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel);
extern void cost_valuesscan(Path *path, PlannerInfo *root,
@ -149,6 +152,15 @@ extern void compute_semi_anti_join_factors(PlannerInfo *root,
List *restrictlist,
SemiAntiJoinFactors *semifactors);
extern void set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern double get_parameterized_baserel_size(PlannerInfo *root,
RelOptInfo *rel,
List *param_clauses);
extern double get_parameterized_joinrel_size(PlannerInfo *root,
RelOptInfo *rel,
double outer_rows,
double inner_rows,
SpecialJoinInfo *sjinfo,
List *restrict_clauses);
extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel,

View File

@ -30,7 +30,8 @@ extern bool add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
List *pathkeys, Relids required_outer);
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel);
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
extern IndexPath *create_index_path(PlannerInfo *root,
IndexOptInfo *index,
List *indexclauses,
@ -45,6 +46,7 @@ extern IndexPath *create_index_path(PlannerInfo *root,
extern BitmapHeapPath *create_bitmap_heap_path(PlannerInfo *root,
RelOptInfo *rel,
Path *bitmapqual,
Relids required_outer,
double loop_count);
extern BitmapAndPath *create_bitmap_and_path(PlannerInfo *root,
RelOptInfo *rel,
@ -54,16 +56,19 @@ extern BitmapOrPath *create_bitmap_or_path(PlannerInfo *root,
List *bitmapquals);
extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
List *tidquals);
extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths);
extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths,
Relids required_outer);
extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
RelOptInfo *rel,
List *subpaths,
List *pathkeys);
List *pathkeys,
Relids required_outer);
extern ResultPath *create_result_path(List *quals);
extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, SpecialJoinInfo *sjinfo);
extern Path *create_subqueryscan_path(RelOptInfo *rel, List *pathkeys);
extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
List *pathkeys, Relids required_outer);
extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel);
extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel);
@ -71,7 +76,7 @@ extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel);
extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
double rows, Cost startup_cost, Cost total_cost,
List *pathkeys,
Relids required_outer, List *param_clauses,
Relids required_outer,
List *fdw_private);
extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path);
@ -115,6 +120,10 @@ extern HashPath *create_hashjoin_path(PlannerInfo *root,
Relids required_outer,
List *hashclauses);
extern Path *reparameterize_path(PlannerInfo *root, Path *path,
Relids required_outer,
double loop_count);
/*
* prototypes for relnode.c
*/
@ -129,5 +138,19 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root,
RelOptInfo *inner_rel,
SpecialJoinInfo *sjinfo,
List **restrictlist_ptr);
extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root,
RelOptInfo *rel);
extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root,
RelOptInfo *baserel,
Relids required_outer);
extern ParamPathInfo *get_joinrel_parampathinfo(PlannerInfo *root,
RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
SpecialJoinInfo *sjinfo,
Relids required_outer,
List **restrict_clauses);
extern ParamPathInfo *get_appendrel_parampathinfo(RelOptInfo *appendrel,
Relids required_outer);
#endif /* PATHNODE_H */

View File

@ -114,8 +114,8 @@ extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
bool create_it);
extern void generate_base_implied_equalities(PlannerInfo *root);
extern List *generate_join_implied_equalities(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
Relids join_relids,
Relids outer_relids,
RelOptInfo *inner_rel);
extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2);
extern void add_child_rel_equivalences(PlannerInfo *root,
@ -134,6 +134,7 @@ extern bool has_relevant_eclass_joinclause(PlannerInfo *root,
RelOptInfo *rel1);
extern bool eclass_useful_for_merging(EquivalenceClass *eclass,
RelOptInfo *rel);
extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
/*
* pathkeys.c

View File

@ -19,13 +19,14 @@
/* Convenience macro for the common case of a valid-everywhere qual */
#define make_simple_restrictinfo(clause) \
make_restrictinfo(clause, true, false, false, NULL, NULL)
make_restrictinfo(clause, true, false, false, NULL, NULL, NULL)
extern RestrictInfo *make_restrictinfo(Expr *clause,
bool is_pushed_down,
bool outerjoin_delayed,
bool pseudoconstant,
Relids required_relids,
Relids outer_relids,
Relids nullable_relids);
extern List *make_restrictinfo_from_bitmapqual(Path *bitmapqual,
bool is_pushed_down,
@ -40,7 +41,9 @@ extern List *extract_actual_clauses(List *restrictinfo_list,
extern void extract_actual_join_clauses(List *restrictinfo_list,
List **joinquals,
List **otherquals);
extern List *select_nonredundant_join_clauses(List *restrictinfo_list,
List *reference_list);
extern bool join_clause_is_movable_to(RestrictInfo *rinfo, Index baserelid);
extern bool join_clause_is_movable_into(RestrictInfo *rinfo,
Relids currentrelids,
Relids current_and_outer);
#endif /* RESTRICTINFO_H */

View File

@ -971,6 +971,76 @@ drop cascades to table inhts
drop cascades to table inht3
drop cascades to table inht4
--
-- Test parameterized append plans for inheritance trees
--
create temp table patest0 (id, x) as
select x, x from generate_series(0,1000) x;
create temp table patest1() inherits (patest0);
insert into patest1
select x, x from generate_series(0,1000) x;
create temp table patest2() inherits (patest0);
insert into patest2
select x, x from generate_series(0,1000) x;
create index patest0i on patest0(id);
create index patest1i on patest1(id);
create index patest2i on patest2(id);
analyze patest0;
analyze patest1;
analyze patest2;
explain (costs off)
select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
QUERY PLAN
----------------------------------------------------------
Nested Loop
-> Limit
-> Seq Scan on int4_tbl
-> Append
-> Index Scan using patest0i on patest0
Index Cond: (id = int4_tbl.f1)
-> Index Scan using patest1i on patest1 patest0
Index Cond: (id = int4_tbl.f1)
-> Index Scan using patest2i on patest2 patest0
Index Cond: (id = int4_tbl.f1)
(10 rows)
select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
id | x | f1
----+---+----
0 | 0 | 0
0 | 0 | 0
0 | 0 | 0
(3 rows)
drop index patest2i;
explain (costs off)
select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
QUERY PLAN
----------------------------------------------------------
Nested Loop
-> Limit
-> Seq Scan on int4_tbl
-> Append
-> Index Scan using patest0i on patest0
Index Cond: (id = int4_tbl.f1)
-> Index Scan using patest1i on patest1 patest0
Index Cond: (id = int4_tbl.f1)
-> Seq Scan on patest2 patest0
Filter: (int4_tbl.f1 = id)
(10 rows)
select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
id | x | f1
----+---+----
0 | 0 | 0
0 | 0 | 0
0 | 0 | 0
(3 rows)
drop table patest0 cascade;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table patest1
drop cascades to table patest2
--
-- Test merge-append plans for inheritance trees
--
create table matest0 (id serial primary key, name text);

View File

@ -2675,12 +2675,11 @@ select * from
int4(sin(1)) q1,
int4(sin(0)) q2
where q1 = thousand or q2 = thousand;
QUERY PLAN
-----------------------------------------------------------------------------
QUERY PLAN
------------------------------------------------------------------------
Hash Join
Hash Cond: (tenk1.twothousand = int4_tbl.f1)
-> Nested Loop
Join Filter: ((q1.q1 = tenk1.thousand) OR (q2.q2 = tenk1.thousand))
-> Nested Loop
-> Function Scan on q1
-> Function Scan on q2
@ -2693,7 +2692,7 @@ where q1 = thousand or q2 = thousand;
Index Cond: (q2.q2 = thousand)
-> Hash
-> Seq Scan on int4_tbl
(16 rows)
(15 rows)
explain (costs off)
select * from
@ -2717,6 +2716,116 @@ where thousand = (q1 + q2);
-> Seq Scan on int4_tbl
(12 rows)
--
-- test placement of movable quals in a parameterized join tree
--
explain (costs off)
select * from tenk1 t1 left join
(tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)
on t1.hundred = t2.hundred and t1.ten = t3.ten
where t1.unique1 = 1;
QUERY PLAN
--------------------------------------------------------
Nested Loop Left Join
-> Index Scan using tenk1_unique1 on tenk1 t1
Index Cond: (unique1 = 1)
-> Nested Loop
Join Filter: (t1.ten = t3.ten)
-> Index Scan using tenk1_hundred on tenk1 t2
Index Cond: (t1.hundred = hundred)
-> Index Scan using tenk1_unique2 on tenk1 t3
Index Cond: (unique2 = t2.thousand)
(9 rows)
explain (costs off)
select * from tenk1 t1 left join
(tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)
on t1.hundred = t2.hundred and t1.ten + t2.ten = t3.ten
where t1.unique1 = 1;
QUERY PLAN
--------------------------------------------------------
Nested Loop Left Join
-> Index Scan using tenk1_unique1 on tenk1 t1
Index Cond: (unique1 = 1)
-> Nested Loop
Join Filter: ((t1.ten + t2.ten) = t3.ten)
-> Index Scan using tenk1_hundred on tenk1 t2
Index Cond: (t1.hundred = hundred)
-> Index Scan using tenk1_unique2 on tenk1 t3
Index Cond: (unique2 = t2.thousand)
(9 rows)
explain (costs off)
select count(*) from
tenk1 a join tenk1 b on a.unique1 = b.unique2
left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand
join int4_tbl on b.thousand = f1;
QUERY PLAN
--------------------------------------------------------------------------
Aggregate
-> Nested Loop Left Join
Join Filter: (a.unique2 = b.unique1)
-> Nested Loop
-> Nested Loop
-> Seq Scan on int4_tbl
-> Index Scan using tenk1_thous_tenthous on tenk1 b
Index Cond: (thousand = int4_tbl.f1)
-> Index Scan using tenk1_unique1 on tenk1 a
Index Cond: (unique1 = b.unique2)
-> Index Only Scan using tenk1_thous_tenthous on tenk1 c
Index Cond: (thousand = a.thousand)
(12 rows)
select count(*) from
tenk1 a join tenk1 b on a.unique1 = b.unique2
left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand
join int4_tbl on b.thousand = f1;
count
-------
10
(1 row)
explain (costs off)
select b.unique1 from
tenk1 a join tenk1 b on a.unique1 = b.unique2
left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand
join int4_tbl i1 on b.thousand = f1
right join int4_tbl i2 on i2.f1 = b.tenthous
order by 1;
QUERY PLAN
-----------------------------------------------------------------------------------------
Sort
Sort Key: b.unique1
-> Nested Loop Left Join
-> Seq Scan on int4_tbl i2
-> Nested Loop Left Join
Join Filter: (b.unique1 = 42)
-> Nested Loop
-> Nested Loop
-> Seq Scan on int4_tbl i1
-> Index Scan using tenk1_thous_tenthous on tenk1 b
Index Cond: ((thousand = i1.f1) AND (i2.f1 = tenthous))
-> Index Scan using tenk1_unique1 on tenk1 a
Index Cond: (unique1 = b.unique2)
-> Index Only Scan using tenk1_thous_tenthous on tenk1 c
Index Cond: (thousand = a.thousand)
(15 rows)
select b.unique1 from
tenk1 a join tenk1 b on a.unique1 = b.unique2
left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand
join int4_tbl i1 on b.thousand = f1
right join int4_tbl i2 on i2.f1 = b.tenthous
order by 1;
unique1
---------
0
(5 rows)
--
-- test join removal
--

View File

@ -291,6 +291,37 @@ SELECT a.attrelid::regclass, a.attname, a.attinhcount, e.expected
DROP TABLE inht1, inhs1 CASCADE;
--
-- Test parameterized append plans for inheritance trees
--
create temp table patest0 (id, x) as
select x, x from generate_series(0,1000) x;
create temp table patest1() inherits (patest0);
insert into patest1
select x, x from generate_series(0,1000) x;
create temp table patest2() inherits (patest0);
insert into patest2
select x, x from generate_series(0,1000) x;
create index patest0i on patest0(id);
create index patest1i on patest1(id);
create index patest2i on patest2(id);
analyze patest0;
analyze patest1;
analyze patest2;
explain (costs off)
select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
drop index patest2i;
explain (costs off)
select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
drop table patest0 cascade;
--
-- Test merge-append plans for inheritance trees
--

View File

@ -707,6 +707,48 @@ select * from
int4(sin(0)) q2
where thousand = (q1 + q2);
--
-- test placement of movable quals in a parameterized join tree
--
explain (costs off)
select * from tenk1 t1 left join
(tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)
on t1.hundred = t2.hundred and t1.ten = t3.ten
where t1.unique1 = 1;
explain (costs off)
select * from tenk1 t1 left join
(tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2)
on t1.hundred = t2.hundred and t1.ten + t2.ten = t3.ten
where t1.unique1 = 1;
explain (costs off)
select count(*) from
tenk1 a join tenk1 b on a.unique1 = b.unique2
left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand
join int4_tbl on b.thousand = f1;
select count(*) from
tenk1 a join tenk1 b on a.unique1 = b.unique2
left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand
join int4_tbl on b.thousand = f1;
explain (costs off)
select b.unique1 from
tenk1 a join tenk1 b on a.unique1 = b.unique2
left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand
join int4_tbl i1 on b.thousand = f1
right join int4_tbl i2 on i2.f1 = b.tenthous
order by 1;
select b.unique1 from
tenk1 a join tenk1 b on a.unique1 = b.unique2
left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand
join int4_tbl i1 on b.thousand = f1
right join int4_tbl i2 on i2.f1 = b.tenthous
order by 1;
--
-- test join removal
--