From 93ed18720105ebacfd2a2bdeb55bf33feaf2ec80 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Sat, 11 Apr 2026 16:38:47 +0900 Subject: [PATCH] Fix estimate_array_length error with set-operation array coercions When a nested set operation's output type doesn't match the parent's expected type, recurse_set_operations builds a projection target list using generate_setop_tlist with varno 0. If the required type coercion involves an ArrayCoerceExpr, estimate_array_length could be called on such a Var, and would pass it to examine_variable, which errors in find_base_rel because varno 0 has no valid relation entry. Fix by skipping the statistics lookup for Vars with varno 0. Bug introduced by commit 9391f7152. Back-patch to v17, where estimate_array_length was taught to use statistics. Reported-by: Justin Pryzby Author: Tender Wang Reviewed-by: Richard Guo Discussion: https://postgr.es/m/adjW8rfPDkplC7lF@pryzbyj2023 Backpatch-through: 17 --- src/backend/utils/adt/selfuncs.c | 12 ++++++++++++ src/test/regress/expected/union.out | 17 +++++++++++++++++ src/test/regress/sql/union.sql | 4 ++++ 3 files changed, 33 insertions(+) diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index d5468edcf31..2c1daefc0c0 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -2165,6 +2165,18 @@ estimate_array_length(PlannerInfo *root, Node *arrayexpr) AttStatsSlot sslot; double nelem = 0; + /* + * Skip calling examine_variable for Var with varno 0, which has no + * valid relation entry and would error in find_base_rel. Such a Var + * can appear when a nested set operation's output type doesn't match + * the parent's expected type, because recurse_set_operations builds a + * projection target list using generate_setop_tlist with varno 0, and + * if the required type coercion involves an ArrayCoerceExpr, we can + * be called on that Var. + */ + if (IsA(arrayexpr, Var) && ((Var *) arrayexpr)->varno == 0) + return 10; /* default guess, should match scalararraysel */ + examine_variable(root, arrayexpr, 0, &vardata); if (HeapTupleIsValid(vardata.statsTuple)) { diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 0fd0e1c38b3..882b075b9a1 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -1489,3 +1489,20 @@ on true limit 1; -> Result (6 rows) +-- Test handling of Vars with varno 0 in estimate_array_length +explain (verbose, costs off) +select null::int[] union all select null::int[] union all select null::bigint[]; + QUERY PLAN +--------------------------------------------- + Append + -> Result + Output: (NULL::integer[]) + -> Append + -> Result + Output: NULL::integer[] + -> Result + Output: NULL::integer[] + -> Result + Output: NULL::bigint[] +(10 rows) + diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql index f8826514e42..c52ca012b5c 100644 --- a/src/test/regress/sql/union.sql +++ b/src/test/regress/sql/union.sql @@ -577,3 +577,7 @@ select * from tenk1 t1 left join lateral (select t1.tenthous from tenk2 t2 union all (values(1))) on true limit 1; + +-- Test handling of Vars with varno 0 in estimate_array_length +explain (verbose, costs off) +select null::int[] union all select null::int[] union all select null::bigint[];