Commit 5b7b5518 authored by Tom Lane's avatar Tom Lane

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.
parent cd1f4db4
......@@ -468,7 +468,6 @@ fileGetForeignPaths(PlannerInfo *root,
total_cost,
NIL, /* no pathkeys */
NULL, /* no outer rel either */
NIL,
NIL)); /* no fdw_private data */
/*
......
......@@ -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);
......
......@@ -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);
/*
......
......@@ -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;
......
......@@ -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
......@@ -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);
......
This diff is collapsed.
This diff is collapsed.
......@@ -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 */
......
......@@ -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;
......
......@@ -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);
......
......@@ -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
......
......@@ -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;
......
This diff is collapsed.
......@@ -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 */
......
......@@ -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);
......
......@@ -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);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -212,6 +212,7 @@ typedef enum NodeTag
T_PlannerGlobal,
T_RelOptInfo,
T_IndexOptInfo,
T_ParamPathInfo,
T_Path,
T_IndexPath,
T_BitmapHeapPath,
......
......@@ -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;
......
......@@ -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,
......
......@@ -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 */
......@@ -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
......
......@@ -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 */
......@@ -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);
......
......@@ -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
--
......
......@@ -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
--
......
......@@ -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
--
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment