Skip to content

sql: make zero-column relation aliases visible to name resolution#37646

Draft
ggevay wants to merge 1 commit into
MaterializeInc:mainfrom
ggevay:gabor/sql-313-zero-column-alias-shadowing
Draft

sql: make zero-column relation aliases visible to name resolution#37646
ggevay wants to merge 1 commit into
MaterializeInc:mainfrom
ggevay:gabor/sql-313-zero-column-alias-shadowing

Conversation

@ggevay

@ggevay ggevay commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Fixes SQL-313

Motivation

A relation alias that exposes zero columns, e.g. (SELECT) AS q, a zero-column CTE, view, or table, was invisible to name resolution. Scope tracked relation names only through the table_name of its column items, so a column-less relation left no trace in the scope. This diverged from PostgreSQL, where range table entries exist independent of their columns, in two user-visible ways:

  • The alias did not shadow same-named aliases in outer scopes, so a reference like q.a inside a subquery silently bound to an outer q instead of erroring.
  • The alias escaped duplicate table name detection, so queries like SELECT * FROM t, (SELECT) AS t were accepted instead of erroring with table name "t" specified more than once.

Description

User-facing change: zero-column relation aliases now shadow outer aliases of the same name and participate in duplicate-alias detection, matching PostgreSQL. Queries that previously bound a qualified column reference to an outer alias of the same name now fail with column "q.a" does not exist, and duplicate zero-column aliases (in comma joins, explicit JOIN, CROSS JOIN, NATURAL JOIN, USING join aliases, and DELETE ... USING) are now rejected with table name "t" specified more than once. Qualified wildcards on zero-column relations (SELECT q.* FROM (SELECT) AS q) now expand to zero columns instead of erroring, also matching PostgreSQL.

Implementation: Scope gains a zero_arity_table_names field, a lightweight analogue of PostgreSQL's column-less range table entries. It is maintained by scope construction (from_source), product, project, and plan_table_alias, cleared where non-lateral subqueries make outer relations invisible, and carried into the group scope so shadowing survives GROUP BY. Qualified column resolution refuses to match items from lateral levels farther out than the closest zero-arity relation with the referenced name, and Scope::table_names includes the tracked names so duplicate detection sees them. The design degrades gracefully: where the new field is not propagated, behavior is exactly the old behavior.

Known deliberate gap: a whole-row reference to a zero-column relation (SELECT q FROM (SELECT) AS q) still errors with column "q" does not exist, where PostgreSQL returns an empty record (). Supporting that requires zero-field record support and is a different root cause.

Verification

  • Every asserted behavior was pinned against PostgreSQL first (error messages, shadowing across LATERAL, non-lateral sibling visibility, grouping, wildcard expansion, duplicate detection in all join forms).
  • ~40 new regression cases in test/sqllogictest/scoping.slt covering the original repro, all duplicate-alias variants, CTEs, zero-column views and tables, LATERAL, GROUP BY, outer joins with a zero-arity side, qualified wildcards, and DELETE ... USING.
  • Regression runs, all passing: scoping, subquery, joins, cte, cte_lowering, table_func, outer_join (+ lowering, simplification, variadic), select_all_group_by, scalar_subqueries_select_list, array/list_subquery, join-identity-elision, func_aliases, degenerate, distinct_on, order_by, tpch_select, window_funcs, cockroach/{join, apply_join, postgresjoin, aggregate, exec_hash_join, exec_merge_join, lookup_join, alias_types}, postgres/{join-lateral, subselect}, plus cargo test for mz-sql and mz-transform. The only non-passes (CockroachDB INDEX syntax bails, duplicate USING column "unsupported") were confirmed pre-existing on main.

🤖 Generated with Claude Code

A Scope tracked relation names only through the table_name of its
column items, so a relation exposing zero columns, e.g. (SELECT) AS q,
left no trace in the scope. Such an alias did not shadow same-named
aliases in outer scopes, so q.a silently bound to an outer q instead
of erroring, and it escaped duplicate table name detection, so
FROM t, (SELECT) AS t was accepted. Both diverge from PostgreSQL,
where range table entries exist independent of their columns.

Track the names of zero-arity relations in a dedicated Scope field,
maintained by scope construction, product, project, and aliasing.
Name resolution refuses to match items beyond the closest zero-arity
relation with the referenced table name, duplicate detection includes
the tracked names, and qualified wildcards on zero-column relations
now expand to zero columns as in PostgreSQL.

Fixes SQL-313.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ggevay ggevay added the A-ADAPTER Topics related to the ADAPTER layer label Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ADAPTER Topics related to the ADAPTER layer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant