diff --git a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosDateTimeMemberTranslator.cs b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosDateTimeMemberTranslator.cs index 508995534f0..34a565005a2 100644 --- a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosDateTimeMemberTranslator.cs +++ b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosDateTimeMemberTranslator.cs @@ -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( @@ -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); } } diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs index b3ae176a9cc..6a307857c0b 100644 --- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerDateTimeMemberTranslator.cs @@ -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( diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerTimeOnlyMemberTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerTimeOnlyMemberTranslator.cs index bf2159ecba7..c366974b8a7 100644 --- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerTimeOnlyMemberTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerTimeOnlyMemberTranslator.cs @@ -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. /// -public class SqlServerTimeOnlyMemberTranslator : IMemberTranslator +public class SqlServerTimeOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMemberTranslator { - private static readonly Dictionary DatePartMappings = new() - { - { nameof(TimeOnly.Hour), "hour" }, - { nameof(TimeOnly.Minute), "minute" }, - { nameof(TimeOnly.Second), "second" }, - { nameof(TimeOnly.Millisecond), "millisecond" } - }; - - private readonly ISqlExpressionFactory _sqlExpressionFactory; - - /// - /// 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. - /// - public SqlServerTimeOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) - => _sqlExpressionFactory = sqlExpressionFactory; - /// /// 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 @@ -45,15 +26,30 @@ public SqlServerTimeOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFact Type returnType, IDiagnosticsLogger 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 { - 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; } } diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerTimeSpanMemberTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerTimeSpanMemberTranslator.cs index d65e0e8bdfd..13a9171f478 100644 --- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerTimeSpanMemberTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerTimeSpanMemberTranslator.cs @@ -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. /// -public class SqlServerTimeSpanMemberTranslator : IMemberTranslator +public class SqlServerTimeSpanMemberTranslator( + ISqlExpressionFactory sqlExpressionFactory) + : IMemberTranslator { - private static readonly Dictionary DatePartMappings = new() - { - { nameof(TimeSpan.Hours), "hour" }, - { nameof(TimeSpan.Minutes), "minute" }, - { nameof(TimeSpan.Seconds), "second" }, - { nameof(TimeSpan.Milliseconds), "millisecond" } - }; - - private readonly ISqlExpressionFactory _sqlExpressionFactory; - - /// - /// 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. - /// - public SqlServerTimeSpanMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) - => _sqlExpressionFactory = sqlExpressionFactory; - /// /// 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 @@ -45,15 +28,30 @@ public SqlServerTimeSpanMemberTranslator(ISqlExpressionFactory sqlExpressionFact Type returnType, IDiagnosticsLogger 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; } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteDateTimeMemberTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteDateTimeMemberTranslator.cs index 39f24fa31f1..f9080403b41 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteDateTimeMemberTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteDateTimeMemberTranslator.cs @@ -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"; diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index 3c2aa4c0970..e99936b4181 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -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] diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index ec8180be84c..e312398fa06 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -8652,6 +8652,48 @@ public virtual Task Non_string_concat_uses_appropriate_type_mapping(bool async) ss => ss.Set().Select(e => e.Duration + interval)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_datetimeoffset_microsecond_component(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.Timeline.Microsecond == 200)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_datetimeoffset_nanosecond_component(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.Timeline.Nanosecond == 400)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_timespan_microsecond_component(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.Duration.Microseconds == 200)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_timespan_nanosecond_component(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.Duration.Nanoseconds == 400)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_timeonly_microsecond_component(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.Time.Microsecond == 200)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_timeonly_nanosecond_component(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.Time.Nanosecond == 400)); + protected GearsOfWarContext CreateContext() => Fixture.CreateContext(); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index 248bd260ce1..6a98ec09589 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -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().Where(o => o.OrderDate.Value.Nanosecond != 0 && o.OrderDate.Value.Microsecond != 0), + assertEmpty: true); } diff --git a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs index 4abffab963a..27b788b794c 100644 --- a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs +++ b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs @@ -157,6 +157,17 @@ public static IReadOnlyList 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 } }; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 0a079557a4a..5de2aa4b63a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -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); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 9cec3c4e613..e1e304656a2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -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); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index 160949226d5..528e501d582 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -13864,6 +13864,78 @@ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k] """); } + public override async Task Where_datetimeoffset_microsecond_component(bool async) + { + await base.Where_datetimeoffset_microsecond_component(async); + + AssertSql( + """ +SELECT [m].[Id], [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].[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].[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].[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].[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].[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); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 9420310aecc..696079c3a30 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -11852,6 +11852,78 @@ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k] """); } + public override async Task Where_datetimeoffset_microsecond_component(bool async) + { + await base.Where_datetimeoffset_microsecond_component(async); + + AssertSql( + """ +SELECT [m].[Id], [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].[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].[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].[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].[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].[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); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 73d5554d42f..10082b2c7e2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -10410,6 +10410,78 @@ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k] """); } + 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].[PeriodEnd], [m].[PeriodStart], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' 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].[PeriodEnd], [m].[PeriodStart], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' 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].[PeriodEnd], [m].[PeriodStart], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' 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].[PeriodEnd], [m].[PeriodStart], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' 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].[PeriodEnd], [m].[PeriodStart], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' 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].[PeriodEnd], [m].[PeriodStart], [m].[Rating], [m].[Time], [m].[Timeline] +FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [m] +WHERE DATEPART(nanosecond, [m].[Time]) % 1000 = 400 +"""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 328f42a797b..1581cbb64a4 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -9892,6 +9892,24 @@ FROM json_each(@__keys_2) AS "k" """); } + public override Task Where_datetimeoffset_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_datetimeoffset_microsecond_component(async)); + + public override Task Where_datetimeoffset_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_datetimeoffset_nanosecond_component(async)); + + public override Task Where_timespan_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timespan_microsecond_component(async)); + + public override Task Where_timespan_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timespan_nanosecond_component(async)); + + public override Task Where_timeonly_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timeonly_microsecond_component(async)); + + public override Task Where_timeonly_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timeonly_nanosecond_component(async)); + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs index 1dcb58c4e58..05bc1e93a46 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs @@ -445,6 +445,9 @@ public override async Task Correlated_collection_with_distinct_without_default_i public override Task Max_on_empty_sequence_throws(bool async) => Assert.ThrowsAsync(() => base.Max_on_empty_sequence_throws(async)); + public override Task Where_nanosecond_and_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_nanosecond_and_microsecond_component(async)); + [ConditionalFact] public async Task Single_Predicate_Cancellation() => await Assert.ThrowsAnyAsync( diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPCGearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPCGearsOfWarQuerySqliteTest.cs index 07174a52b7b..9b854e78419 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPCGearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPCGearsOfWarQuerySqliteTest.cs @@ -468,6 +468,24 @@ public override Task DateTimeOffset_to_unix_time_milliseconds(bool async) public override Task DateTimeOffset_to_unix_time_seconds(bool async) => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_seconds(async)); + public override Task Where_datetimeoffset_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_datetimeoffset_microsecond_component(async)); + + public override Task Where_datetimeoffset_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_datetimeoffset_nanosecond_component(async)); + + public override Task Where_timespan_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timespan_microsecond_component(async)); + + public override Task Where_timespan_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timespan_nanosecond_component(async)); + + public override Task Where_timeonly_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timeonly_microsecond_component(async)); + + public override Task Where_timeonly_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timeonly_nanosecond_component(async)); + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs index bef3059af39..43318bef23e 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/TPTGearsOfWarQuerySqliteTest.cs @@ -462,6 +462,24 @@ public override Task DateTimeOffset_to_unix_time_milliseconds(bool async) public override Task DateTimeOffset_to_unix_time_seconds(bool async) => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_seconds(async)); + public override Task Where_datetimeoffset_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_datetimeoffset_microsecond_component(async)); + + public override Task Where_datetimeoffset_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_datetimeoffset_nanosecond_component(async)); + + public override Task Where_timespan_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timespan_microsecond_component(async)); + + public override Task Where_timespan_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timespan_nanosecond_component(async)); + + public override Task Where_timeonly_microsecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timeonly_microsecond_component(async)); + + public override Task Where_timeonly_nanosecond_component(bool async) + => AssertTranslationFailed(() => base.Where_timeonly_nanosecond_component(async)); + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); }