Skip to content

Commit d026d96

Browse files
committed
Aggregate window functions: SUM/AVG/COUNT/COUNT_BIG/MIN/MAX/STDEV/STDEVP/VAR/VARP/CHECKSUM_AGG/APPROX_COUNT_DISTINCT(...) OVER (PARTITION BY ...) with implicit-frame whole-partition default; ORDER BY in OVER and explicit ROWS/RANGE BETWEEN raise NotSupportedException; placement / DISTINCT / STRING_AGG misuse raise Msg 4108 / 10759 / 4113.
1 parent 16fe863 commit d026d96

9 files changed

Lines changed: 663 additions & 63 deletions

File tree

CLAUDE.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,13 @@ Top-level OFFSET/FETCH (post-set-op chain) attaches alongside the top-level ORDE
179179
`COUNT(*)` / `COUNT(expr)` / `COUNT(DISTINCT expr)` / `COUNT_BIG`, `SUM` / `AVG` (integer-truncating; `decimal(38, max(s, 6))` widening for AVG over decimals), `MAX` / `MIN`, statistical (`STDEV` / `STDEVP` / `VAR` / `VARP`), `STRING_AGG`, `CHECKSUM_AGG`, `APPROX_COUNT_DISTINCT`. Standalone and inside `GROUP BY` / `HAVING`.
180180

181181
### Window functions
182-
`ROW_NUMBER() OVER([PARTITION BY <expr-list>] ORDER BY <expr-list-with-direction>)` only — the single shape EF Core 10 emits for top-N / Skip+Take per group. `RANK` / `DENSE_RANK` / analytic family (`LAG` / `LEAD` / `FIRST_VALUE`) and frame specs (`ROWS BETWEEN`, `RANGE BETWEEN`) aren't modeled — EF Core 10 doesn't emit them from idiomatic LINQ. Result type is `bigint`. ORDER BY is required inside OVER (without it, parse fails — SQL Server raises Msg 4112).
182+
Two shapes are modeled:
183+
- `ROW_NUMBER() OVER([PARTITION BY <expr-list>] ORDER BY <expr-list-with-direction>)` — for EF Core 10's top-N / Skip+Take per group. Result type `bigint`. ORDER BY is required inside OVER (parse fails otherwise — SQL Server raises Msg 4112).
184+
- Aggregate windows `SUM/AVG/COUNT/COUNT_BIG/MIN/MAX/STDEV/STDEVP/VAR/VARP/CHECKSUM_AGG/APPROX_COUNT_DISTINCT(<expr>) OVER ([PARTITION BY <expr-list>])` — implicit-frame whole-partition default only (no ORDER BY in OVER for aggregates). Result types match the plain aggregate forms (probe-confirmed against SQL Server 2025, 2026-05-08): `SUM(int)` stays int (overflow → Msg 8115), `SUM(decimal(p,s))``decimal(38,s)`, `AVG(int)` truncates, `AVG(decimal(p,s))``decimal(38, max(s,6))`, `COUNT*` → int, `COUNT_BIG`/`APPROX_COUNT_DISTINCT` → bigint, `MIN`/`MAX` preserve input type, `STDEV`/`VAR` family → float. NULL semantics also match plain aggregates: all-NULL partition → NULL except COUNT(col) which returns 0.
183185

