Add aggregate and navigation window functions with ROWS/RANGE frame support#23
Add aggregate and navigation window functions with ROWS/RANGE frame support#23
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
...xpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs
Fixed
Show fixed
Hide fixed
...xpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs
Fixed
Show fixed
Hide fixed
...xpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs
Fixed
Show fixed
Hide fixed
...xpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs
Fixed
Show fixed
Hide fixed
...xpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs
Fixed
Show fixed
Hide fixed
There was a problem hiding this comment.
Pull request overview
Adds support for a broader set of SQL window functions in the EF Core relational extensions layer, including aggregate and navigation/value functions, and introduces ROWS/RANGE frame clause support throughout the translation pipeline.
Changes:
- Extend window specification + window function SQL expressions to carry and render
ROWS/RANGE BETWEEN ... AND ...frames. - Add window frame bound infrastructure and EF Core translators (method + member) to translate
WindowFrameBoundusage into intermediate SQL nodes. - Add unit + integration test coverage and update documentation to describe the expanded window-function surface area.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/ExpressiveSharp.Tests/RelationalExtensions/WindowSpecSqlExpressionFrameTests.cs | Unit tests for WindowSpecSqlExpression frame fields + builder behavior. |
| tests/ExpressiveSharp.Tests/RelationalExtensions/WindowFunctionSqlExpressionFrameTests.cs | Unit tests for SQL rendering of framed window function expressions. |
| tests/ExpressiveSharp.Tests/RelationalExtensions/WindowFrameBoundSqlExpressionTests.cs | Unit tests for bound formatting and bound expression equality. |
| tests/ExpressiveSharp.Tests/ExpressiveSharp.Tests.csproj | Adds references needed for the new relational extensions unit tests. |
| tests/ExpressiveSharp.EntityFrameworkCore.IntegrationTests/Infrastructure/WindowFunctionTestBase.cs | Adds broad integration tests for aggregates, frames, navigation/value, and ranking functions. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/RelationalExpressivePlugin.cs | Registers EF Core member translator plugin for WindowFrameBound property translation. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowSpecSqlExpression.cs | Adds frame fields to window spec intermediate node + printing/equality updates. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowSpecMethodCallTranslator.cs | Adds translation for RowsBetween/RangeBetween and bound factories. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionSqlExpression.cs | Adds optional frame emission to window function SQL rendering + equality/hash updates. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionMethodCallTranslator.cs | Adds translation for aggregate, navigation/value, and additional ranking functions; propagates frames where appropriate. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionMemberTranslatorPlugin.cs | New EF Core plugin to register WindowFrameBound member translation. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionEvaluatableExpressionFilter.cs | Prevents client evaluation of WindowFrameBound calls and property accesses. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFrameType.cs | New internal enum for ROWS vs RANGE selection. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFrameBoundSqlExpression.cs | New intermediate SQL node representing a single frame boundary. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFrameBoundMemberTranslator.cs | Translates static WindowFrameBound property getters into intermediate bound nodes. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFrameBoundInfo.cs | New internal bound-kind + offset representation and SQL fragment formatting. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.csproj | Exposes internals to the test assembly for new unit tests. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowFunction.cs | Expands public window function stubs (ranking/aggregate/navigation/value). |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowFrameBound.cs | New public marker/factory type for frame boundaries. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowDefinition.cs | Adds RowsBetween/RangeBetween and introduces FramedWindowDefinition. |
| README.md | Updates feature matrix entry to reflect expanded window-function support. |
| docs/recipes/window-functions-ranking.md | Documents aggregate window functions and frames (plus warning block). |
| docs/guide/window-functions.md | Updates guide with new functions, frame API, and examples. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| /// <summary> | ||
| /// Registers member access translators with EF Core's query pipeline. | ||
| /// Currently handles only <see cref="WindowFrameBound"/> property getters |
There was a problem hiding this comment.
The XML doc cref to WindowFrameBound is not in scope in this file (no using ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.WindowFunctions; / fully-qualified name), which can trigger CS1574. With TreatWarningsAsErrors=true, this may break the build—please add the using or fully-qualify the type in the <see cref=...>.
| /// Currently handles only <see cref="WindowFrameBound"/> property getters | |
| /// Currently handles only <see cref="ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.WindowFunctions.WindowFrameBound"/> property getters |
| ::: warning Frames apply to aggregate functions only | ||
| The SQL standard forbids frame clauses on ranking functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE). SQL Server and PostgreSQL reject the syntax. Only aggregate functions (SUM, AVG, COUNT, MIN, MAX) support frames. |
There was a problem hiding this comment.
This warning claims that only aggregate functions support frames, but this PR also adds FIRST_VALUE / LAST_VALUE / NTH_VALUE support with ROWS/RANGE frames (and those functions do accept frames in SQL). Please reword the warning to state that frames are forbidden only on ranking functions (ROW_NUMBER/RANK/DENSE_RANK/NTILE/etc.), rather than implying frames are aggregate-only.
| ::: warning Frames apply to aggregate functions only | |
| The SQL standard forbids frame clauses on ranking functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE). SQL Server and PostgreSQL reject the syntax. Only aggregate functions (SUM, AVG, COUNT, MIN, MAX) support frames. | |
| ::: warning Frames are not allowed on ranking functions | |
| The SQL standard forbids frame clauses on ranking functions such as `ROW_NUMBER`, `RANK`, `DENSE_RANK`, and `NTILE`. SQL Server and PostgreSQL reject that syntax. Frames are valid for aggregate window functions and some value window functions, but not for ranking functions. |
docs/guide/window-functions.md
Outdated
| ::: | ||
|
|
||
| ::: warning Ranking functions don't support frames | ||
| The SQL standard forbids frame clauses on ranking functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE) — SQL Server and PostgreSQL will reject the query. Only aggregate functions (SUM, AVG, COUNT, MIN, MAX) accept frames. |
There was a problem hiding this comment.
This warning says only aggregate functions accept frames, but the same page documents FIRST_VALUE/LAST_VALUE/NTH_VALUE (which support frames) and the PR adds frame support for them. Please adjust the wording to: ranking functions don't support frames; aggregate and value functions may.
| The SQL standard forbids frame clauses on ranking functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE) — SQL Server and PostgreSQL will reject the query. Only aggregate functions (SUM, AVG, COUNT, MIN, MAX) accept frames. | |
| The SQL standard forbids frame clauses on ranking functions (ROW_NUMBER, RANK, DENSE_RANK, NTILE) — SQL Server and PostgreSQL will reject the query. Aggregate functions (SUM, AVG, COUNT, MIN, MAX) and value functions such as FIRST_VALUE, LAST_VALUE, and NTH_VALUE may accept frames. |
| WindowFrameBoundKind.Preceding => $"{Offset} PRECEDING", | ||
| WindowFrameBoundKind.CurrentRow => "CURRENT ROW", | ||
| WindowFrameBoundKind.Following => $"{Offset} FOLLOWING", | ||
| WindowFrameBoundKind.UnboundedFollowing => "UNBOUNDED FOLLOWING", | ||
| _ => throw new InvalidOperationException($"Unknown WindowFrameBoundKind: {Kind}"), | ||
| }; |
There was a problem hiding this comment.
ToSqlFragment() interpolates Offset for Preceding/Following without validating it is non-null and non-negative. If a WindowFrameBoundInfo is ever constructed with Kind=Preceding/Following and Offset=null (or < 0), this will emit invalid SQL (e.g. " PRECEDING" / "-1 FOLLOWING"). Consider enforcing invariants (throw in ToSqlFragment, or validate in the record ctor) so invalid bounds fail fast.
| WindowFrameBoundKind.Preceding => $"{Offset} PRECEDING", | |
| WindowFrameBoundKind.CurrentRow => "CURRENT ROW", | |
| WindowFrameBoundKind.Following => $"{Offset} FOLLOWING", | |
| WindowFrameBoundKind.UnboundedFollowing => "UNBOUNDED FOLLOWING", | |
| _ => throw new InvalidOperationException($"Unknown WindowFrameBoundKind: {Kind}"), | |
| }; | |
| WindowFrameBoundKind.Preceding => $"{GetValidatedOffset()} PRECEDING", | |
| WindowFrameBoundKind.CurrentRow => "CURRENT ROW", | |
| WindowFrameBoundKind.Following => $"{GetValidatedOffset()} FOLLOWING", | |
| WindowFrameBoundKind.UnboundedFollowing => "UNBOUNDED FOLLOWING", | |
| _ => throw new InvalidOperationException($"Unknown WindowFrameBoundKind: {Kind}"), | |
| }; | |
| private int GetValidatedOffset() | |
| { | |
| if (!Offset.HasValue) | |
| { | |
| throw new InvalidOperationException( | |
| $"Window frame bound '{Kind}' requires a non-null offset."); | |
| } | |
| if (Offset.Value < 0) | |
| { | |
| throw new InvalidOperationException( | |
| $"Window frame bound '{Kind}' requires a non-negative offset, but got {Offset.Value}."); | |
| } | |
| return Offset.Value; | |
| } |
| @@ -0,0 +1,43 @@ | |||
| using System.Reflection; | |||
| using ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.WindowFunctions; | |||
| using Microsoft.EntityFrameworkCore; | |||
There was a problem hiding this comment.
using Microsoft.EntityFrameworkCore; appears unused in this file. With TreatWarningsAsErrors=true, CS8019 can fail the build—please remove the unnecessary using directive.
| using Microsoft.EntityFrameworkCore; |
… COUNT and AVG functions
Introduce support for aggregate window functions such as SUM, AVG, COUNT, MIN, and MAX, along with ROWS and RANGE frame specifications. Enhance the query pipeline with new window frame types and member translators to facilitate these features. Update documentation to reflect the new capabilities.
RowsBetween/RangeBetween on OrderedWindowDefinition
(SQL standard forbids frames on ranking functions)