Skip to content

Register the relations inside parenthesized joins in their scope - #12

Merged
ravenac95 merged 3 commits into
mainfrom
claude/sharp-archimedes-e6egci
Oct 5, 2026
Merged

ravenac95 merged 3 commits into
mainfrom
claude/sharp-archimedes-e6egci

Conversation

@ravenac95

Copy link
Copy Markdown
Member

Summary

A parenthesized join FROM item such as (a JOIN b ON ...) registered nothing in its scope. build_scope reported the whole item through ScopeVisitor::skipped, so a and b were missing from Scope::sources and columns that referred to them could not resolve or qualify. Schema validation rejected every query over a parenthesized join, even valid ones, with "Unknown table or alias".

The fix follows the shapes the parser produces: a JoinedTable, possibly wrapped in Paren, or Alias when the alias has a column list.

  • Unaliased (a JOIN b ON ...): left and each joins[i].this are registered in the enclosing scope exactly like top-level items. Nested parentheses recurse. The inner names count as preceding sources, so a later LATERAL item sees them. LATERAL VIEWs inside the join are registered as they are for a SELECT.
  • Aliased (a JOIN b) AS j / j(x, y): in SQL the alias hides the inner names, so only j is registered. It is a source with no scope of its own (SourceKind::DerivedTable, is_scope: false), the same shape a PIVOT source has: a derived relation with no query of its own to scope. Column aliases stay on the Alias node the source holds. The inner items are still built, so a derived table inside still gets its scope, and they are still reported. They are registered temporarily so a LATERAL item inside the join sees the items before it, and then the enclosing names are restored. An outer name that an inner item shadowed survives.
  • Join conditions: a subquery in an inner join's ON now gets a subquery scope, as one in a top-level join's ON does. In an aliased join the conditions may use the hidden names, which a child scope could not resolve, so they are reported through skipped instead of scoped.
  • Visitor events: ScopeVisitor::reference now reports the items inside a parenthesized join at depth + 1, with pre-order indices. The trait docs already described this but noted it was not implemented yet. Because an item's event fires once it is complete, the join's own event fires after its items' events. For (a JOIN b) JOIN c the events are a (1, 1), b (2, 1), join (0, 0), c (3, 0). The docs state this, and a test pins it.
  • Name collisions: these keep the existing rule that the last one wins in sources, as for top-level items.

Behaviour changes visible to callers

  • qualify_columns now qualifies the columns of an unaliased parenthesized join. On main, SELECT x, y FROM (a JOIN b ON a.id = b.id) fails with an unresolved-column error.
  • validate_with_schema now accepts valid queries over an unaliased parenthesized join, and reports exact unknown-column or ambiguity errors for invalid ones.
  • ScopeVisitor: a parenthesized join's contents now arrive as reference events instead of one skipped event.

Known limitation (unchanged)

In an aliased join, the inner ON columns, such as a.id in (a JOIN b ON a.id = b.id) AS j, are still walked as columns of the enclosing scope, where a is hidden. Schema validation therefore still reports them as unknown, the same as on main. Fixing that needs the join to resolve its own conditions, which is a separate change.

Tests

  • scope.rs: registration, (index, depth, registered_as) event order (including two levels of nesting), ON subquery scopes, LATERAL visibility after and inside a join, both alias shapes plus aliases on doubly parenthesized and nested joins, hidden derived tables, collisions, LATERAL VIEW inside a join, and input-node addresses for the new scopes.
  • qualify_columns and validation tests for the user-visible fix. Both fail on main.

Local CI passes, with the low-memory env: cargo fmt --check, make check-consistency, clippy -D warnings for polyglot-sql-ffi (all targets) and polyglot-sql-wasm, and make test-rust-verify-core with the sqlglot fixtures extracted (identity 977/977, transpilation 6045/6045, dialect identity, transpile, parser and custom dialect all at 100%).

🤖 Generated with Claude Code

https://claude.ai/code/session_016uUGFKsqsLyKC6tprkPgFp


Generated by Claude Code

claude added 2 commits October 5, 2026 22:24
A parenthesized join FROM item such as `(a JOIN b ON ...)` used to register
nothing, so its tables were missing from `Scope::sources` and their columns
could not resolve. Its relations are now registered in the enclosing scope
like top-level items, nested joins included, and a later LATERAL item sees
them. An aliased join `(a JOIN b) AS j` registers only `j`, since the alias
hides the names inside it; the inner items are still built and reported.

Subqueries in the conditions of the inner joins now get subquery scopes, and
`ScopeVisitor::reference` reports the inner items at `depth + 1`, before the
join itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uUGFKsqsLyKC6tprkPgFp
A subquery in the condition of a join inside an aliased parenthesized join
may refer to the names the alias hides, which a child scope cannot resolve,
so those conditions are reported as skipped instead of scoped.

Adds tests for an alias on a doubly parenthesized join, an aliased join
nested in an unaliased one, a hidden derived table, a LATERAL VIEW inside a
join, the input-node addresses of the new scopes, and schema validation over
parenthesized joins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uUGFKsqsLyKC6tprkPgFp
Comment thread CHANGELOG.md

@ravenac95 ravenac95 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY-focused pass: 0 Critical, 1 Important, 2 Minor. Not checked against a Linear issue (scope was DRY only). The polyglot repo has no REVIEW.md, so severities follow the general rubric.


Generated by Claude Code

Comment thread crates/polyglot-sql/src/scope.rs Outdated
Comment thread crates/polyglot-sql/src/scope.rs Outdated
Comment thread crates/polyglot-sql/src/scope.rs Outdated
`skip_join_conditions` and `collect_nested_join_subqueries` walked a joined
table the same way; both now use `for_each_join_condition`, so a condition
cannot end up both skipped and scoped. `add_from_item` binds the result of
`parenthesized_join` once and passes it on. The tests sort source and
lateral-source keys with one `sorted_keys` helper, and the CHANGELOG entry
is shorter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uUGFKsqsLyKC6tprkPgFp
@ravenac95
ravenac95 merged commit cd576ce into main Oct 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants