Repository navigation
fix(parser,ast,formatter): named window round-trip, GROUPS/EXCLUDE frames and INTERVAL bounds (#608, #609) - #629
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @ajitpratap0's task in 27s —— View job Review of PR #629 (#608 named windows, #609 GROUPS/EXCLUDE/INTERVAL)I read the diff for the parser, AST, formatter and CLI changes. I did not run the tests or the race gate here. I'm relying on the validator evidence in the PR description for that. Overall: The change is small, additive and well scoped. The What looks good
Observations and minor suggestions
Performance and security
Test coverageCoverage is good: tests fail on the parent, round-trips run across dialects, and the golden doc example is pinned. Two small gaps are the casing and window-name edge cases from item 2 and item 5, and negative tests for I found no blocking issues, so this looks good to merge once CI is green. |
…OVER (w ...) refinement (#608) WindowSpec.Name held both the WINDOW definition name and the bare OVER w reference, and writeWindowSpec / windowSpecSQL always wrote it inside the parentheses. SELECT sum(x) OVER w FROM t WINDOW w AS (PARTITION BY y) rendered as "OVER (w) ... WINDOW w AS (w PARTITION BY y)", which fails to re-parse (E2002 at 1:21). The SQL:2003 existing-window refinement OVER (w ORDER BY z) also failed to parse in every dialect. - ast: add the additive field WindowSpec.BaseWindow for the existing window name refined inside the parentheses; document Name as the name outside them. Pool release resets it. - parser: parseWindowSpec stores a leading identifier after "(" as BaseWindow. The non-standard WINDOW w AS w2 keeps w2 as the base window (rendered w AS (w2)) instead of silently dropping it. - ast SQL(): a spec with only Name renders as OVER w; otherwise OVER (<base> parts...). Hand-built specs with Name plus parts keep their previous OVER (w PARTITION BY ...) output. WINDOW definitions no longer repeat their name inside AS (...). - formatter: windowSpecSQL writes BaseWindow instead of Name. - goldens: "extra/named window" now pins the corrected output and is stable; added refinement, parenthesized and chained-window cases and hand-built OVER name/base nodes. Verified the reproduction failed before the fix and round-trips byte for byte afterwards through ast.SQL(), formatter.FormatAST and gosqlx.Format in the default, postgresql and mysql dialects (inline window specs checked in all dialects). task check passes. Fixes #608 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
…NTERVAL '<n>' <unit> qualifiers (#609) Window frames accepted only ROWS/RANGE, had no EXCLUDE clause, and an ANSI-qualified INTERVAL literal could not be used as a frame offset: ... GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW -> E2002 at 1:32 ... ROWS ... CURRENT ROW EXCLUDE TIES -> E2002 at 1:73 ... RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND ... -> E2002 at 1:59 The interval failure had a general root cause: the unit after the quoted value was taken as an implicit column alias, so SELECT d + INTERVAL '1' DAY rendered as SELECT d + INTERVAL '1' AS DAY. Changes: - parser: parseWindowSpec starts a frame on GROUPS as well; parseWindowFrame parses an optional EXCLUDE {CURRENT ROW | GROUP | TIES | NO OTHERS} via the new parseFrameExclusion (invalid continuations are parse errors). - parser: parseIntervalExpression takes an optional unquoted unit qualifier after the string literal (DAY, YEAR TO MONTH, MySQL YEAR_MONTH, ...). TO is consumed only when a valid trailing field follows. Quoted identifiers and non-unit words (including plurals like DAYS) stay aliases; the MySQL numeric form INTERVAL 30 DAY is unchanged (unit kept in Value). - ast (additive): WindowFrame.Exclude and IntervalExpression.Qualifier string fields, rendered by SQL(); the IntervalExpression pool release already zeroes the whole struct and WindowFrame is not pooled. - formatter: windowFrameSQL emits EXCLUDE; intervals already render through IntervalExpression.SQL(). The CLI renderer emits EXCLUDE too. - docs: corrected the FunctionCall doc example (Type "PRECEDING" with a LiteralValue offset, as the parser produces) and the doc.go WindowFrame snippet; SQL_COMPATIBILITY.md lists GROUPS, EXCLUDE and the ANSI interval qualifier. Tests: pkg/gosqlx/issue_609_test.go (round-trip through SQL(), re-parse and FormatAST across default/postgresql/mysql/sqlite, plus gosqlx.Format), new parser AST/error tests, golden parse/node cases, and a CLI formatter case. The existing RANGE ... INTERVAL '1' YEAR PRECEDING coverage case now parses. Out of scope: dialect gating of GROUPS/EXCLUDE, MySQL INTERVAL 1 DAY rendering as INTERVAL '1 DAY', CLI rendering of IntervalExpression (#599), arithmetic on the right of a comparison (#596), interval field precision. Fixes #609 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
- F1 (must-fix, regression): the `gosqlx format` CLI renderer ignored WindowSpec.BaseWindow and never emitted SelectStatement.Windows, so inputs this PR newly accepts were silently rewritten with different semantics (`OVER (w ORDER BY z) ... WINDOW w AS (PARTITION BY y)` became `OVER (ORDER BY z)` and `format -i` overwrote the file with exit 0). formatWindowSpec now writes the base window first, the OVER caller renders a bare `OVER w` for named-window references (instead of `OVER ()`), and formatSelect emits the WINDOW clause between HAVING and ORDER BY. Added CLI regression tests covering refinement, bare references, chained window definitions, clause ordering, idempotency and the `format -i` path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve
f71328c to
f5433f6
Compare
|
Merge train (batch 3): rebased onto main (f5433f6).
|
|
Claude finished @ajitpratap0's task in 18s —— View job Review of PR #629 (#608 named windows, #609 GROUPS/EXCLUDE/INTERVAL)I read the parser, AST and SQL-rendering diffs. I did not run tests or the race gate, so I'm relying on the validator evidence in the PR description for that. Overall: No blocking issues. The change is additive and well scoped. The What looks good
Suggestions (non-blocking)
Performance and security
TestsCoverage is good. Tests fail on the parent, round-trips run across dialects, and the doc example is pinned by a golden test. The gaps are items 2–4 above. Looks good to merge once CI is green. |
Summary
#608 ast/formatter: named windows render as OVER (w) ... WINDOW w AS (w ...) and do not round-trip; golden file pins the broken output
Fixes #608. WindowSpec.Name was doing two jobs: the WINDOW definition name and the bare
OVER wreference. Both renderers wrote it inside the parentheses, soOVER w ... WINDOW w AS (PARTITION BY y)came out asOVER (w) ... WINDOW w AS (w PARTITION BY y), which fails to re-parse with E2002. The SQL:2003 refinementOVER (w ORDER BY z)did not parse in any dialect.The change adds one field, WindowSpec.BaseWindow, for the existing window that a parenthesized spec refines. The pool release path resets it. The parser fills BaseWindow, and ast.SQL() and pkg/formatter now render
OVER w,OVER (w ...)andWINDOW w AS (<base> ...)correctly. The non-standardWINDOW w AS w2is normalized toWINDOW w AS (w2).Verification:
Deferred:
gosqlx format, cmd/gosqlx/cmd/sql_formatter.go) still drops WINDOW and base-window names. That is cli:gosqlx formatsilently corrupts many expressions; switch the CLI to pkg/formatter #599, which replaces that renderer.#609 parser: window frames reject GROUPS, EXCLUDE and INTERVAL '1' DAY bounds; ast doc example uses Type:"2 PRECEDING"
This change makes window frames accept the GROUPS unit and the SQL:2011 EXCLUDE clause (CURRENT ROW, GROUP, TIES, NO OTHERS); an invalid continuation is a positioned E2002 parse error. INTERVAL literals now take an ANSI unit qualifier (
INTERVAL '1' DAY,INTERVAL '1-2' YEAR TO MONTH, MySQL units such as YEAR_MONTH), stored in a new additive fieldIntervalExpression.Qualifier. This fixes theRANGE BETWEEN INTERVAL '1' DAY PRECEDINGfailure and the oldd + INTERVAL '1' AS DAYmis-render.ast.SQL(),pkg/formatterand the CLI formatter all emit the newWindowFrame.Excludefield. The FunctionCall doc example now uses the parser's bound shape and is pinned by a golden test.Validation:
INTERVAL 30 DAY, existing ROWS/RANGE frames and ast/formatter: named windows render as OVER (w) ... WINDOW w AS (w ...) and do not round-trip; golden file pins the broken output #608 named windows give the same output as before.Deferred, out of scope:
INTERVAL 1 DAYrenders asINTERVAL '1 DAY'. Pre-existing; recommend a follow-up issue.gosqlx formatdrops INTERVAL values. Tracked in cli:gosqlx formatsilently corrupts many expressions; switch the CLI to pkg/formatter #599.Commits (2), one implementation commit per issue plus test-only follow-ups:
18 files changed, 1059 insertions(+), 37 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.#608 evidence (validator)
#608 race gate: PASS. Ran
CGO_ENABLED=1 go test -race -timeout 300s $(go list ./... | grep -v /cbinding)in the worktree at HEAD bbe520e: exit 0, 44 packages ok, 0 FAIL, 0 DATA RACE. Log: /private/tmp/claude-501/-Users-ajitpratapsingh-dev-GoSQLX/b99cd3cf-cb92-4294-a0db-d6a7c9a86253/scratchpad/b3/608/val/race.txt. No flakes and no re-runs needed.#609 evidence (validator)
#609 race gate: PASS. Ran CGO_ENABLED=1 go test -race -timeout 300s $(go list ./... | grep -v /cbinding) in the worktree at HEAD f71328c. Exit code 0: 44 packages ok (16 from the test cache, which is valid for identical code), no FAIL, no DATA RACE. No flakes, so no re-run was needed.
Breaking Changes
BaseWindow stringonast.WindowSpec, documented as the existing window name refined inside the parentheses. No signature changes;writeWindowSpecis unexported. Observable behaviour changes:OVER wnow renders asOVER w; it used to render asOVER (w).AS (...).OVER (w ...)andOVER (w)now parse, with BaseWindow set.WINDOW w AS w2now keeps w2, rendering asAS (w2).All of these turn previously invalid or non-round-tripping output into valid output. Hand-built OVER specs with Name plus parts render as before.
INTERVAL '<v>' <unit>is no longer parsed as INTERVAL '' plus a column alias named after the unit. No signatures change. The Value of existing interval forms is unchanged (the MySQL numeric form still stores "30 DAY").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 #608
Fixes #609
Follow-up to #566
🤖 Generated with Claude Code
https://claude.ai/code/session_01UZSk1292jPc8ipDboNxeve