From 371a05707b1cd32c5cd4305e28db49164efa0b9a Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Thu, 8 Oct 2026 15:32:45 +0900 Subject: [PATCH v1] Fix bogus Assert in find_var_for_subquery_tle find_var_for_subquery_tle() searches a subquery rel's targetlist for a Var referencing a given subquery output column, and asserted that any Var it finds there belongs to that rel. That does not hold for an appendrel child. set_append_rel_size() builds the child's targetlist by translating the parent's, and as noted there the result can contain arbitrary expressions. In particular, when a member of a LATERAL UNION ALL subquery is pulled up and its output is a lateral reference, the translated expression is a Var of the laterally referenced rel. If the child is itself a subquery with sorted paths, convert_subquery_pathkeys() would trip the Assert. In non-assert builds, the function went on to compare such a Var's varattno with the subquery column's resno, and so could match a Var of an unrelated rel. To fix, ignore Vars that do not belong to the subquery rel, the same way set_rel_width() already does. Also correct the adjacent comment, which claimed that the only non-Var entries are placeholders. --- src/backend/optimizer/path/pathkeys.c | 12 ++++++++++-- src/test/regress/expected/join.out | 18 ++++++++++++++++++ src/test/regress/sql/join.sql | 7 +++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index d6a305aedac..50651813a11 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -1262,10 +1262,18 @@ find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle) { Var *var = (Var *) lfirst(lc); - /* Ignore placeholders */ + /* Ignore placeholders and other non-Var expressions */ if (!IsA(var, Var)) continue; - Assert(var->varno == rel->relid); + + /* + * Ordinarily, a Var in a rel's targetlist must belong to that rel; + * but there are corner cases involving LATERAL references where that + * isn't so. If the Var has the wrong varno, ignore it, since it + * cannot be referencing the subquery's output. + */ + if (var->varno != rel->relid) + continue; /* If we find a Var referencing this TLE, we're good */ if (var->varattno == tle->resno) diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 4ee94d00ade..3bfd7b6e74c 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -10238,6 +10238,24 @@ select * from generate_series(100,200) g, 123 | 4567890123456789 | 123 (3 rows) +-- lateral with UNION ALL subselect, where a member is a subquery whose +-- output contains a lateral reference +explain (costs off) + select ss.v from int8_tbl x, + lateral (select x.q1 from (select distinct q2 from int8_tbl) s union all + select q2 from int8_tbl) ss(v); + QUERY PLAN +--------------------------------------------------------- + Nested Loop + -> Seq Scan on int8_tbl x + -> Append + -> Subquery Scan on s + -> HashAggregate + Group Key: int8_tbl_1.q2 + -> Seq Scan on int8_tbl int8_tbl_1 + -> Seq Scan on int8_tbl +(8 rows) + -- lateral with VALUES explain (costs off) select count(*) from tenk1 a, diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index bf8153afbcd..3866c3d16e6 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -3985,6 +3985,13 @@ select * from generate_series(100,200) g, lateral (select * from int8_tbl a where g = q1 union all select * from int8_tbl b where g = q2) ss; +-- lateral with UNION ALL subselect, where a member is a subquery whose +-- output contains a lateral reference +explain (costs off) + select ss.v from int8_tbl x, + lateral (select x.q1 from (select distinct q2 from int8_tbl) s union all + select q2 from int8_tbl) ss(v); + -- lateral with VALUES explain (costs off) select count(*) from tenk1 a, -- 2.37.1 (Apple Git-137.1)