Skip to content

Add totalseconds canonical function binding - #1578

Open
RicardoGS98 wants to merge 2 commits into
OData:mainfrom
RicardoGS98:feature/totalseconds-canonical-function
Open

Add totalseconds canonical function binding#1578
RicardoGS98 wants to merge 2 commits into
OData:mainfrom
RicardoGS98:feature/totalseconds-canonical-function

Conversation

@RicardoGS98

@RicardoGS98 RicardoGS98 commented May 19, 2026

Copy link
Copy Markdown

Summary

Implements the OData V4 canonical function totalseconds(Edm.Duration), which is recognized by the URI parser but is not bound by the LINQ expression binder — currently producing Unknown function 'totalseconds' at query time (see SRResources.ODataFunctionNotSupported).

Per the spec, totalseconds returns an Edm.Decimal representing the duration as a fractional number of seconds. The CLR mapping is TimeSpan.TotalSeconds cast to decimal.

This also makes the common diff-between-dates pattern work out of the box:

$filter=totalseconds(EndDate sub StartDate) gt 3600

because the OData parser types DateTimeOffset sub DateTimeOffset as Edm.Duration, and the binary subtract binder already produces a TimeSpan LINQ expression, which BindTotalSeconds then consumes.

Changes

  • ClrCanonicalFunctions: add TotalSecondsFunctionName and TimeSpanTotalSeconds PropertyInfo.
  • QueryBinder.SingleValueFunctionCall: route totalseconds to a new virtual BindTotalSeconds method.
  • AllowedFunctions: add TotalSeconds = 0x40000000; include it in AllDateTimeFunctions (and transitively in AllFunctions).
  • FilterQueryValidator + QueryValidatorHelpers: map "totalseconds" to AllowedFunctions.TotalSeconds in both (they were duplicated).
  • Tests:
    • FilterBinderTests: covers totalseconds(TimeSpanProp) and totalseconds(DateTimeOffset sub DateTimeOffset).
    • FilterQueryValidatorTests and AllowedFunctionsTests: data sets updated so the group/exclude tests cover the new flag.
  • PublicAPI.Unshipped.txt and PublicApi.bsl baselines updated.

Note on totaloffsetminutes

I considered including totaloffsetminutes in the same change since it tends to come up together, but mapping it requires the sign bit (0x80000000) of the AllowedFunctions enum, which makes AllFunctions negative and breaks the value > AllowedFunctions.AllFunctions range check in ODataValidationSettings. That would need either widening the underlying type to long (binary breaking) or a separate validation strategy, so I left it out of this PR.

Test plan

  • dotnet build src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.csproj — 0 errors.
  • dotnet test test/Microsoft.AspNetCore.OData.Tests/Microsoft.AspNetCore.OData.Tests.csproj — 6336 passed, 5 skipped, 0 failed (locally).
  • PublicAPI baseline regenerated and matched.
  • Reviewer to verify EDM/URI parser already exposes the canonical signature for totalseconds(Edm.Duration) without further model registration (it does on the cases I tested; flagging for confirmation).

🤖 Generated with Claude Code

Implements the OData V4 canonical function totalseconds(Edm.Duration)
which was recognized by the URI parser but not bound by the LINQ
expression binder, causing "Unknown function 'totalseconds'" errors at
query time.

Maps to TimeSpan.TotalSeconds cast to decimal (per spec). Also works
naturally when the argument is a DateTimeOffset subtraction, since the
parser types that as Edm.Duration:

  $filter=totalseconds(EndDate sub StartDate) gt 3600

Adds:
- ClrCanonicalFunctions.TotalSecondsFunctionName and PropertyInfo for
  TimeSpan.TotalSeconds
- QueryBinder.BindTotalSeconds (virtual) routed from
  BindSingleValueFunctionCallNode
- AllowedFunctions.TotalSeconds flag, included in AllDateTimeFunctions
- FilterQueryValidator and QueryValidatorHelpers map "totalseconds" to
  the new flag
- Unit tests covering TimeSpan property and DateTimeOffset subtraction
- PublicAPI baselines updated

Note: totaloffsetminutes was intentionally not added in the same change
because it would require the high bit (0x80000000), which breaks the
existing signed-int range check in ODataValidationSettings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@RicardoGS98

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@WanjohiSammy

Copy link
Copy Markdown
Member

@RicardoGS98
Please add E2E tests

Addresses the review feedback on OData#1578 asking for E2E coverage.

Adds test/Microsoft.AspNetCore.OData.E2E.Tests/DurationFunctions/ with 37 cases over
a 5-row in-memory data source. Because the source is in-memory the LINQ provider is
System.Linq, so HandleNullPropagation resolves to True: these are the first tests to
exercise the null propagation branch of BindTotalSeconds, which the FilterBinderTests
unit tests (running with it False) never reached.

Coverage:
- $filter with gt/lt/ge+le/eq/ne on a non-nullable Edm.Duration, including eq 0 and
  the constant-on-the-left form
- nullable Edm.Duration: eq null, ne null and numeric comparisons
- totalseconds(End sub Start) on DateTimeOffset subtraction, including a negative
  duration and a lifted subtraction (NullableEnd sub Start)
- $orderby asc/desc on both the nullable and the non-nullable property
- $compute combined with $filter, $orderby and $select
- a fractional case (PT1.5S) asserted both via $filter and via an exact payload,
  showing the result is Edm.Decimal and not a truncated integer
- AllowedFunctions validation: 400 for both $filter and $orderby when TotalSeconds is
  excluded, 200 under AllDateTimeFunctions, with symmetric controls

The suite has no EF Core or LocalDB dependency: TimeSpan.TotalSeconds is not
translatable by EF Core, consistent with the existing "EFCore could not be translated"
notes in DateAndTimeOfDayWithEfTest.

Also adds the unit tests that were missing:
- DateFunctions_TotalSecondsFunction_Nullable, for parity with fractionalseconds
  (needs a TimeSpan? property on the Product test model)
- two OrderByQueryValidatorTests cases: QueryValidatorHelpers.ToODataFunction is only
  reached through $orderby and had no coverage at all

And completes the public API baseline: adding TotalSeconds to AllDateTimeFunctions
also changes the value of AllFunctions, and the analyzer serializes enum flags in
ascending order of value, so AllDateTimeFunctions moves to the end of the AllFunctions
decomposition. Without that entry the build emits RS0016 and RS0017.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ2xdVpZtVBDKog32Jjuy7
@RicardoGS98

Copy link
Copy Markdown
Author

@RicardoGS98 Please add E2E tests

Thanks for the review — E2E coverage added in test/Microsoft.AspNetCore.OData.E2E.Tests/DurationFunctions/
(5 new files, no existing E2E test modified). 37 E2E cases over a 5-row in-memory dataset.

@RicardoGS98 RicardoGS98 reopened this Jul 27, 2026
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