Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 19 additions & 5 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,13 +412,27 @@ type FindOverKeyword<S extends string, Accumulated extends string = ''> =

type StripLeadingOpenParen<S extends string> = Trim<S> extends `(${infer Rest}` ? Rest : never;

type SplitWindowExpression<Entry extends string> = FindOverKeyword<Entry> 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<Entry extends string> = [FindOverKeyword<Entry>] extends [never]
? never
: FindOverKeyword<Entry> extends {
expr: infer Expr extends string;
rest: infer Rest extends string;
}
? StripLeadingOpenParen<Rest> 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<Rest> extends ''
? never
: { expr: Expr; after: Trim<DropFirstWord<Trim<Rest>>> }
: ExtractParenGroup<AfterOpen> extends { rest: infer AfterClose extends string }
? { expr: Expr; after: Trim<AfterClose> }
: never
Expand Down
28 changes: 28 additions & 0 deletions tests/window.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DB, 'select sum(salary) over w from employees window w as (order by id)'>,
{ sum: number }[]
>
>;

type NamedWindowWithAlias = Expect<
Equal<
Query<DB, 'select sum(salary) over w as total from employees window w as (order by id)'>,
{ total: number }[]
>
>;

type NamedWindowAlongsideRegularColumn = Expect<
Equal<
Query<DB, 'select dept, rank() over w from employees window w as (order by salary)'>,
{ dept: string; rank: number }[]
>
>;

export type WindowLock = [
RowNumberWithAlias,
RankWithoutAliasDefaultsToFunctionName,
EmptyOverClause,
WindowFunctionAlongsideRegularColumn,
NamedWindowKeepsTheFunctionName,
NamedWindowWithAlias,
NamedWindowAlongsideRegularColumn,
];