Repository navigation
Register the relations inside parenthesized joins in their scope - #12
Merged
Merged
Conversation
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
ravenac95
commented
Oct 5, 2026
ravenac95
commented
Oct 5, 2026
ravenac95
left a comment
Member
Author
There was a problem hiding this comment.
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
`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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A parenthesized join FROM item such as
(a JOIN b ON ...)registered nothing in its scope.build_scopereported the whole item throughScopeVisitor::skipped, soaandbwere missing fromScope::sourcesand 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 inParen, orAliaswhen the alias has a column list.(a JOIN b ON ...):leftand eachjoins[i].thisare registered in the enclosing scope exactly like top-level items. Nested parentheses recurse. The inner names count as preceding sources, so a laterLATERALitem sees them.LATERAL VIEWs inside the join are registered as they are for aSELECT.(a JOIN b) AS j/j(x, y): in SQL the alias hides the inner names, so onlyjis registered. It is a source with no scope of its own (SourceKind::DerivedTable,is_scope: false), the same shape aPIVOTsource has: a derived relation with no query of its own to scope. Column aliases stay on theAliasnode 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 aLATERALitem inside the join sees the items before it, and then the enclosing names are restored. An outer name that an inner item shadowed survives.ONnow gets a subquery scope, as one in a top-level join'sONdoes. In an aliased join the conditions may use the hidden names, which a child scope could not resolve, so they are reported throughskippedinstead of scoped.ScopeVisitor::referencenow reports the items inside a parenthesized join atdepth + 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 cthe events area (1, 1),b (2, 1), join(0, 0),c (3, 0). The docs state this, and a test pins it.sources, as for top-level items.Behaviour changes visible to callers
qualify_columnsnow qualifies the columns of an unaliased parenthesized join. Onmain,SELECT x, y FROM (a JOIN b ON a.id = b.id)fails with an unresolved-column error.validate_with_schemanow 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 asreferenceevents instead of oneskippedevent.Known limitation (unchanged)
In an aliased join, the inner
ONcolumns, such asa.idin(a JOIN b ON a.id = b.id) AS j, are still walked as columns of the enclosing scope, whereais hidden. Schema validation therefore still reports them as unknown, the same as onmain. 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),ONsubquery scopes,LATERALvisibility after and inside a join, both alias shapes plus aliases on doubly parenthesized and nested joins, hidden derived tables, collisions,LATERAL VIEWinside a join, and input-node addresses for the new scopes.qualify_columnsandvalidationtests for the user-visible fix. Both fail onmain.Local CI passes, with the low-memory env:
cargo fmt --check,make check-consistency, clippy-D warningsforpolyglot-sql-ffi(all targets) andpolyglot-sql-wasm, andmake test-rust-verify-corewith 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