Avoid combinatorial explosion in add_child_rel_equivalences().

If an EquivalenceClass member expression includes variables from
multiple appendrels, then instead of producing one substituted
expression per child relation as intended, we'd create additional
child expressions for combinations of children of different appendrels.
This happened because the child expressions generated while considering
the first appendrel were taken as sources during substitution of the
second appendrel, and so on.  The extra expressions are useless, and are
harmless unless there are too many of them --- but if you have several
appendrels with a thousand or so members each, it gets bad fast.

To fix, consider only original (non-em_is_child) EC members as candidates
to be expanded.  This requires the ability to substitute directly from a
top parent relation's Vars to those of an indirect descendant relation,
but we already have that in adjust_appendrel_attrs_multilevel().

Per bug #15847 from Feike Steenbergen.  This is a longstanding misbehavior,
but it's only worth worrying about when there are more appendrel children
than we've historically considered wise to use.  So I'm not going to take
the risk of back-patching this.

Discussion: https://postgr.es/m/15847-ea3734094bf8ae61@postgresql.org
This commit is contained in:
Tom Lane 2019-06-13 18:10:08 -04:00
parent de87a084c0
commit d25ea01275
1 changed files with 36 additions and 13 deletions

View File

@ -2135,11 +2135,10 @@ add_child_rel_equivalences(PlannerInfo *root,
continue;
/*
* No point in searching if parent rel not mentioned in eclass; but we
* can't tell that for sure if parent rel is itself a child.
* No point in searching if child's topmost parent rel is not
* mentioned in eclass.
*/
if (parent_rel->reloptkind == RELOPT_BASEREL &&
!bms_is_subset(parent_rel->relids, cur_ec->ec_relids))
if (!bms_is_subset(child_rel->top_parent_relids, cur_ec->ec_relids))
continue;
foreach(lc2, cur_ec->ec_members)
@ -2149,18 +2148,41 @@ add_child_rel_equivalences(PlannerInfo *root,
if (cur_em->em_is_const)
continue; /* ignore consts here */
/* Does it reference parent_rel? */
if (bms_overlap(cur_em->em_relids, parent_rel->relids))
/*
* We consider only original EC members here, not
* already-transformed child members. Otherwise, if some original
* member expression references more than one appendrel, we'd get
* an O(N^2) explosion of useless derived expressions for
* combinations of children.
*/
if (cur_em->em_is_child)
continue; /* ignore children here */
/* Does this member reference child's topmost parent rel? */
if (bms_overlap(cur_em->em_relids, child_rel->top_parent_relids))
{
/* Yes, generate transformed child version */
Expr *child_expr;
Relids new_relids;
Relids new_nullable_relids;
child_expr = (Expr *)
adjust_appendrel_attrs(root,
(Node *) cur_em->em_expr,
1, &appinfo);
if (parent_rel->reloptkind == RELOPT_BASEREL)
{
/* Simple single-level transformation */
child_expr = (Expr *)
adjust_appendrel_attrs(root,
(Node *) cur_em->em_expr,
1, &appinfo);
}
else
{
/* Must do multi-level transformation */
child_expr = (Expr *)
adjust_appendrel_attrs_multilevel(root,
(Node *) cur_em->em_expr,
child_rel->relids,
child_rel->top_parent_relids);
}
/*
* Transform em_relids to match. Note we do *not* do
@ -2169,7 +2191,7 @@ add_child_rel_equivalences(PlannerInfo *root,
* don't want the child member to be marked as constant.
*/
new_relids = bms_difference(cur_em->em_relids,
parent_rel->relids);
child_rel->top_parent_relids);
new_relids = bms_add_members(new_relids, child_rel->relids);
/*
@ -2177,10 +2199,11 @@ add_child_rel_equivalences(PlannerInfo *root,
* parent and child relids are singletons.
*/
new_nullable_relids = cur_em->em_nullable_relids;
if (bms_overlap(new_nullable_relids, parent_rel->relids))
if (bms_overlap(new_nullable_relids,
child_rel->top_parent_relids))
{
new_nullable_relids = bms_difference(new_nullable_relids,
parent_rel->relids);
child_rel->top_parent_relids);
new_nullable_relids = bms_add_members(new_nullable_relids,
child_rel->relids);
}