fix_infinite_row_estimates.patch

text/plain

Filename: fix_infinite_row_estimates.patch
Type: text/plain
Part: 0
Message: Re: Assertion failure with LEFT JOINs among >500 relations

Patch

Format: unified
File+
src/backend/optimizer/path/costsize.c 22 24
src/backend/optimizer/plan/createplan.c 2 0
src/backend/optimizer/util/pathnode.c 6 0
src/test/regress/expected/partition_join.out 5 6
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 <float.h>
 #include <math.h>
 
 #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.