diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index cd3716d494..edcae1f36a 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -45,10 +45,9 @@ * (total_cost - startup_cost) * tuples_to_fetch / path->rows; * Note that a base relation's rows count (and, by extension, plan_rows for * plan nodes below the LIMIT node) are set without regard to any LIMIT, so - * that this equation works properly. (Note: while path->rows is never zero - * for ordinary relations, it is zero for paths for provably-empty relations, - * so beware of division-by-zero.) The LIMIT is applied as a top-level - * plan node. + * that this equation works properly. (Also, these routines guarantee not to + * set the rows count to zero, so there will be no zero divide.) The LIMIT is + * applied as a top-level plan node. * * For largely historical reasons, most of the routines in this module use * the passed result Path only to store their results (rows, startup_cost and @@ -71,6 +70,7 @@ #include "postgres.h" +#include #include #include "access/amapi.h" @@ -189,11 +189,14 @@ double clamp_row_est(double nrows) { /* - * Force estimate to be at least one row, to make explain output look - * better and to avoid possible divide-by-zero when interpolating costs. - * Make it an integer, too. + * Avoid infinite and NaN row estimates. Costs derived from such values + * are going to be useless. Also estimate to be at least one row, to make + * explain output look better and to avoid possible divide-by-zero when + * interpolating costs. Make it an integer, too. */ - if (nrows <= 1.0) + if (isinf(nrows)) + nrows = rint(DBL_MAX); + else if (nrows <= 1.0 || isnan(nrows)) nrows = 1.0; else nrows = rint(nrows); @@ -2037,10 +2040,15 @@ cost_append(AppendPath *apath) apath->path.startup_cost = 0; apath->path.total_cost = 0; - apath->path.rows = 0; if (apath->subpaths == NIL) + { + /* Avoid 0 rows to prevent divide by 0 risks later in planning */ + apath->path.rows = 1.0; return; + } + + apath->path.rows = 0; if (!apath->path.parallel_aware) { @@ -2737,12 +2745,8 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path, QualCost restrict_qual_cost; double ntuples; - /* Protect some assumptions below that rowcounts aren't zero or NaN */ - if (outer_path_rows <= 0 || isnan(outer_path_rows)) - outer_path_rows = 1; - if (inner_path_rows <= 0 || isnan(inner_path_rows)) - inner_path_rows = 1; - + Assert(outer_path_rows > 0); + Assert(inner_path_rows > 0); /* Mark the path with the correct row estimate */ if (path->path.param_info) path->path.rows = path->path.param_info->ppi_rows; @@ -2952,12 +2956,8 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace, innerendsel; Path sort_path; /* dummy for result of cost_sort */ - /* Protect some assumptions below that rowcounts aren't zero or NaN */ - if (outer_path_rows <= 0 || isnan(outer_path_rows)) - outer_path_rows = 1; - if (inner_path_rows <= 0 || isnan(inner_path_rows)) - inner_path_rows = 1; - + Assert(outer_path_rows > 0); + Assert(inner_path_rows > 0); /* * A merge join will stop as soon as it exhausts either input stream * (unless it's an outer join, in which case the outer side has to be @@ -3185,9 +3185,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, rescannedtuples; double rescanratio; - /* Protect some assumptions below that rowcounts aren't zero or NaN */ - if (inner_path_rows <= 0 || isnan(inner_path_rows)) - inner_path_rows = 1; + Assert(inner_path_rows > 0); /* Mark the path with the correct row estimate */ if (path->jpath.path.param_info) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 3d7a4e373f..57f4d2d2b4 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1112,6 +1112,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags) copy_generic_path_info(plan, (Path *) best_path); + plan->plan_rows = 0.0; + return plan; } diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 5281a2f998..02bec0e96f 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1321,6 +1321,9 @@ create_append_path(PlannerInfo *root, if (rows >= 0) pathnode->path.rows = rows; + /* Avoid infinite row estimates in extreme cases */ + pathnode->path.rows = clamp_row_est(pathnode->path.rows); + return pathnode; } @@ -1465,6 +1468,9 @@ create_merge_append_path(PlannerInfo *root, input_startup_cost, input_total_cost, pathnode->path.rows); + /* Avoid infinite row estimates in extreme cases */ + pathnode->path.rows = clamp_row_est(pathnode->path.rows); + return pathnode; } diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 0057f41caa..1155be8d35 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1439,8 +1439,8 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 -------------------------------------------- Sort Sort Key: a, t2.b - -> Hash Left Join - Hash Cond: (t2.b = a) + -> Nested Loop Left Join + Join Filter: (a = t2.b) -> Append -> Seq Scan on prt2_p1 t2_1 Filter: (a = 0) @@ -1448,10 +1448,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 Filter: (a = 0) -> Seq Scan on prt2_p3 t2_3 Filter: (a = 0) - -> Hash - -> Result - One-Time Filter: false -(14 rows) + -> Result + One-Time Filter: false +(13 rows) -- -- tests for hash partitioned tables.