Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SqlServer: Translate TimeSpan members #19774

Merged
merged 9 commits into from
Feb 19, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public SqlServerMemberTranslatorProvider([NotNull] RelationalMemberTranslatorPro
new IMemberTranslator[]
{
new SqlServerDateTimeMemberTranslator(sqlExpressionFactory),
new SqlServerStringMemberTranslator(sqlExpressionFactory)
new SqlServerStringMemberTranslator(sqlExpressionFactory),
new SqlServerTimeSpanMemberTranslator(sqlExpressionFactory)
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal
{
public class SqlServerTimeSpanMemberTranslator : IMemberTranslator
{
private static readonly Dictionary<string, string> _datePartMappings = new Dictionary<string, string>
{
{ nameof(TimeSpan.Hours), "hour" },
{ nameof(TimeSpan.Minutes), "minute" },
{ nameof(TimeSpan.Seconds), "second" },
{ nameof(TimeSpan.Milliseconds), "millisecond" }
};

private readonly ISqlExpressionFactory _sqlExpressionFactory;

public SqlServerTimeSpanMemberTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member, Type returnType)
{
Check.NotNull(member, nameof(member));
Check.NotNull(returnType, nameof(returnType));

if (member.DeclaringType == typeof(TimeSpan) && _datePartMappings.TryGetValue(member.Name, out string value))
{
return _sqlExpressionFactory.Function("DATEPART", new []
{
_sqlExpressionFactory.Fragment(value),
instance
}, returnType);
}

return null;
}
}
}
80 changes: 80 additions & 0 deletions test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7533,6 +7533,86 @@ public virtual Task Checked_context_throws_on_client_evaluation(bool isAsync)

private int GetThreatLevel() => 256;

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task TimeSpan_Hours(bool async)
bricelam marked this conversation as resolved.
Show resolved Hide resolved
{
return AssertQueryScalar(
async,
ss => ss.Set<Mission>()
.Select(m => m.Duration.Hours));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task TimeSpan_Minutes(bool async)
{
return AssertQueryScalar(
async,
ss => ss.Set<Mission>()
.Select(m => m.Duration.Minutes));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task TimeSpan_Seconds(bool async)
{
return AssertQueryScalar(
async,
ss => ss.Set<Mission>()
.Select(m => m.Duration.Seconds));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task TimeSpan_Milliseconds(bool async)
{
return AssertQueryScalar(
async,
ss => ss.Set<Mission>()
.Select(m => m.Duration.Milliseconds));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeSpan_Hours(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Mission>()
.Where(m => m.Duration.Hours == 1));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeSpan_Minutes(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Mission>()
.Where(m => m.Duration.Minutes == 1));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeSpan_Seconds(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Mission>()
.Where(m => m.Duration.Seconds == 1));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeSpan_Milliseconds(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Mission>()
.Where(m => m.Duration.Milliseconds == 1));
}

protected GearsOfWarContext CreateContext() => Fixture.CreateContext();

protected virtual void ClearLog()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,24 @@ public static IReadOnlyList<Mission> CreateMissions()
Id = 1,
CodeName = "Lightmass Offensive",
Rating = 2.1,
Timeline = new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0))
Timeline = new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0)),
Duration = new TimeSpan(1, 2, 3)
},
new Mission
{
Id = 2,
CodeName = "Hollow Storm",
Rating = 4.2,
Timeline = new DateTimeOffset(2, 3, 1, 8, 0, 0, new TimeSpan(-5, 0, 0))
Timeline = new DateTimeOffset(2, 3, 1, 8, 0, 0, new TimeSpan(-5, 0, 0)),
Duration = new TimeSpan(0, 1, 2, 3, 456)
},
new Mission
{
Id = 3,
CodeName = "Halvo Bay defense",
Rating = null,
Timeline = new DateTimeOffset(10, 5, 3, 12, 0, 0, new TimeSpan())
Timeline = new DateTimeOffset(10, 5, 3, 12, 0, 0, new TimeSpan()),
Duration = new TimeSpan(0, 1, 0, 15, 456)
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class Mission
public string CodeName { get; set; }
public double? Rating { get; set; }
public DateTimeOffset Timeline { get; set; }
public TimeSpan Duration { get; set; }

public virtual ICollection<SquadMission> ParticipatingSquads { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2626,7 +2626,7 @@ public override async Task Where_datetimeoffset_now(bool async)
await base.Where_datetimeoffset_now(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE [m].[Timeline] <> SYSDATETIMEOFFSET()");
}
Expand All @@ -2636,7 +2636,7 @@ public override async Task Where_datetimeoffset_utcnow(bool async)
await base.Where_datetimeoffset_utcnow(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE [m].[Timeline] <> CAST(SYSUTCDATETIME() AS datetimeoffset)");
}
Expand All @@ -2657,7 +2657,7 @@ public override async Task Where_datetimeoffset_year_component(bool async)
await base.Where_datetimeoffset_year_component(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(year, [m].[Timeline]) = 2");
}
Expand All @@ -2667,7 +2667,7 @@ public override async Task Where_datetimeoffset_month_component(bool async)
await base.Where_datetimeoffset_month_component(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(month, [m].[Timeline]) = 1");
}
Expand All @@ -2677,7 +2677,7 @@ public override async Task Where_datetimeoffset_dayofyear_component(bool async)
await base.Where_datetimeoffset_dayofyear_component(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(dayofyear, [m].[Timeline]) = 2");
}
Expand All @@ -2687,7 +2687,7 @@ public override async Task Where_datetimeoffset_day_component(bool async)
await base.Where_datetimeoffset_day_component(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(day, [m].[Timeline]) = 2");
}
Expand All @@ -2697,7 +2697,7 @@ public override async Task Where_datetimeoffset_hour_component(bool async)
await base.Where_datetimeoffset_hour_component(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(hour, [m].[Timeline]) = 10");
}
Expand All @@ -2707,7 +2707,7 @@ public override async Task Where_datetimeoffset_minute_component(bool async)
await base.Where_datetimeoffset_minute_component(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(minute, [m].[Timeline]) = 0");
}
Expand All @@ -2717,7 +2717,7 @@ public override async Task Where_datetimeoffset_second_component(bool async)
await base.Where_datetimeoffset_second_component(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(second, [m].[Timeline]) = 0");
}
Expand All @@ -2727,7 +2727,7 @@ public override async Task Where_datetimeoffset_millisecond_component(bool async
await base.Where_datetimeoffset_millisecond_component(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(millisecond, [m].[Timeline]) = 0");
}
Expand Down Expand Up @@ -6548,7 +6548,7 @@ public override async Task DateTimeOffset_Contains_Less_than_Greater_than(bool a
@"@__start_0='1902-01-01T10:00:00.1234567+01:30'
@__end_1='1902-01-03T10:00:00.1234567+01:30'

SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE ((@__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset)) AND ([m].[Timeline] < @__end_1)) AND [m].[Timeline] IN ('1902-01-02T10:00:00.1234567+01:30')");
}
Expand Down Expand Up @@ -7400,7 +7400,7 @@ public override async Task DateTimeOffset_Date_returns_datetime(bool async)
AssertSql(
@"@__dateTimeOffset_Date_0='0002-03-01T00:00:00'

SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline]
SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE CONVERT(date, [m].[Timeline]) >= @__dateTimeOffset_Date_0");
}
Expand Down Expand Up @@ -7495,6 +7495,82 @@ FROM [LocustLeaders] AS [l]
WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND (CAST([l].[ThreatLevel] AS bigint) >= (CAST(5 AS bigint) + CAST([l].[ThreatLevel] AS bigint)))");
}

