postgres.git / summary / log / commit / refs
commit 93ed18720105ebacfd2a2bdeb55bf33feaf2ec80
Author: Richard Guo <rguo@postgresql.org>
Date: Sat Apr 11 07:38:47 2026 +0000
First released in: 17.10, 18.4
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 <pryzby@telsasoft.com>
Author: Tender Wang <tndrwang@gmail.com>
Reviewed-by: Richard Guo <guofenglinux@gmail.com>
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[];
[parent: c05c3baf169f]