1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-07 19:06:32 +03:00

Fix assertion failure in generate_orderedappend_paths()

In generate_orderedappend_paths(), there is an assumption that a child
relation's row estimate is always greater than zero.  There is an
Assert verifying this assumption, and the estimate is also used to
convert an absolute tuple count into a fraction.

However, this assumption is not always valid -- for example, upper
relations can have their row estimates unset, resulting in a value of
zero.  This can cause an assertion failure in debug builds or lead to
the tuple fraction being computed as infinity in production builds.

To fix, use the row estimate from the cheapest_total path to compute
the tuple fraction.  The row estimate in this path should already have
been forced to a valid value.

In passing, update the comment for generate_orderedappend_paths() to
note that the function also considers the cheapest-fractional case
when not all tuples need to be retrieved.  That is, it collects all
the cheapest fractional paths and builds an ordered append path for
each interesting ordering.

Backpatch to v18, where this issue was introduced.

Bug: #19102
Reported-by: Kuntal Ghosh <kuntalghosh.2007@gmail.com>
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Kuntal Ghosh <kuntalghosh.2007@gmail.com>
Reviewed-by: Andrei Lepikhov <lepihov@gmail.com>
Discussion: https://postgr.es/m/19102-93480667e1200169@postgresql.org
Backpatch-through: 18
This commit is contained in:
Richard Guo
2025-11-05 18:09:21 +09:00
parent a4fd971c6f
commit c1777f2d6d
3 changed files with 49 additions and 7 deletions

View File

@@ -1810,9 +1810,11 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
* We generate a path for each ordering (pathkey list) appearing in * We generate a path for each ordering (pathkey list) appearing in
* all_child_pathkeys. * all_child_pathkeys.
* *
* We consider both cheapest-startup and cheapest-total cases, ie, for each * We consider the cheapest-startup and cheapest-total cases, and also the
* interesting ordering, collect all the cheapest startup subpaths and all the * cheapest-fractional case when not all tuples need to be retrieved. For each
* cheapest total paths, and build a suitable path for each case. * interesting ordering, we collect all the cheapest startup subpaths, all the
* cheapest total paths, and, if applicable, all the cheapest fractional paths,
* and build a suitable path for each case.
* *
* We don't currently generate any parameterized ordered paths here. While * We don't currently generate any parameterized ordered paths here. While
* it would not take much more code here to do so, it's very unclear that it * it would not take much more code here to do so, it's very unclear that it
@@ -1977,14 +1979,18 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
double path_fraction = root->tuple_fraction; double path_fraction = root->tuple_fraction;
/* /*
* Merge Append considers only live children relations. Dummy * We should not have a dummy child relation here. However,
* relations must be filtered out before. * we cannot use childrel->rows to compute the tuple fraction,
* as childrel can be an upper relation with an unset row
* estimate. Instead, we use the row estimate from the
* cheapest_total path, which should already have been forced
* to a sane value.
*/ */
Assert(childrel->rows > 0); Assert(cheapest_total->rows > 0);
/* Convert absolute limit to a path fraction */ /* Convert absolute limit to a path fraction */
if (path_fraction >= 1.0) if (path_fraction >= 1.0)
path_fraction /= childrel->rows; path_fraction /= cheapest_total->rows;
cheapest_fractional = cheapest_fractional =
get_cheapest_fractional_path_for_pathkeys(childrel->pathlist, get_cheapest_fractional_path_for_pathkeys(childrel->pathlist,

View File

@@ -339,6 +339,37 @@ SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1;
2 2
(3 rows) (3 rows)
-- Test partitionwise aggregation with ordered append path built from fractional paths
EXPLAIN (COSTS OFF)
SELECT count(*) FROM pagg_tab GROUP BY c ORDER BY c LIMIT 1;
QUERY PLAN
------------------------------------------------------------
Limit
-> Merge Append
Sort Key: pagg_tab.c
-> GroupAggregate
Group Key: pagg_tab.c
-> Sort
Sort Key: pagg_tab.c
-> Seq Scan on pagg_tab_p1 pagg_tab
-> GroupAggregate
Group Key: pagg_tab_1.c
-> Sort
Sort Key: pagg_tab_1.c
-> Seq Scan on pagg_tab_p2 pagg_tab_1
-> GroupAggregate
Group Key: pagg_tab_2.c
-> Sort
Sort Key: pagg_tab_2.c
-> Seq Scan on pagg_tab_p3 pagg_tab_2
(18 rows)
SELECT count(*) FROM pagg_tab GROUP BY c ORDER BY c LIMIT 1;
count
-------
250
(1 row)
RESET enable_hashagg; RESET enable_hashagg;
-- ROLLUP, partitionwise aggregation does not apply -- ROLLUP, partitionwise aggregation does not apply
EXPLAIN (COSTS OFF) EXPLAIN (COSTS OFF)

View File

@@ -76,6 +76,11 @@ EXPLAIN (COSTS OFF)
SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1; SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1;
SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1; SELECT a FROM pagg_tab WHERE a < 3 GROUP BY a ORDER BY 1;
-- Test partitionwise aggregation with ordered append path built from fractional paths
EXPLAIN (COSTS OFF)
SELECT count(*) FROM pagg_tab GROUP BY c ORDER BY c LIMIT 1;
SELECT count(*) FROM pagg_tab GROUP BY c ORDER BY c LIMIT 1;
RESET enable_hashagg; RESET enable_hashagg;
-- ROLLUP, partitionwise aggregation does not apply -- ROLLUP, partitionwise aggregation does not apply