184-
`WindowExpression` (`Parser/Expressions/WindowExpression.cs`) registers itself in `ParserContext.WindowCollector` like aggregates do with `AggregateCollector`. The executor's `ProjectWindowedRows` buffers post-WHERE tuples, partitions by each window's PARTITION BY keys, sorts each partition by ORDER BY keys, assigns row numbers per partition, then walks the buffer in original order binding per-tuple results before projecting. Combining window functions with GROUP BY / HAVING / aggregates raises `NotSupportedException`.
186+
`RANK` / `DENSE_RANK` / analytic family (`LAG` / `LEAD` / `FIRST_VALUE`), explicit frame specs (`ROWS BETWEEN`, `RANGE BETWEEN`), and aggregate-window ORDER BY (which would require modeling SQL Server's RANGE-with-tie-grouping default frame) aren't modeled — EF Core 10 doesn't emit them from idiomatic LINQ. The frame keywords parse and raise `NotSupportedException` rather than landing as Msg 102, so users hitting the wall get a diagnostic naming the unsupported feature. `STRING_AGG OVER` raises **Msg 4113** (`"The function 'string_agg' is not a valid windowing function..."`); `COUNT(DISTINCT ...) OVER` (and `SUM(DISTINCT) OVER`) raise **Msg 10759** (`"Use of DISTINCT is not allowed with the OVER clause."`); a windowed function in WHERE / HAVING / GROUP BY / ON raises **Msg 4108** (`"Windowed functions can only appear in the SELECT or ORDER BY clauses."`) — gated parse-time by `ParserContext.AllowsWindowExpressions` toggling around the WHERE / GROUP BY / HAVING parses.
187+
188+
`WindowExpression` (`Parser/Expressions/WindowExpression.cs`) carries a `WindowKind` discriminator (`RowNumber` / `Aggregate`) and an `AggregateInfo: AggregateExpression?` slot for the wrapped aggregate when `Aggregate`. The aggregate-OVER parse path runs through `WindowExpression.WrapAggregate`, which is invoked from `Expression.Parse`'s binary loop when an `AggregateExpression` is followed by `OVER`. The wrap method pops the aggregate from `AggregateCollector` (since it's window-evaluated, not group-evaluated) and registers the window in `WindowCollector`. The executor's `ProjectWindowedRows` partitions by each window's PARTITION BY keys; for ROW_NUMBER it sorts each partition and assigns 1..N ranks; for aggregates it builds one `Aggregator` per partition, accumulates across all rows in insertion order, and broadcasts `Result()` to every row. Pre-resolved operand and result types (computed in `BuildSqlProjection` at parse time) are passed through to avoid a runtime column-type resolver. Combining window functions with GROUP BY / HAVING / aggregates in the same SELECT still raises `NotSupportedException`.
185189

186190
EF Core 10 always wraps ROW_NUMBER in a derived-table subquery: `SELECT ... FROM (SELECT cols, ROW_NUMBER() OVER(...) AS row FROM T) AS sub WHERE sub.row <= N` (Take) or `WHERE 1 < sub.row AND sub.row <= K` (Skip+Take). The simulator's plain-derived-table-doesn't-see-outer-scope limitation doesn't bite here because the ROW_NUMBER subquery has no outer correlation — its OVER refers only to the inner FROM.
187191

@@ -270,7 +274,7 @@ Type-metadata accessors (`GetDataTypeName` / `GetFieldType`) read from `Simulate
270274
- `ANY` / `SOME` / `ALL` quantifiers.
271275
- `UNION` / `UNION ALL` inside a subquery body.
272276
- Row-constructor `IN ((1,2), (3,4))`.
273-
- Window functions other than `ROW_NUMBER`: `RANK` / `DENSE_RANK`, analytic (`LAG` / `LEAD` / `FIRST_VALUE` / `LAST_VALUE`), aggregate-OVER form (`SUM(x) OVER(...)` / `COUNT(*) OVER(...)`), frame specs (`ROWS BETWEEN` / `RANGE BETWEEN`). EF Core 10 doesn't emit any of these from idiomatic LINQ.
277+
- Window functions other than `ROW_NUMBER` and the aggregate-OVER family (`SUM` / `AVG` / `COUNT` / `COUNT_BIG` / `MIN` / `MAX` / statistical / `CHECKSUM_AGG` / `APPROX_COUNT_DISTINCT`): `RANK` / `DENSE_RANK`, analytic (`LAG` / `LEAD` / `FIRST_VALUE` / `LAST_VALUE`), explicit frame specs (`ROWS BETWEEN` / `RANGE BETWEEN`), and ORDER BY inside OVER for aggregate windows (which would require RANGE-with-tie-grouping default frame fidelity). EF Core 10 doesn't emit any of these from idiomatic LINQ. The frame and ORDER-BY-in-aggregate-OVER paths parse + raise `NotSupportedException` (not silent Msg 102) so users get a diagnostic.
274278
- `STRING_AGG`'s `WITHIN GROUP (ORDER BY ...)`.
275279
- `LIKE` with `COLLATE` override (default collation only — case-insensitive Latin1_General-shaped).
276280
- `CONVERT` / `TRY_CONVERT` style codes other than `0` / `120` / `121` for date-like → string. Other styles raise Msg 281; money / float / binary style codes and `CONVERT(date, str, 103)`-style date parsing not modeled.

0 commit comments

Comments
 (0)