diff options
| author | Tom Lane | 2015-07-26 20:19:08 +0000 |
|---|---|---|
| committer | Pavan Deolasee | 2016-10-18 12:53:07 +0000 |
| commit | 85b492b8e1cc0a668f3abcd33ab51df8f5e14bbb (patch) | |
| tree | 9288340a2e352e292d8b8d30787b7407f744be08 /src | |
| parent | e532cd2ba2179b0846f3f7c5462711afc31858b1 (diff) | |
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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/backend/optimizer/path/allpaths.c | 104 | ||||
| -rw-r--r-- | src/test/regress/expected/join.out | 54 | ||||
| -rw-r--r-- | src/test/regress/sql/join.sql | 28 |
3 files changed, 63 insertions, 123 deletions
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 3b7940c5ce..2ca91839d9 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -366,6 +366,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, break; } } + + /* + * We insist that all non-dummy rels have a nonzero rowcount estimate. + */ + Assert(rel->rows > 0 || IS_DUMMY_REL(rel)); } /* @@ -586,6 +591,9 @@ set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Let FDW adjust the size estimates, if it can */ rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid); + + /* ... but do not let it set the rows estimate to zero */ + rel->rows = clamp_row_est(rel->rows); } /* @@ -615,6 +623,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { int parentRTindex = rti; + bool has_live_children; double parent_rows; double parent_size; double *parent_attrsizes; @@ -635,6 +644,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * Note: if you consider changing this logic, beware that child rels could * have zero rows and/or width, if they were excluded by constraints. */ + has_live_children = false; parent_rows = 0; parent_size = 0; nattrs = rel->max_attr - rel->min_attr + 1; @@ -762,70 +772,80 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, if (IS_DUMMY_REL(childrel)) continue; + /* We have at least one live child. */ + has_live_children = true; + /* * Accumulate size information from each live child. */ - if (childrel->rows > 0) + Assert(childrel->rows > 0); + + parent_rows += childrel->rows; + parent_size += childrel->width * childrel->rows; + + /* + * Accumulate per-column estimates too. We need not do anything for + * PlaceHolderVars in the parent list. If child expression isn't a + * Var, or we didn't record a width estimate for it, we have to fall + * back on a datatype-based estimate. + * + * By construction, child's reltargetlist is 1-to-1 with parent's. + */ + forboth(parentvars, rel->reltargetlist, + childvars, childrel->reltargetlist) { - parent_rows += childrel->rows; - parent_size += childrel->width * childrel->rows; + Var *parentvar = (Var *) lfirst(parentvars); + Node *childvar = (Node *) lfirst(childvars); - /* - * Accumulate per-column estimates too. We need not do anything - * for PlaceHolderVars in the parent list. If child expression - * isn't a Var, or we didn't record a width estimate for it, we - * have to fall back on a datatype-based estimate. - * - * By construction, child's reltargetlist is 1-to-1 with parent's. - */ - forboth(parentvars, rel->reltargetlist, - childvars, childrel->reltargetlist) + if (IsA(parentvar, Var)) { - Var *parentvar = (Var *) lfirst(parentvars); - Node *childvar = (Node *) lfirst(childvars); + int pndx = parentvar->varattno - rel->min_attr; + int32 child_width = 0; - if (IsA(parentvar, Var)) + if (IsA(childvar, Var) && + ((Var *) childvar)->varno == childrel->relid) { - int pndx = parentvar->varattno - rel->min_attr; - int32 child_width = 0; + int cndx = ((Var *) childvar)->varattno - childrel->min_attr; - if (IsA(childvar, Var) && - ((Var *) childvar)->varno == childrel->relid) - { - int cndx = ((Var *) childvar)->varattno - childrel->min_attr; - - child_width = childrel->attr_widths[cndx]; - } - if (child_width <= 0) - child_width = get_typavgwidth(exprType(childvar), - exprTypmod(childvar)); - Assert(child_width > 0); - parent_attrsizes[pndx] += child_width * childrel->rows; + child_width = childrel->attr_widths[cndx]; } + if (child_width <= 0) + child_width = get_typavgwidth(exprType(childvar), + exprTypmod(childvar)); + Assert(child_width > 0); + parent_attrsizes[pndx] += child_width * childrel->rows; } } } - /* - * Save the finished size estimates. - */ - rel->rows = parent_rows; - if (parent_rows > 0) + if (has_live_children) { + /* + * Save the finished size estimates. + */ int i; + Assert(parent_rows > 0); + rel->rows = parent_rows; rel->width = rint(parent_size / parent_rows); for (i = 0; i < nattrs; i++) rel->attr_widths[i] = rint(parent_attrsizes[i] / parent_rows); + + /* + * Set "raw tuples" count equal to "rows" for the appendrel; needed + * because some places assume rel->tuples is valid for any baserel. + */ + rel->tuples = parent_rows; } else - rel->width = 0; /* attr_widths should be zero already */ - - /* - * Set "raw tuples" count equal to "rows" for the appendrel; needed - * because some places assume rel->tuples is valid for any baserel. - */ - rel->tuples = parent_rows; + { + /* + * All children were excluded by constraints, so mark the whole + * appendrel dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } pfree(parent_attrsizes); } diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index ad614eb119..6ab9544e28 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -2221,60 +2221,6 @@ select aa, bb, unique1, unique1 (0 rows) -- --- regression test: check handling of empty-FROM subquery underneath outer join --- -explain (costs off, nodes off) -select * from int8_tbl i1 left join (int8_tbl i2 join - (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 -order by 1, 2; - QUERY PLAN -------------------------------------------------------- - Sort - Sort Key: i1.q1, i1.q2 - -> Hash Left Join - Hash Cond: (i1.q2 = i2.q2) - -> Remote Subquery Scan on all - -> Seq Scan on int8_tbl i1 - -> Hash - -> Hash Join - Hash Cond: (i2.q1 = (123)) - -> Remote Subquery Scan on all - -> Seq Scan on int8_tbl i2 - -> Hash - -> Result -(13 rows) - -select * from int8_tbl i1 left join (int8_tbl i2 join - (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 -order by 1, 2; - q1 | q2 | q1 | q2 | x -------------------+-------------------+-----+------------------+----- - 123 | 456 | 123 | 456 | 123 - 123 | 4567890123456789 | 123 | 4567890123456789 | 123 - 4567890123456789 | -4567890123456789 | | | - 4567890123456789 | 123 | | | - 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 123 -(5 rows) - --- --- regression test: check a case where join_clause_is_movable_into() gives --- an imprecise result, causing an assertion failure --- -select count(*) -from - (select t3.tenthous as x1, coalesce(t1.stringu1, t2.stringu1) as x2 - from tenk1 t1 - left join tenk1 t2 on t1.unique1 = t2.unique1 - join tenk1 t3 on t1.unique2 = t3.unique2) ss, - tenk1 t4, - tenk1 t5 -where t4.thousand = t5.unique1 and ss.x1 = t4.tenthous and ss.x2 = t5.stringu1; - count -------- - 1000 -(1 row) - --- -- Clean up -- DROP TABLE t1; diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 287dbba56c..1fc794d15c 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -402,33 +402,6 @@ select aa, bb, unique1, unique1 where bb < bb and bb is null; -- --- regression test: check handling of empty-FROM subquery underneath outer join --- -explain (costs off, nodes off) -select * from int8_tbl i1 left join (int8_tbl i2 join - (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 -order by 1, 2; - -select * from int8_tbl i1 left join (int8_tbl i2 join - (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 -order by 1, 2; - --- --- regression test: check a case where join_clause_is_movable_into() gives --- an imprecise result, causing an assertion failure --- -select count(*) -from - (select t3.tenthous as x1, coalesce(t1.stringu1, t2.stringu1) as x2 - from tenk1 t1 - left join tenk1 t2 on t1.unique1 = t2.unique1 - join tenk1 t3 on t1.unique2 = t3.unique2) ss, - tenk1 t4, - tenk1 t5 -where t4.thousand = t5.unique1 and ss.x1 = t4.tenthous and ss.x2 = t5.stringu1; - - --- -- Clean up -- @@ -1249,6 +1222,7 @@ select atts.relid::regclass, s.* from pg_stats s join a.attrelid::regclass::text join (select unnest(indkey) attnum, indexrelid from pg_index i) atts on atts.attnum = a.attnum where schemaname != 'pg_catalog'; + -- -- Test LATERAL -- |