public override async Task TimeSpan_Hours(bool async)
{
await base.TimeSpan_Hours(async);

AssertSql(
@"SELECT DATEPART(hour, [m].[Duration])
FROM [Missions] AS [m]");
}

public override async Task TimeSpan_Minutes(bool async)
{
await base.TimeSpan_Minutes(async);

AssertSql(
@"SELECT DATEPART(minute, [m].[Duration])
FROM [Missions] AS [m]");
}

public override async Task TimeSpan_Seconds(bool async)
{
await base.TimeSpan_Seconds(async);

AssertSql(
@"SELECT DATEPART(second, [m].[Duration])
FROM [Missions] AS [m]");
}

public override async Task TimeSpan_Milliseconds(bool async)
{
await base.TimeSpan_Milliseconds(async);

AssertSql(
@"SELECT DATEPART(millisecond, [m].[Duration])
FROM [Missions] AS [m]");
}

public override async Task Where_TimeSpan_Hours(bool async)
{
await base.Where_TimeSpan_Hours(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(hour, [m].[Duration]) = 1");
}

public override async Task Where_TimeSpan_Minutes(bool async)
{
await base.Where_TimeSpan_Minutes(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(minute, [m].[Duration]) = 1");
}

public override async Task Where_TimeSpan_Seconds(bool async)
{
await base.Where_TimeSpan_Seconds(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(second, [m].[Duration]) = 1");
}

public override async Task Where_TimeSpan_Milliseconds(bool async)
{
await base.Where_TimeSpan_Milliseconds(async);

AssertSql(
@"SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(millisecond, [m].[Duration]) = 1");
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
Expand Down
Loading