Skip to content

Commit 491c24f

Browse files
committed
Make entirely-dummy appendrels get marked as such in set_append_rel_size.
The planner generally expects that the estimated rowcount of any relation is at least one row, *unless* it has been proven empty by constraint exclusion or similar mechanisms, which is marked by installing a dummy path as the rel's cheapest path (cf. IS_DUMMY_REL). When I split up allpaths.c's processing of base rels into separate set_base_rel_sizes and set_base_rel_pathlists steps, the intention was that dummy rels would get marked as such during the "set size" step; this is what justifies an Assert in indxpath.c's get_loop_count that other relations should either be dummy or have positive rowcount. Unfortunately I didn't get that quite right for append relations: if all the child rels have been proven empty then set_append_rel_size would come up with a rowcount of zero, which is correct, but it didn't then do set_dummy_rel_pathlist. (We would have ended up with the right state after set_append_rel_pathlist, but that's too late, if we generate indexpaths for some other rel first.) In addition to fixing the actual bug, I installed an Assert enforcing this convention in set_rel_size; that then allows simplification of a couple of now-redundant tests for zero rowcount in set_append_rel_size. Also, to cover the possibility that third-party FDWs have been careless about not returning a zero rowcount estimate, apply clamp_row_est to whatever an FDW comes up with as the rows estimate. Per report from Andreas Seltenreich. Back-patch to 9.2. Earlier branches did not have the separation between set_base_rel_sizes and set_base_rel_pathlists steps, so there was no intermediate state where an appendrel would have had inconsistent rowcount and pathlist. It's possible that adding the Assert to set_rel_size would be a good idea in older branches too; but since they're not under development any more, it's likely not worth the trouble.
1 parent 41ed5bb commit 491c24f

File tree

3 files changed

+93
-42
lines changed

3 files changed

+93
-42
lines changed

src/backend/optimizer/path/allpaths.c

+62-42
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
343343
break;
344344
}
345345
}
346+
347+
/*
348+
* We insist that all non-dummy rels have a nonzero rowcount estimate.
349+
*/
350+
Assert(rel->rows > 0 || IS_DUMMY_REL(rel));
346351
}
347352

348353
/*
@@ -461,6 +466,9 @@ set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
461466

462467
/* Let FDW adjust the size estimates, if it can */
463468
rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid);
469+
470+
/* ... but do not let it set the rows estimate to zero */
471+
rel->rows = clamp_row_est(rel->rows);
464472
}
465473

466474
/*
@@ -493,6 +501,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
493501
Index rti, RangeTblEntry *rte)
494502
{
495503
int parentRTindex = rti;
504+
bool has_live_children;
496505
double parent_rows;
497506
double parent_size;
498507
double *parent_attrsizes;
@@ -513,6 +522,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
513522
* Note: if you consider changing this logic, beware that child rels could
514523
* have zero rows and/or width, if they were excluded by constraints.
515524
*/
525+
has_live_children = false;
516526
parent_rows = 0;
517527
parent_size = 0;
518528
nattrs = rel->max_attr - rel->min_attr + 1;
@@ -640,70 +650,80 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
640650
if (IS_DUMMY_REL(childrel))
641651
continue;
642652

