Repository navigation
fix(parser,ast,formatter): keep JOIN ... USING and accept parenthesized set operations (#603, #612) - #630
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @ajitpratap0's task in 35s —— View job Review of PR #630 (JOIN ... USING and parenthesized set operations)I read the parser, AST, release and SQL-rendering diffs. I did not run the test suite in this job, so the CI results and your validator logs are the only evidence for behavior. Summary: This is a well-scoped, additive change. The What looks good
Suggestions (non-blocking)
SecurityNo concerns. There is no new unbounded recursion or allocation driven by input: depth is bounded, and the VerdictThe changes look correct and well tested, and I have no blocking concerns. This is a comment, not a formal approval. |
…ering ON cols (#603) The parser returned a JOIN ... USING column list as the join Condition: a bare Identifier for one column, a ListExpression for several. JoinClause had no field that recorded USING, so ast SQL(), pkg/formatter and the CLI formatter printed "ON <cols>". For one column that changes the meaning (ON id is a boolean test). For several the output did not re-parse (E2002 at the comma), and the CLI printed "ON LIST". Add an additive field JoinClause.Using []Expression. For USING joins the parser now stores one *ast.Identifier per column (with Pos set) in Using and leaves Condition nil. ON, CROSS, NATURAL and APPLY joins are unchanged, as are the USING error messages. - ast: Children() returns Left, Right, Condition (if any), then the Using columns. The ON and no-condition fast paths are unchanged. ReleaseAST releases the Using expressions before zeroing the join. - Renderers: ast writeJoin, formatter joinSQL and the CLI formatJoin emit "USING (c1, c2)" through the normal expression renderers. - Consumers: the CLI analyzer no longer flags USING joins as JOIN_WITHOUT_CONDITION/cartesian, and the schema validator validates every USING column. pkg/transform rewrites table qualifiers in Condition only; USING identifiers are unqualified, so nothing changes there. Walk-based consumers (linter, extract) see the columns through Children(). Behaviour change for library users: for USING joins, JoinClause.Condition is now nil and the columns are in JoinClause.Using. They were previously an Identifier or ListExpression in Condition. TestParser_MultiColumnUSING is updated to the new representation. Regression tests cover all 9 dialects: the parser AST shape, the ast SQL() round trip (idempotent, same Using names after re-parse), formatter compact/readable/FormatString output and round trip, CLI format (readable and compact) and analyzer, schema validation of USING columns, Children/Walk/Inspect, and pool release under -race. Fixes #603 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
…TH queries as derived tables and statements (#612) A derived table in FROM/JOIN/comma lists had to be a single SELECT, and a statement or set-operation operand could not start with "(". These inputs failed with misleading errors: SELECT * FROM (SELECT 1 UNION SELECT 2) t -> expected ), got UNION SELECT * FROM ((SELECT 1) UNION (SELECT 2)) t -> expected ) or JOIN SELECT * FROM ((SELECT 1)) t -> E4001 alias after join group SELECT * FROM (WITH c AS (...) SELECT a FROM c) t -> expected alias name after AS (SELECT 1) UNION (SELECT 2), (SELECT 1) [ORDER BY 1] -> expected statement SELECT 1 UNION (SELECT 2) -> expected SELECT after set op Parser: - Derived tables parse a full query expression (set operations, WITH, nested parentheses) via parseQueryExpression/parseParenthesizedQuery. - Set-operation right operands and statements may be "(query)"; parseSelectWithSetOperations now wraps parseSetOperationChain. - A non-allocating lookahead (parenOpensQueryExpression) tells "((SELECT ...)) t" and "((SELECT ...) UNION ...) t" apart from a parenthesized join group such as "((SELECT ...) t JOIN ...)", which keeps its E4001. - A FROM-less select list may be followed by ORDER BY/LIMIT/OFFSET/FETCH (SELECT 1 ORDER BY 1), so the rendered forms round-trip. - ORDER BY/LIMIT/OFFSET/FETCH after a parenthesized last operand is merged into that operand when that keeps the meaning; otherwise (the operand has its own tail, is a set operation, or a right operand with WITH) the parser returns E4001 instead of building a wrong tree. - Each parenthesized level counts against MaxRecursionDepth (E2007). AST (additive only): - TableReference.SetSubquery *SetOperation holds a set-operation derived table; single-SELECT bodies stay in Subquery. Children() returns it and releaseTableReference releases its operands (left spine iteratively) without touching the shared JoinClause.Left copy. - SetOperation.LeftParenthesized / RightParenthesized record operand parentheses. SQL(), pkg/formatter and the CLI formatter emit them and always parenthesize a non-nil Right *SetOperation, so A EXCEPT (B UNION C) and A UNION (B ORDER BY x LIMIT n) keep their meaning. Output for inputs that parsed before is unchanged; the set-op reference test in pkg/formatter now models the parentheses and randomizes the flags. Docs: API_REFERENCE (new fields), SQL_COMPATIBILITY (two set-operation rows and the E4001 limitation). TestIssue521_DoubleParenFromShapes moves the four previously unsupported shapes to the parsing cases. Tests: parser cases across the default parser and all 9 dialects with exact SQL(), round-trip, controls, E4001 tails, depth limits and a no-alloc check for the lookahead; ast SQL/Children/Walk/release tests; formatter and CLI re-parse tests. Fixes #612 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
- ADV630-1: checkUnionInjection now walks the whole set-operation tree and checks every SELECT that appears anywhere in the right-hand operand of a UNION, including the leftmost SELECT of a parenthesized group. Since #612 the parser builds right-nested SetOperation trees for "x UNION (SELECT ... FROM pg_user UNION SELECT ...)", and the old check only type-asserted Right to *ast.SelectStatement, so that payload produced no findings. Each operand is visited exactly once (no duplicate findings), and leaf statements are scanned via a separate walk so nested set operations are not re-checked. Added TestScan_UnionInjectionInNestedSetOperations covering nested and doubly nested parenthesized UNIONs, EXCEPT/INTERSECT inside a UNION operand, parenthesized left groups, and negative cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
9120fce to
205a6c3
Compare
|
Merge train (batch 3): rebased onto main after #628 merged (205a6c3). Conflicts:
Checks after the rebase:
Adversarial pass (Opus): found no must-fix items. The should-fix security item ADV630-1 is fixed in this PR: Deferred to follow-up issues: two scanner gaps that predate this PR. Derived tables and comma-joined system tables are not scanned, and the NULL-column UNION check never fires. |
|
Claude finished @ajitpratap0's task in 21s —— View job Review of PR #630 (updated for the new security-scanner commit)I read the new commit Security scanner change (
|
Summary
#603 parser/ast: JOIN ... USING (cols) is stored as a plain condition and renders as
ON colsFixes #603.
JOIN ... USING (cols)was being rendered asON colsby ast SQL(), pkg/formatter andgosqlx format. Multi-column USING producedON id, k, which failed to re-parse with E2002, and the CLI printedON LIST. The parser now stores USING columns in a new additive field,JoinClause.Using []Expression, one *ast.Identifier with Pos per column, and leaves Condition nil. All three renderers emitUSING (c1, c2). Children() and Walk expose the columns, ReleaseAST releases them, the CLI analyzer treats USING as a join condition, and the schema validator checks every USING column. Independent validation found 0 failures in all 11 AllDialects entries for SQL() and formatter round-trips, re-parse and idempotence; the built CLI shows the correct output; and the race gate, go vet and golangci-lint all pass. Out of scope and deferred, for sound reasons:USING idgrammar.#612 parser: parenthesized set operations not accepted as derived tables or statements; ((SELECT ...)) t gives a misleading join-group error
Fixes #612. A derived table in FROM, JOIN or a comma list can now be a set operation, a WITH query or a doubly parenthesized query. A whole statement or a set-operation operand can now be written as
(query), for example(SELECT 1) UNION (SELECT 2)andSELECT 1 UNION (SELECT 2). The AST changes are additive:TableReference.SetSubqueryandSetOperation.LeftParenthesized/RightParenthesized. SQL(), pkg/formatter and the CLI formatter keep the parentheses that change meaning, such asA EXCEPT (B UNION C). ORDER BY/LIMIT that the AST cannot represent returns E4001 instead of building a wrong tree. Independent validation:Deferred, as planned:
WITH ... (SELECT ...)andCREATE VIEW v AS (SELECT ...)INSERT INTO t (SELECT ...)(parser: INSERT gaps: RETURNING after INSERT ... SELECT, DEFAULT VALUES, MySQL INSERT ... SET, INSERT INTO t (SELECT ...) #610);inside CTE/IN/EXISTS subqueries and set operations, and;;after INSERT ... SELECT / CREATE VIEW #602)Also known: BenchmarkParserSimpleSelect is +4.8% and +16 B/op because TableReference grew from 128 to 136 bytes; reordering fields could recover this in a follow-up.
Commits (2), one implementation commit per issue plus test-only follow-ups:
ON cols#603)28 files changed, 2378 insertions(+), 151 deletions(-)Test Plan
CGO_ENABLED=1 go test -race -timeout 300s $(go list ./... | grep -v /cbinding)),go vet,golangci-lint, re-ran the reproduction cases on HEAD, and confirmed the new tests fail on the parent commit.benchstat -count=6before/after on the same machine.#603 evidence (validator)
#603 race gate: PASS. Command run:
CGO_ENABLED=1 go test -race -timeout 300s $(go list ./... | grep -v /cbinding)in the worktree at HEAD 1d36dd2. Exit 0, 44 packages ok, no FAIL lines and no flakes, so nothing was re-run. Log: /private/tmp/claude-501/-Users-ajitpratapsingh-dev-GoSQLX/b99cd3cf-cb92-4294-a0db-d6a7c9a86253/scratchpad/b3/603/val/race.txt. TestChildrenCoverage_VisitorContract and the new concurrent TestJoinUsing_PoolRelease_Issue603 are both in this run.#612 evidence (validator)
#612 race gate: PASS.
CGO_ENABLED=1 go test -race -timeout 300s $(go list ./... | grep -v /cbinding)exited 0 and every package passed; the first run had 29 cached packages. I then re-ran the whole suite with -count=1, so nothing was cached. It again exited 0 with no FAIL: parser 10.0s, ast 4.0s, formatter 2.0s, cmd/gosqlx/cmd 2.0s.task test:cbindingpassed (pkg/cbinding ok). TestPerformanceRegression (./pkg/sql/parser, without race, -count=1) passed. No flaky tests were seen.Breaking Changes
ON cols#603: Additive: new exported fieldUsing []ast.Expressiononast.JoinClause(appended after Condition; existing keyed literals still compile). Observable behaviour change: forJOIN ... USING (cols)the parser now leavesConditionnil and stores the columns inUsing, as plain *ast.Identifier nodes with no ListExpression wrapper. Every renderer (ast SQL(), pkg/formatter, CLI format) now emitsUSING (cols)instead ofON cols. Children() for USING joins returns the Using identifiers in place of the old Condition node. No exported function signatures change; parseJoinCondition is unexported. The commit body and PR state the representation change.ast.TableReference.SetSubquery *ast.SetOperation,ast.SetOperation.LeftParenthesized boolandast.SetOperation.RightParenthesized bool. No signature or field type changes; TableReference.Subquery stays*SelectStatementand still holds every derived table whose body is a single SELECT.Notes
gosqlx formatsilently corrupts many expressions; switch the CLI to pkg/formatter #599-schema/postgres: loader misreports index columns and key flags (column order, INCLUDE, expression and partitioned indexes, read-only roles) #620); later PRs in the train are rebased onto this one after it merges.main.Fixes #603
Fixes #612
Follow-up to #566
🤖 Generated with Claude Code
https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve