mirror of
https://github.com/postgres/postgres.git
synced 2025-11-19 13:42:17 +03:00
Revise parameterized-path mechanism to fix assorted issues.
This patch adjusts the treatment of parameterized paths so that all paths with the same parameterization (same set of required outer rels) for the same relation will have the same rowcount estimate. We cache the rowcount estimates to ensure that property, and hopefully save a few cycles too. Doing this makes it practical for add_path_precheck to operate without a rowcount estimate: it need only assume that paths with different parameterizations never dominate each other, which is close enough to true anyway for coarse filtering, because normally a more-parameterized path should yield fewer rows thanks to having more join clauses to apply. In add_path, we do the full nine yards of comparing rowcount estimates along with everything else, so that we can discard parameterized paths that don't actually have an advantage. This fixes some issues I'd found with add_path rejecting parameterized paths on the grounds that they were more expensive than not-parameterized ones, even though they yielded many fewer rows and hence would be cheaper once subsequent joining was considered. To make the same-rowcounts assumption valid, we have to require that any parameterized path enforce *all* join clauses that could be obtained from the particular set of outer rels, even if not all of them are useful for indexing. This is required at both base scans and joins. It's a good thing anyway since the net impact is that join quals are checked at the lowest practical level in the join tree. Hence, discard the original rather ad-hoc mechanism for choosing parameterization joinquals, and build a better one that has a more principled rule for when clauses can be moved. The original rule was actually buggy anyway for lack of knowledge about which relations are part of an outer join's outer side; getting this right requires adding an outer_relids field to RestrictInfo.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user