Skip to content

Add aggregate and navigation window functions with ROWS/RANGE frame support#23

Merged
koenbeuk merged 2 commits intomainfrom
feat/more-window-functions
Apr 6, 2026
Merged

Add aggregate and navigation window functions with ROWS/RANGE frame support#23
koenbeuk merged 2 commits intomainfrom
feat/more-window-functions

Conversation

@koenbeuk
Copy link
Copy Markdown
Collaborator

@koenbeuk koenbeuk commented Apr 6, 2026

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.

  • Aggregate: SUM, AVG, COUNT, MIN, MAX with optional ROWS/RANGE BETWEEN frames
  • Navigation: LAG, LEAD, FIRST_VALUE, LAST_VALUE, NTH_VALUE
  • Ranking: PERCENT_RANK, CUME_DIST (completing the ranking set)
  • Window frame infrastructure: WindowFrameBound, FramedWindowDefinition,
    RowsBetween/RangeBetween on OrderedWindowDefinition
  • Frames correctly restricted to aggregate/navigation functions only
    (SQL standard forbids frames on ranking functions)
  • 32 integration tests across all function categories
  • Unit tests for internal SQL expression rendering

Copilot AI review requested due to automatic review settings April 6, 2026 00:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 WindowFrameBound usage 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
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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=...>.

Suggested change
/// Currently handles only <see cref="WindowFrameBound"/> property getters
/// Currently handles only <see cref="ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.WindowFunctions.WindowFrameBound"/> property getters

Copilot uses AI. Check for mistakes.
Comment on lines +365 to +366
::: 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.
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
::: 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.

Copilot uses AI. Check for mistakes.
:::

::: 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.
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +34
WindowFrameBoundKind.Preceding => $"{Offset} PRECEDING",
WindowFrameBoundKind.CurrentRow => "CURRENT ROW",
WindowFrameBoundKind.Following => $"{Offset} FOLLOWING",
WindowFrameBoundKind.UnboundedFollowing => "UNBOUNDED FOLLOWING",
_ => throw new InvalidOperationException($"Unknown WindowFrameBoundKind: {Kind}"),
};
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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;
}

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,43 @@
using System.Reflection;
using ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.WindowFunctions;
using Microsoft.EntityFrameworkCore;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using Microsoft.EntityFrameworkCore; appears unused in this file. With TreatWarningsAsErrors=true, CS8019 can fail the build—please remove the unnecessary using directive.

Suggested change
using Microsoft.EntityFrameworkCore;

Copilot uses AI. Check for mistakes.
@koenbeuk koenbeuk merged commit a0e48e7 into main Apr 6, 2026
13 of 15 checks passed
@koenbeuk koenbeuk deleted the feat/more-window-functions branch April 6, 2026 01:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants