From fc4089f3c65a5f1b413a3299ba02b66a8e5e37d0 Mon Sep 17 00:00:00 2001
From: David Rowley <drowley@postgresql.org>
Date: Tue, 10 Oct 2023 16:50:03 +1300
Subject: [PATCH] Fix possible crash in add_paths_to_append_rel()

While working on a8a968a82, I failed to consider that
cheapest_startup_path can be NULL when there is no non-parameterized
path in the pathlist.  This is well documented in set_cheapest(), I just
failed to notice.

Here we adjust the code to just check if the RelOptInfo has a
cheapest_startup_path set before adding it to the startup_subpaths list.

Reported-by: Richard Guo
Author: Richard Guo
Discussion: https://postgr.es/m/CAMbWs49w3t03V69XhdCuw+GDwivny4uQUxrkVp6Gejaspt0wMQ@mail.gmail.com
---
 src/backend/optimizer/path/allpaths.c | 11 +++++++----
 src/test/regress/expected/union.out   | 16 ++++++++++++++++
 src/test/regress/sql/union.sql        |  9 ++++++++-
 3 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 7af001feaa..eea49cca7b 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1350,14 +1350,17 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 
 		/*
 		 * When the planner is considering cheap startup plans, we'll also
-		 * collect all the cheapest_startup_paths and build an AppendPath
-		 * containing those as subpaths.
+		 * collect all the cheapest_startup_paths (if set) and build an
+		 * AppendPath containing those as subpaths.
 		 */
-		if (rel->consider_startup && childrel->pathlist != NIL &&
-			childrel->cheapest_startup_path->param_info == NULL)
+		if (rel->consider_startup && childrel->cheapest_startup_path != NULL)
+		{
+			/* cheapest_startup_path must not be a parameterized path. */
+			Assert(childrel->cheapest_startup_path->param_info == NULL);
 			accumulate_append_subpath(childrel->cheapest_startup_path,
 									  &startup_subpaths,
 									  NULL);
+		}
 		else
 			startup_subpaths_valid = false;
 
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index f046e522de..64cebe4833 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -1453,3 +1453,19 @@ inner join tenk2 t2 on t1.tenthous = t2.tenthous
          ->  Result
 (8 rows)
 
+-- Ensure there is no problem if cheapest_startup_path is NULL
+explain (costs off)
+select * from tenk1 t1
+left join lateral
+  (select t1.tenthous from tenk2 t2 union all (values(1)))
+on true limit 1;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Limit
+   ->  Nested Loop Left Join
+         ->  Seq Scan on tenk1 t1
+         ->  Append
+               ->  Index Only Scan using tenk2_hundred on tenk2 t2
+               ->  Result
+(6 rows)
+
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index d65ca9f86d..599013e7c9 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -550,4 +550,11 @@ explain (costs off)
 select t1.unique1 from tenk1 t1
 inner join tenk2 t2 on t1.tenthous = t2.tenthous
    union all
-(values(1)) limit 1;
\ No newline at end of file
+(values(1)) limit 1;
+
+-- Ensure there is no problem if cheapest_startup_path is NULL
+explain (costs off)
+select * from tenk1 t1
+left join lateral
+  (select t1.tenthous from tenk2 t2 union all (values(1)))
+on true limit 1;