653+
/* We have at least one live child. */
654+
has_live_children = true;
655+
643656
/*
644657
* Accumulate size information from each live child.
645658
*/
646-
if (childrel->rows > 0)
659+
Assert(childrel->rows > 0);
660+
661+
parent_rows += childrel->rows;
662+
parent_size += childrel->width * childrel->rows;
663+
664+
/*
665+
* Accumulate per-column estimates too. We need not do anything for
666+
* PlaceHolderVars in the parent list. If child expression isn't a
667+
* Var, or we didn't record a width estimate for it, we have to fall
668+
* back on a datatype-based estimate.
669+
*
670+
* By construction, child's reltargetlist is 1-to-1 with parent's.
671+
*/
672+
forboth(parentvars, rel->reltargetlist,
673+
childvars, childrel->reltargetlist)
647674
{
648-
parent_rows += childrel->rows;
649-
parent_size += childrel->width * childrel->rows;
675+
Var *parentvar = (Var *) lfirst(parentvars);
676+
Node *childvar = (Node *) lfirst(childvars);
650677

651-
/*
652-
* Accumulate per-column estimates too. We need not do anything
653-
* for PlaceHolderVars in the parent list. If child expression
654-
* isn't a Var, or we didn't record a width estimate for it, we
655-
* have to fall back on a datatype-based estimate.
656-
*
657-
* By construction, child's reltargetlist is 1-to-1 with parent's.
658-
*/
659-
forboth(parentvars, rel->reltargetlist,
660-
childvars, childrel->reltargetlist)
678+
if (IsA(parentvar, Var))
661679
{
662-
Var *parentvar = (Var *) lfirst(parentvars);
663-
Node *childvar = (Node *) lfirst(childvars);
680+
int pndx = parentvar->varattno - rel->min_attr;
681+
int32 child_width = 0;
664682

665-
if (IsA(parentvar, Var))
683+
if (IsA(childvar, Var) &&
684+
((Var *) childvar)->varno == childrel->relid)
666685
{
667-
int pndx = parentvar->varattno - rel->min_attr;
668-
int32 child_width = 0;
686+
int cndx = ((Var *) childvar)->varattno - childrel->min_attr;
669687

670-
if (IsA(childvar, Var) &&
671-
((Var *) childvar)->varno == childrel->relid)
672-
{
673-
int cndx = ((Var *) childvar)->varattno - childrel->min_attr;
674-
675-
child_width = childrel->attr_widths[cndx];
676-
}
677-
if (child_width <= 0)
678-
child_width = get_typavgwidth(exprType(childvar),
679-
exprTypmod(childvar));
680-
Assert(child_width > 0);
681-
parent_attrsizes[pndx] += child_width * childrel->rows;
688+
child_width = childrel->attr_widths[cndx];
682689
}
690+
if (child_width <= 0)
691+
child_width = get_typavgwidth(exprType(childvar),
692+
exprTypmod(childvar));
693+
Assert(child_width > 0);
694+
parent_attrsizes[pndx] += child_width * childrel->rows;
683695
}
684696
}
685697
}
686698

687-
/*
688-
* Save the finished size estimates.
689-
*/
690-
rel->rows = parent_rows;
691-
if (parent_rows > 0)
699+
if (has_live_children)
692700
{
701+
/*
702+
* Save the finished size estimates.
703+
*/
693704
int i;
694705

706+
Assert(parent_rows > 0);
707+
rel->rows = parent_rows;
695708
rel->width = rint(parent_size / parent_rows);
696709
for (i = 0; i < nattrs; i++)
697710
rel->attr_widths[i] = rint(parent_attrsizes[i] / parent_rows);
711+
712+
/*
713+
* Set "raw tuples" count equal to "rows" for the appendrel; needed
714+
* because some places assume rel->tuples is valid for any baserel.
715+
*/
716+
rel->tuples = parent_rows;
698717
}
699718
else
700-
rel->width = 0; /* attr_widths should be zero already */
701-
702-
/*
703-
* Set "raw tuples" count equal to "rows" for the appendrel; needed
704-
* because some places assume rel->tuples is valid for any baserel.
705-
*/
706-
rel->tuples = parent_rows;
719+
{
720+
/*
721+
* All children were excluded by constraints, so mark the whole
722+
* appendrel dummy. We must do this in this phase so that the rel's
723+
* dummy-ness is visible when we generate paths for other rels.
724+
*/
725+
set_dummy_rel_pathlist(rel);
726+
}
707727

708728
pfree(parent_attrsizes);
709729
}

src/test/regress/expected/join.out

+20
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,26 @@ select count(*) from tenk1 x where
21642164
(1 row)
21652165

21662166
rollback;
2167+
--
2168+
-- regression test: be sure we cope with proven-dummy append rels
2169+
--
2170+
explain (costs off)
2171+
select aa, bb, unique1, unique1
2172+
from tenk1 right join b on aa = unique1
2173+
where bb < bb and bb is null;
2174+
QUERY PLAN
2175+
--------------------------
2176+
Result
2177+
One-Time Filter: false
2178+
(2 rows)
2179+
2180+
select aa, bb, unique1, unique1
2181+
from tenk1 right join b on aa = unique1
2182+
where bb < bb and bb is null;
2183+
aa | bb | unique1 | unique1
2184+
----+----+---------+---------
2185+
(0 rows)
2186+
21672187
--
21682188
-- Clean up
21692189
--

src/test/regress/sql/join.sql

+11
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,17 @@ select count(*) from tenk1 x where
353353
x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1);
354354
rollback;
355355

356+
--
357+
-- regression test: be sure we cope with proven-dummy append rels
358+
--
359+
explain (costs off)
360+
select aa, bb, unique1, unique1
361+
from tenk1 right join b on aa = unique1
362+
where bb < bb and bb is null;
363+
364+
select aa, bb, unique1, unique1
365+
from tenk1 right join b on aa = unique1
366+
where bb < bb and bb is null;
356367

357368
--
358369
-- Clean up

0 commit comments

Comments
 (0)