Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ public class CosmosDateTimeMemberTranslator(ISqlExpressionFactory sqlExpressionF

return member.Name switch
{
nameof(DateTime.Year) => DatePart("yyyy"),
nameof(DateTime.Month) => DatePart("mm"),
nameof(DateTime.Day) => DatePart("dd"),
nameof(DateTime.Hour) => DatePart("hh"),
nameof(DateTime.Minute) => DatePart("mi"),
nameof(DateTime.Second) => DatePart("ss"),
nameof(DateTime.Millisecond) => DatePart("ms"),
nameof(DateTime.Microsecond) => DatePart("mcs"),
nameof(DateTime.Nanosecond) => DatePart("ns"),
nameof(DateTime.Year) => DateTimePart("yyyy"),
nameof(DateTime.Month) => DateTimePart("mm"),
nameof(DateTime.Day) => DateTimePart("dd"),
nameof(DateTime.Hour) => DateTimePart("hh"),
nameof(DateTime.Minute) => DateTimePart("mi"),
nameof(DateTime.Second) => DateTimePart("ss"),
nameof(DateTime.Millisecond) => DateTimePart("ms"),
nameof(DateTime.Microsecond) => sqlExpressionFactory.Modulo(DateTimePart("mcs"), sqlExpressionFactory.Constant(1000)),
nameof(DateTime.Nanosecond) => sqlExpressionFactory.Modulo(DateTimePart("ns"), sqlExpressionFactory.Constant(1000)),

nameof(DateTime.UtcNow)
=> sqlExpressionFactory.Function(
Expand All @@ -53,7 +53,10 @@ public class CosmosDateTimeMemberTranslator(ISqlExpressionFactory sqlExpressionF
_ => null
};

SqlExpression DatePart(string part)
=> sqlExpressionFactory.Function("DateTimePart", arguments: [sqlExpressionFactory.Constant(part), instance!], returnType);
SqlExpression DateTimePart(string part)
=> sqlExpressionFactory.Function(
"DateTimePart",
arguments: [sqlExpressionFactory.Constant(part), instance!],
returnType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public class SqlServerDateTimeMemberTranslator(
nameof(DateTime.Minute) => DatePart("minute"),
nameof(DateTime.Second) => DatePart("second"),
nameof(DateTime.Millisecond) => DatePart("millisecond"),
nameof(DateTime.Microsecond) => sqlExpressionFactory.Modulo(DatePart("microsecond"), sqlExpressionFactory.Constant(1000)),
nameof(DateTime.Nanosecond) => sqlExpressionFactory.Modulo(DatePart("nanosecond"), sqlExpressionFactory.Constant(1000)),

nameof(DateTime.Date)
=> sqlExpressionFactory.Function(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,8 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqlServerTimeOnlyMemberTranslator : IMemberTranslator
public class SqlServerTimeOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMemberTranslator
{
private static readonly Dictionary<string, string> DatePartMappings = new()
{
{ nameof(TimeOnly.Hour), "hour" },
{ nameof(TimeOnly.Minute), "minute" },
{ nameof(TimeOnly.Second), "second" },
{ nameof(TimeOnly.Millisecond), "millisecond" }
};

private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqlServerTimeOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFactory)
=> _sqlExpressionFactory = sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -45,15 +26,30 @@ public SqlServerTimeOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFact
Type returnType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (member.DeclaringType == typeof(TimeOnly) && DatePartMappings.TryGetValue(member.Name, out var value))
var declaringType = member.DeclaringType;

if (declaringType != typeof(TimeOnly))
{
return null;
}

return member.Name switch
Comment thread
cincuranet marked this conversation as resolved.
{
return _sqlExpressionFactory.Function(
"DATEPART", new[] { _sqlExpressionFactory.Fragment(value), instance! },
nameof(TimeOnly.Hour) => DatePart("hour"),
nameof(TimeOnly.Minute) => DatePart("minute"),
nameof(TimeOnly.Second) => DatePart("second"),
nameof(TimeOnly.Millisecond) => DatePart("millisecond"),
nameof(TimeOnly.Microsecond) => sqlExpressionFactory.Modulo(DatePart("microsecond"), sqlExpressionFactory.Constant(1000)),
nameof(TimeOnly.Nanosecond) => sqlExpressionFactory.Modulo(DatePart("nanosecond"), sqlExpressionFactory.Constant(1000)),
_ => null,
};

SqlExpression DatePart(string part)
=> sqlExpressionFactory.Function(
"DATEPART",
arguments: [sqlExpressionFactory.Fragment(part), instance!],
nullable: true,
argumentsPropagateNullability: Statics.FalseTrue,
returnType);
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,10 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqlServerTimeSpanMemberTranslator : IMemberTranslator
public class SqlServerTimeSpanMemberTranslator(
ISqlExpressionFactory sqlExpressionFactory)
: IMemberTranslator
{
private static readonly Dictionary<string, string> DatePartMappings = new()
{
{ nameof(TimeSpan.Hours), "hour" },
{ nameof(TimeSpan.Minutes), "minute" },
{ nameof(TimeSpan.Seconds), "second" },
{ nameof(TimeSpan.Milliseconds), "millisecond" }
};

private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqlServerTimeSpanMemberTranslator(ISqlExpressionFactory sqlExpressionFactory)
=> _sqlExpressionFactory = sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -45,15 +28,30 @@ public SqlServerTimeSpanMemberTranslator(ISqlExpressionFactory sqlExpressionFact
Type returnType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (member.DeclaringType == typeof(TimeSpan) && DatePartMappings.TryGetValue(member.Name, out var value))
var declaringType = member.DeclaringType;

if (declaringType != typeof(TimeSpan))
{
return null;
}

return member.Name switch
{
return _sqlExpressionFactory.Function(
"DATEPART", new[] { _sqlExpressionFactory.Fragment(value), instance! },
nameof(TimeSpan.Hours) => DatePart("hour"),
nameof(TimeSpan.Minutes) => DatePart("minute"),
nameof(TimeSpan.Seconds) => DatePart("second"),
nameof(TimeSpan.Milliseconds) => DatePart("millisecond"),
nameof(TimeSpan.Microseconds) => sqlExpressionFactory.Modulo(DatePart("microsecond"), sqlExpressionFactory.Constant(1000)),
nameof(TimeSpan.Nanoseconds) => sqlExpressionFactory.Modulo(DatePart("nanosecond"), sqlExpressionFactory.Constant(1000)),
_ => null,
};

SqlExpression DatePart(string part)
=> sqlExpressionFactory.Function(
"DATEPART",
arguments: [sqlExpressionFactory.Fragment(part), instance!],
nullable: true,
argumentsPropagateNullability: Statics.FalseTrue,
returnType);
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ public class SqliteDateTimeMemberTranslator(SqliteSqlExpressionFactory sqlExpres
typeof(double)),
sqlExpressionFactory.Constant(1000)),
sqlExpressionFactory.Constant(1000));

case nameof(DateTime.Microsecond):
case nameof(DateTime.Nanosecond):
return null;
}

var format = "%Y-%m-%d %H:%M:%f";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5091,6 +5091,19 @@ await AssertTranslationFailed(
AssertSql();
}

public override Task Where_nanosecond_and_microsecond_component(bool async)
=> Fixture.NoSyncTest(
async, async a =>
{
await base.Where_nanosecond_and_microsecond_component(a);

AssertSql("""
SELECT VALUE c
FROM root c
WHERE ((c["$type"] = "Order") AND (((DateTimePart("ns", c["OrderDate"]) % 1000) != 0) AND ((DateTimePart("mcs", c["OrderDate"]) % 1000) != 0)))
""");
});

#region ToPageAsync

[ConditionalFact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8652,6 +8652,48 @@ public virtual Task Non_string_concat_uses_appropriate_type_mapping(bool async)
ss => ss.Set<Mission>().Select(e => e.Duration + interval));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_datetimeoffset_microsecond_component(bool async)
=> AssertQuery(
async,
ss => ss.Set<Mission>().Where(e => e.Timeline.Microsecond == 200));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_datetimeoffset_nanosecond_component(bool async)
=> AssertQuery(
async,
ss => ss.Set<Mission>().Where(e => e.Timeline.Nanosecond == 400));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_timespan_microsecond_component(bool async)
=> AssertQuery(
async,
ss => ss.Set<Mission>().Where(e => e.Duration.Microseconds == 200));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_timespan_nanosecond_component(bool async)
=> AssertQuery(
async,
ss => ss.Set<Mission>().Where(e => e.Duration.Nanoseconds == 400));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_timeonly_microsecond_component(bool async)
=> AssertQuery(
async,
ss => ss.Set<Mission>().Where(e => e.Time.Microsecond == 200));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_timeonly_nanosecond_component(bool async)
=> AssertQuery(
async,
ss => ss.Set<Mission>().Where(e => e.Time.Nanosecond == 400));

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5839,4 +5839,13 @@ public virtual Task Static_member_access_gets_parameterized_within_larger_evalua

private static string StaticProperty
=> "ALF";

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_nanosecond_and_microsecond_component(bool async)
=> AssertQuery(
async,
// TODO: this is basically just about translation, we don't have data with nanoseconds and microseconds
ss => ss.Set<Order>().Where(o => o.OrderDate.Value.Nanosecond != 0 && o.OrderDate.Value.Microsecond != 0),
assertEmpty: true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ public static IReadOnlyList<Mission> CreateMissions()
Date = new DateOnly(1, 1, 1),
Time = new TimeOnly(0, 0, 0),
Difficulty = MissionDifficulty.Unknown
},
new()
{
Id = 4,
CodeName = "Nanoseconds",
Rating = null,
Timeline = new DateTimeOffset(11, 5, 3, 12, 0, 0, 0, 200, new TimeSpan()).Add(TimeSpan.FromTicks(4) /* 400 nanoseconds */),
Duration = new TimeSpan(0, 2, 0, 15, 456, 200).Add(TimeSpan.FromTicks(4) /* 400 nanoseconds */),
Date = new DateOnly(1, 1, 1),
Time = new TimeOnly(0, 0, 0, 10, 200).Add(TimeSpan.FromTicks(4) /* 400 nanoseconds */),
Difficulty = MissionDifficulty.Unknown
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10517,6 +10517,78 @@ FROM [Missions] AS [m]
);
}

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

AssertSql(
"""
SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Difficulty], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(microsecond, [m].[Timeline]) % 1000 = 200
""");
}

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

AssertSql(
"""
SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Difficulty], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(nanosecond, [m].[Timeline]) % 1000 = 400
""");
}

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

AssertSql(
"""
SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Difficulty], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(microsecond, [m].[Duration]) % 1000 = 200
""");
}

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

AssertSql(
"""
SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Difficulty], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(nanosecond, [m].[Duration]) % 1000 = 400
""");
}

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

AssertSql(
"""
SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Difficulty], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(microsecond, [m].[Time]) % 1000 = 200
""");
}

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

AssertSql(
"""
SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Difficulty], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE DATEPART(nanosecond, [m].[Time]) % 1000 = 400
""");
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7444,6 +7444,17 @@ FROM [Orders] AS [o]
""");
}

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

AssertSql("""
SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate]
FROM [Orders] AS [o]
WHERE (DATEPART(nanosecond, [o].[OrderDate]) % 1000 <> 0 OR [o].[OrderDate] IS NULL) AND (DATEPART(microsecond, [o].[OrderDate]) % 1000 <> 0 OR [o].[OrderDate] IS NULL)
""");
}

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

Expand Down
Loading