From d174f31bdced5664569528cb12f38991e9543c05 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Thu, 17 Sep 2026 15:26:08 +0900 Subject: [PATCH v1 2/2] Fix planner failure with zero-column join alias Vars When a whole-row Var of a join on the nullable side of an outer join is expanded, the resulting RowExpr is wrapped in a PlaceHolderVar to carry the Var's nullingrels. If the join has no columns, the RowExpr contains no Vars, so the PlaceHolderVar is instead evaluated at the join's input relations. add_nullingrels_if_needed() refused to do that when the Var is an outer reference from a subquery, and raised "unsupported join alias expression". That restriction is unnecessary. Every planner caller passes root->parse as the query, so the Var always belongs to root->parse regardless of its varlevelsup, and get_relids_for_join() on it returns relids at the correct level. This is the same level the non-empty case already gets from pull_varnos_of_level(). Remove the check, and add an assertion that the query is root->parse. --- src/backend/optimizer/util/var.c | 5 +++-- src/test/regress/expected/join.out | 16 ++++++++++++++++ src/test/regress/sql/join.sql | 8 ++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 7390e8982e4..4cfea8d1cb9 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -791,6 +791,8 @@ flatten_join_alias_vars(PlannerInfo *root, Query *query, Node *node) * it's okay to immediately increment sublevels_up. */ Assert(node != (Node *) query); + /* add_nullingrels_if_needed relies on this */ + Assert(root == NULL || query == root->parse); context.root = root; context.query = query; @@ -1257,8 +1259,7 @@ add_nullingrels_if_needed(PlannerInfo *root, Node *newnode, Var *oldvar) if (bms_is_empty(phrels)) /* variable-free? */ { - if (levelsup != 0) /* this won't work otherwise */ - elog(ERROR, "unsupported join alias expression"); + /* oldvar belongs to root->parse even when levelsup > 0 */ phrels = get_relids_for_join(root->parse, oldvar->varno); /* If it's an outer join, eval below not above the join */ phrels = bms_del_member(phrels, oldvar->varno); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index eb88fb2f616..f5d05bceb8f 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -4451,6 +4451,22 @@ group by t23 order by 1; | 2 (6 rows) +-- nulled whole-row Var of a zero-column join, referenced from a subquery +select t1.q1, t1.q2, (select t23::text) +from int8_tbl t1 left join + ((select from int4_tbl where f1 = 0) t2 + cross join (select from int4_tbl where f1 = 0) t3) t23 + on (t1.q1 = 123) +order by 1, 2; + q1 | q2 | t23 +------------------+-------------------+----- + 123 | 456 | () + 123 | 4567890123456789 | () + 4567890123456789 | -4567890123456789 | + 4567890123456789 | 123 | + 4567890123456789 | 4567890123456789 | +(5 rows) + -- -- test incorrect failure to NULL pulled-up subexpressions -- diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 29eca8eda2e..4577e7c54f2 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1397,6 +1397,14 @@ from int8_tbl t1 left join on (t1.q2 = t23.q1) group by t23 order by 1; +-- nulled whole-row Var of a zero-column join, referenced from a subquery +select t1.q1, t1.q2, (select t23::text) +from int8_tbl t1 left join + ((select from int4_tbl where f1 = 0) t2 + cross join (select from int4_tbl where f1 = 0) t3) t23 + on (t1.q1 = 123) +order by 1, 2; + -- -- test incorrect failure to NULL pulled-up subexpressions -- -- 2.37.1 (Apple Git-137.1)