提交 5b7b5518 编写于 作者: T 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.
上级 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);
......
......@@ -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;
}
......@@ -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;
......
......@@ -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.
......
......@@ -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);
......
......@@ -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;
}
......@@ -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.
*
* 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.
*
* "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().
*
* Note that we are *not* checking for local redundancies within the given
* restrictinfo_list; that should have been handled elsewhere.
* join_clause_is_movable_to
* Test whether a join clause is a safe candidate for parameterization
* of a scan on the specified base relation.
*
* 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.
*
* 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.
*
* 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;
/* Quick out if nothing could be removed */
if (reference_list == NIL)
return restrictinfo_list;
foreach(item, restrictinfo_list)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(item);
/* Clause must physically reference target rel */
if (!bms_is_member(baserelid, rinfo->clause_relids))
return false;
/* drop it if redundant with any reference clause */
if (join_clause_is_redundant(rinfo, reference_list))
continue;
/* Cannot move an outer-join clause into the join's outer side */
if (bms_is_member(baserelid, rinfo->outer_relids))
return false;
/* otherwise, add it to result list */
result = lappend(result, rinfo);
}
/* Target rel must not be nullable below the clause */
if (bms_is_member(baserelid, rinfo->nullable_relids))
return false;
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;
}
......@@ -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% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册