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]