From 9e5d23c8ec00732623c54d8d38c5a44bd6914e75 Mon Sep 17 00:00:00 2001 From: Tiago Lauer Date: Sun, 2 Aug 2026 09:28:58 -0300 Subject: [PATCH] fix(parse): recognize a window function using a named window Query // was { 'over w': number }[] `over w` refers to a window declared in a WINDOW clause - the other half of the syntax, where what follows OVER is a name rather than an inline definition. FindOverKeyword and SplitWindowExpression both wanted a paren directly after `over`, so the entry was not read as a window expression at all and fell through to the first-space bare-alias split, which made `over w` the alias of `sum(salary)`. The value came out right by coincidence; the key was nonsense. The [never] guard added alongside it is load-bearing: FindOverKeyword resolves to never for an entry with no OVER, and `never extends { expr: infer E extends string; rest: infer R extends string }` passes with both infers falling back to `string`, which the new named-window branch would have accepted as a window name - every column in the suite came back a window function until the guard went in. Fixes #301 Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 4 ++++ src/parse.ts | 24 +++++++++++++++++++----- tests/window.test-d.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 684fed8..b931a10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Notable changes to this project, following [Keep a Changelog](https://keepachang ## [Unreleased] +### Fixed + +- A window function using a named window (`sum(salary) over w ... window w as (order by id)`) is keyed by the function instead of by `over w`. Only the inline `over (...)` form was recognized, so the entry fell to the bare-alias split and the window reference became the alias ([#301](https://github.com/tiagolauer/OwlSQL/issues/301)). + ## [0.2.0] - 2026-07-29 ### Changed diff --git a/src/parse.ts b/src/parse.ts index 8f3ee9f..d8c2f79 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -412,13 +412,27 @@ type FindOverKeyword = type StripLeadingOpenParen = Trim extends `(${infer Rest}` ? Rest : never; -type SplitWindowExpression = FindOverKeyword extends { - expr: infer Expr extends string; - rest: infer Rest extends string; -} +// The [never] guard is load-bearing now that the paren is no longer required: +// FindOverKeyword resolves to never for an entry with no OVER at all, and +// `never extends { expr: infer E extends string; rest: infer R extends string }` +// passes with both infers falling back to `string` - which the named-window +// branch below would then accept as a window name. +type SplitWindowExpression = [FindOverKeyword] extends [never] + ? never + : FindOverKeyword extends { + expr: infer Expr extends string; + rest: infer Rest extends string; + } ? StripLeadingOpenParen extends infer AfterOpen extends string ? [AfterOpen] extends [never] - ? never + ? // `over w`, referring to a window declared in a WINDOW clause, is the + // other half of the syntax: what follows OVER is a name rather than an + // inline definition. Requiring the paren meant the entry was not + // recognized as a window expression at all, so it fell to the bare-alias + // split and `over w` became the alias of `sum(salary)` (issue #301). + Trim extends '' + ? never + : { expr: Expr; after: Trim>> } : ExtractParenGroup extends { rest: infer AfterClose extends string } ? { expr: Expr; after: Trim } : never diff --git a/tests/window.test-d.ts b/tests/window.test-d.ts index 1ab4146..404f537 100644 --- a/tests/window.test-d.ts +++ b/tests/window.test-d.ts @@ -39,9 +39,37 @@ type WindowFunctionAlongsideRegularColumn = Expect< > >; +// Regression for #301: `over w` refers to a window declared in a WINDOW +// clause, the other half of the syntax. Requiring a paren after OVER meant the +// entry was not read as a window expression at all, so it fell to the +// bare-alias split and `over w` became the alias of `sum(salary)`. +type NamedWindowKeepsTheFunctionName = Expect< + Equal< + Query, + { sum: number }[] + > +>; + +type NamedWindowWithAlias = Expect< + Equal< + Query, + { total: number }[] + > +>; + +type NamedWindowAlongsideRegularColumn = Expect< + Equal< + Query, + { dept: string; rank: number }[] + > +>; + export type WindowLock = [ RowNumberWithAlias, RankWithoutAliasDefaultsToFunctionName, EmptyOverClause, WindowFunctionAlongsideRegularColumn, + NamedWindowKeepsTheFunctionName, + NamedWindowWithAlias, + NamedWindowAlongsideRegularColumn, ];