diff --git a/EFCore.sln.DotSettings b/EFCore.sln.DotSettings index b8b7f701254..e8f3d08f6c0 100644 --- a/EFCore.sln.DotSettings +++ b/EFCore.sln.DotSettings @@ -356,6 +356,8 @@ The .NET Foundation licenses this file to you under the MIT license. True True True + True + True True True True diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs index b4cfb0938a5..3312572c540 100644 --- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs @@ -685,10 +685,37 @@ public virtual SqlExpression Condition(SqlExpression test, SqlExpression ifTrue, { var typeMapping = ExpressionExtensions.InferTypeMapping(ifTrue, ifFalse); - return new SqlConditionalExpression( - ApplyTypeMapping(test, _boolTypeMapping), - ApplyTypeMapping(ifTrue, typeMapping), - ApplyTypeMapping(ifFalse, typeMapping)); + test = ApplyTypeMapping(test, _boolTypeMapping); + ifTrue = ApplyTypeMapping(ifTrue, typeMapping); + ifFalse = ApplyTypeMapping(ifFalse, typeMapping); + + // Simplify: + // a == b ? b : a -> a + // a != b ? a : b -> a + if (test is SqlBinaryExpression + { + OperatorType: ExpressionType.Equal or ExpressionType.NotEqual, + Left: var left, + Right: var right + } binary) + { + // Reverse ifEqual/ifNotEqual for ExpressionType.NotEqual for easier reasoning below + var (ifEqual, ifNotEqual) = binary.OperatorType is ExpressionType.Equal ? (ifTrue, ifFalse) : (ifFalse, ifTrue); + + // a == b ? b : a -> a + if (left.Equals(ifNotEqual) && right.Equals(ifEqual)) + { + return left; + } + + // b == a ? b : a -> a + if (right.Equals(ifNotEqual) && left.Equals(ifEqual)) + { + return right; + } + } + + return new SqlConditionalExpression(test, ifTrue, ifFalse); } /// diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 064e97cbe74..088ee857231 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -825,6 +825,58 @@ public virtual SqlExpression Case( elseResult = lastCase.ElseResult; } + // Simplify: + // a == b ? b : a -> a + // a != b ? a : b -> a + // And lift: + // a == b ? null : a -> NULLIF(a, b) + // a != b ? a : null -> NULLIF(a, b) + if (operand is null + && typeMappedWhenClauses is + [ + { + Test: SqlBinaryExpression + { + OperatorType: ExpressionType.Equal or ExpressionType.NotEqual, + Left: var left, + Right: var right + } binary, + Result: var result + } + ]) + { + // Reverse ifEqual/ifNotEqual for ExpressionType.NotEqual for easier reasoning below + var (ifEqual, ifNotEqual) = binary.OperatorType is ExpressionType.Equal + ? (result, elseResult ?? Constant(null, result.Type, result.TypeMapping)) + : (elseResult ?? Constant(null, result.Type, result.TypeMapping), result); + + if (left.Equals(ifNotEqual)) + { + switch (ifEqual) + { + // a == b ? b : a -> a + case var _ when ifEqual.Equals(right): + return left; + // a == b ? null : a -> NULLIF(a, b) + case SqlConstantExpression { Value: null }: + return Function("NULLIF", [left, right], nullable: true, [false, false], left.Type, left.TypeMapping); + } + } + + if (right.Equals(ifNotEqual)) + { + switch (ifEqual) + { + // b == a ? b : a -> a + case var _ when ifEqual.Equals(left): + return right; + // b == a ? null : a -> NULLIF(a, b) + case SqlConstantExpression { Value: null }: + return Function("NULLIF", [right, left], nullable: true, [false, false], right.Type, right.TypeMapping); + } + } + } + return existingExpression is CaseExpression expr && operand == expr.Operand && typeMappedWhenClauses.SequenceEqual(expr.WhenClauses) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/OperatorTranslationsCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/OperatorTranslationsCosmosTest.cs index ab39e7c5b37..967ee86c1c4 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Translations/OperatorTranslationsCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Translations/OperatorTranslationsCosmosTest.cs @@ -14,6 +14,94 @@ public OperatorTranslationsCosmosTest(BasicTypesQueryCosmosFixture fixture, ITes Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + #region Conditional + + public override Task Conditional_simplifiable_equality(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Conditional_simplifiable_equality(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["Int"] > 1) +"""); + }); + + public override Task Conditional_simplifiable_inequality(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Conditional_simplifiable_inequality(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["Int"] > 1) +"""); + }); + + public override Task Conditional_uncoalesce_with_equality_left(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Conditional_uncoalesce_with_equality_left(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (((c["Int"] = 9) ? null : c["Int"]) > 1) +"""); + }); + + public override Task Conditional_uncoalesce_with_equality_right(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Conditional_uncoalesce_with_equality_right(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (((9 = c["Int"]) ? null : c["Int"]) > 1) +"""); + }); + + public override Task Conditional_uncoalesce_with_unequality_left(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Conditional_uncoalesce_with_unequality_left(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (((c["Int"] != 9) ? c["Int"] : null) > 1) +"""); + }); + + public override Task Conditional_uncoalesce_with_inequality_right(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Conditional_uncoalesce_with_inequality_right(a); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (((9 != c["Int"]) ? c["Int"] : null) > 1) +"""); + }); + + #endregion Conditional + #region Bitwise public override Task Bitwise_or(bool async) diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index 6ce93abfd78..53e80173c62 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -753,7 +753,7 @@ public virtual Task Where_equal_with_conditional(bool async) ss => ss.Set().Where( e => (e.NullableStringA == e.NullableStringB ? e.NullableStringA - : e.NullableStringB) + : e.NullableStringC) == e.NullableStringC).Select(e => e.Id)); [ConditionalTheory] @@ -765,7 +765,7 @@ public virtual Task Where_not_equal_with_conditional(bool async) e => e.NullableStringC != (e.NullableStringA == e.NullableStringB ? e.NullableStringA - : e.NullableStringB)).Select(e => e.Id)); + : e.NullableStringC)).Select(e => e.Id)); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.Specification.Tests/Query/Translations/OperatorTranslationsTestBase.cs b/test/EFCore.Specification.Tests/Query/Translations/OperatorTranslationsTestBase.cs index a1f58662859..4ccbe805f4d 100644 --- a/test/EFCore.Specification.Tests/Query/Translations/OperatorTranslationsTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/Translations/OperatorTranslationsTestBase.cs @@ -8,6 +8,58 @@ namespace Microsoft.EntityFrameworkCore.Query.Translations; public abstract class OperatorTranslationsTestBase(TFixture fixture) : QueryTestBase(fixture) where TFixture : BasicTypesQueryFixtureBase, new() { + // See also operators precedence tests in OperatorsQueryTestBase + + #region Conditional + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Conditional_simplifiable_equality(bool async) + => AssertQuery( + async, + // ReSharper disable once MergeConditionalExpression + cs => cs.Set().Where(x => (x.Int == 9 ? 9 : x.Int) > 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Conditional_simplifiable_inequality(bool async) + => AssertQuery( + async, + // ReSharper disable once MergeConditionalExpression + cs => cs.Set().Where(x => (x.Int != 8 ? x.Int : 8) > 1)); + + // In relational providers, x == a ? null : x ("un-coalescing conditional") is translated to SQL NULLIF + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Conditional_uncoalesce_with_equality_left(bool async) + => AssertQuery( + async, + cs => cs.Set().Where(x => (x.Int == 9 ? null : x.Int) > 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Conditional_uncoalesce_with_equality_right(bool async) + => AssertQuery( + async, + cs => cs.Set().Where(x => (9 == x.Int ? null : x.Int) > 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Conditional_uncoalesce_with_unequality_left(bool async) + => AssertQuery( + async, + cs => cs.Set().Where(x => (x.Int != 9 ? x.Int : null) > 1)); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Conditional_uncoalesce_with_inequality_right(bool async) + => AssertQuery( + async, + cs => cs.Set().Where(x => (9 != x.Int ? x.Int : null) > 1)); + + #endregion Conditional + #region Bitwise #pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index d12c45997f3..508437851dc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -855,9 +855,7 @@ public override async Task Select_null_propagation_works_for_multiple_navigation AssertSql( """ -SELECT CASE - WHEN [c].[Name] IS NOT NULL THEN [c].[Name] -END +SELECT [c].[Name] FROM [Tags] AS [t] LEFT JOIN [Gears] AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId] LEFT JOIN [Tags] AS [t0] ON ([g].[Nickname] = [t0].[GearNickName] OR ([g].[Nickname] IS NULL AND [t0].[GearNickName] IS NULL)) AND ([g].[SquadId] = [t0].[GearSquadId] OR ([g].[SquadId] IS NULL AND [t0].[GearSquadId] IS NULL)) @@ -1981,10 +1979,7 @@ public override async Task Optional_navigation_type_compensation_works_with_pred SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note] FROM [Tags] AS [t] LEFT JOIN [Gears] AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId] -WHERE CASE - WHEN [g].[HasSoulPatch] = CAST(1 AS bit) THEN CAST(1 AS bit) - ELSE [g].[HasSoulPatch] -END = CAST(0 AS bit) +WHERE [g].[HasSoulPatch] = CAST(0 AS bit) """); } @@ -1997,10 +1992,7 @@ public override async Task Optional_navigation_type_compensation_works_with_pred SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note] FROM [Tags] AS [t] LEFT JOIN [Gears] AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId] -WHERE CASE - WHEN [g].[HasSoulPatch] = CAST(0 AS bit) THEN CAST(0 AS bit) - ELSE [g].[HasSoulPatch] -END = CAST(0 AS bit) +WHERE [g].[HasSoulPatch] = CAST(0 AS bit) """); } @@ -3057,9 +3049,7 @@ public override async Task Select_null_conditional_with_inheritance(bool async) AssertSql( """ -SELECT CASE - WHEN [f].[CommanderName] IS NOT NULL THEN [f].[CommanderName] -END +SELECT [f].[CommanderName] FROM [Factions] AS [f] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServer160Test.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServer160Test.cs index d1cced85865..43d006ca396 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServer160Test.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServer160Test.cs @@ -21,6 +21,18 @@ public NorthwindFunctionsQuerySqlServer160Test(Fixture160 fixture, ITestOutputHe public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); + public override async Task Client_evaluation_of_uncorrelated_method_call(bool async) + { + await base.Client_evaluation_of_uncorrelated_method_call(async); + + AssertSql( + """ +SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] +FROM [Order Details] AS [o] +WHERE [o].[UnitPrice] < 7.0 AND 10 < [o].[ProductID] +"""); + } + public override async Task Sum_over_round_works_correctly_in_projection(bool async) { await base.Sum_over_round_works_correctly_in_projection(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index d771cc25f85..77494e237a0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -2207,10 +2207,10 @@ SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE WHEN [e].[NullableStringA] = [e].[NullableStringB] OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN [e].[NullableStringA] - ELSE [e].[NullableStringB] + ELSE [e].[NullableStringC] END = [e].[NullableStringC] OR (CASE WHEN [e].[NullableStringA] = [e].[NullableStringB] OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN [e].[NullableStringA] - ELSE [e].[NullableStringB] + ELSE [e].[NullableStringC] END IS NULL AND [e].[NullableStringC] IS NULL) """); } @@ -2225,13 +2225,13 @@ SELECT [e].[Id] FROM [Entities1] AS [e] WHERE ([e].[NullableStringC] <> CASE WHEN [e].[NullableStringA] = [e].[NullableStringB] OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN [e].[NullableStringA] - ELSE [e].[NullableStringB] + ELSE [e].[NullableStringC] END OR [e].[NullableStringC] IS NULL OR CASE WHEN [e].[NullableStringA] = [e].[NullableStringB] OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN [e].[NullableStringA] - ELSE [e].[NullableStringB] + ELSE [e].[NullableStringC] END IS NULL) AND ([e].[NullableStringC] IS NOT NULL OR CASE WHEN [e].[NullableStringA] = [e].[NullableStringB] OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN [e].[NullableStringA] - ELSE [e].[NullableStringB] + ELSE [e].[NullableStringC] END IS NOT NULL) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index 182be105b6d..18aca22ea4c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -1157,9 +1157,7 @@ public override async Task Select_null_propagation_works_for_multiple_navigation AssertSql( """ -SELECT CASE - WHEN [c].[Name] IS NOT NULL THEN [c].[Name] -END +SELECT [c].[Name] FROM [Tags] AS [t] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId] @@ -2741,10 +2739,7 @@ UNION ALL SELECT [o].[Nickname], [o].[SquadId], [o].[HasSoulPatch] FROM [Officers] AS [o] ) AS [u] ON [t].[GearNickName] = [u].[Nickname] AND [t].[GearSquadId] = [u].[SquadId] -WHERE CASE - WHEN [u].[HasSoulPatch] = CAST(1 AS bit) THEN CAST(1 AS bit) - ELSE [u].[HasSoulPatch] -END = CAST(0 AS bit) +WHERE [u].[HasSoulPatch] = CAST(0 AS bit) """); } @@ -2763,10 +2758,7 @@ UNION ALL SELECT [o].[Nickname], [o].[SquadId], [o].[HasSoulPatch] FROM [Officers] AS [o] ) AS [u] ON [t].[GearNickName] = [u].[Nickname] AND [t].[GearSquadId] = [u].[SquadId] -WHERE CASE - WHEN [u].[HasSoulPatch] = CAST(0 AS bit) THEN CAST(0 AS bit) - ELSE [u].[HasSoulPatch] -END = CAST(0 AS bit) +WHERE [u].[HasSoulPatch] = CAST(0 AS bit) """); } @@ -4066,9 +4058,7 @@ public override async Task Select_null_conditional_with_inheritance(bool async) AssertSql( """ -SELECT CASE - WHEN [l].[CommanderName] IS NOT NULL THEN [l].[CommanderName] -END +SELECT [l].[CommanderName] FROM [LocustHordes] AS [l] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 62be1d75c6a..070adfd5e7c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -1035,9 +1035,7 @@ public override async Task Select_null_propagation_works_for_multiple_navigation AssertSql( """ -SELECT CASE - WHEN [c].[Name] IS NOT NULL THEN [c].[Name] -END +SELECT [c].[Name] FROM [Tags] AS [t] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId] @@ -2363,10 +2361,7 @@ LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[HasSoulPatch] FROM [Gears] AS [g] ) AS [s] ON [t].[GearNickName] = [s].[Nickname] AND [t].[GearSquadId] = [s].[SquadId] -WHERE CASE - WHEN [s].[HasSoulPatch] = CAST(1 AS bit) THEN CAST(1 AS bit) - ELSE [s].[HasSoulPatch] -END = CAST(0 AS bit) +WHERE [s].[HasSoulPatch] = CAST(0 AS bit) """); } @@ -2382,10 +2377,7 @@ LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[HasSoulPatch] FROM [Gears] AS [g] ) AS [s] ON [t].[GearNickName] = [s].[Nickname] AND [t].[GearSquadId] = [s].[SquadId] -WHERE CASE - WHEN [s].[HasSoulPatch] = CAST(0 AS bit) THEN CAST(0 AS bit) - ELSE [s].[HasSoulPatch] -END = CAST(0 AS bit) +WHERE [s].[HasSoulPatch] = CAST(0 AS bit) """); } @@ -3505,9 +3497,7 @@ public override async Task Select_null_conditional_with_inheritance(bool async) AssertSql( """ -SELECT CASE - WHEN [l].[CommanderName] IS NOT NULL THEN [l].[CommanderName] -END +SELECT [l].[CommanderName] FROM [Factions] AS [f] LEFT JOIN [LocustHordes] AS [l] ON [f].[Id] = [l].[Id] WHERE [l].[Id] IS NOT NULL diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 727f120e502..f1948bf9ea3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -724,10 +724,7 @@ public override async Task Optional_navigation_type_compensation_works_with_pred SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note], [t].[PeriodEnd], [t].[PeriodStart] FROM [Tags] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [t] LEFT JOIN [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId] -WHERE CASE - WHEN [g].[HasSoulPatch] = CAST(1 AS bit) THEN CAST(1 AS bit) - ELSE [g].[HasSoulPatch] -END = CAST(0 AS bit) +WHERE [g].[HasSoulPatch] = CAST(0 AS bit) """); } @@ -1570,10 +1567,7 @@ public override async Task Optional_navigation_type_compensation_works_with_pred SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note], [t].[PeriodEnd], [t].[PeriodStart] FROM [Tags] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [t] LEFT JOIN [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId] -WHERE CASE - WHEN [g].[HasSoulPatch] = CAST(0 AS bit) THEN CAST(0 AS bit) - ELSE [g].[HasSoulPatch] -END = CAST(0 AS bit) +WHERE [g].[HasSoulPatch] = CAST(0 AS bit) """); } @@ -5059,9 +5053,7 @@ public override async Task Select_null_propagation_works_for_multiple_navigation AssertSql( """ -SELECT CASE - WHEN [c].[Name] IS NOT NULL THEN [c].[Name] -END +SELECT [c].[Name] FROM [Tags] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [t] LEFT JOIN [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId] LEFT JOIN [Tags] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [t0] ON ([g].[Nickname] = [t0].[GearNickName] OR ([g].[Nickname] IS NULL AND [t0].[GearNickName] IS NULL)) AND ([g].[SquadId] = [t0].[GearSquadId] OR ([g].[SquadId] IS NULL AND [t0].[GearSquadId] IS NULL)) @@ -6988,9 +6980,7 @@ public override async Task Select_null_conditional_with_inheritance(bool async) AssertSql( """ -SELECT CASE - WHEN [f].[CommanderName] IS NOT NULL THEN [f].[CommanderName] -END +SELECT [f].[CommanderName] FROM [Factions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [f] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Translations/OperatorTranslationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Translations/OperatorTranslationsSqlServerTest.cs index 8c28b93ed39..c1343173259 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Translations/OperatorTranslationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Translations/OperatorTranslationsSqlServerTest.cs @@ -12,6 +12,82 @@ public OperatorTranslationsSqlServerTest(BasicTypesQuerySqlServerFixture fixture Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + #region Conditional + + public override async Task Conditional_simplifiable_equality(bool async) + { + await base.Conditional_simplifiable_equality(async); + + AssertSql( + """ +SELECT [n].[Id], [n].[Bool], [n].[Byte], [n].[ByteArray], [n].[DateOnly], [n].[DateTime], [n].[DateTimeOffset], [n].[Decimal], [n].[Double], [n].[Enum], [n].[FlagsEnum], [n].[Float], [n].[Guid], [n].[Int], [n].[Long], [n].[Short], [n].[String], [n].[TimeOnly], [n].[TimeSpan] +FROM [NullableBasicTypesEntities] AS [n] +WHERE [n].[Int] > 1 +"""); + } + + public override async Task Conditional_simplifiable_inequality(bool async) + { + await base.Conditional_simplifiable_inequality(async); + + AssertSql( + """ +SELECT [n].[Id], [n].[Bool], [n].[Byte], [n].[ByteArray], [n].[DateOnly], [n].[DateTime], [n].[DateTimeOffset], [n].[Decimal], [n].[Double], [n].[Enum], [n].[FlagsEnum], [n].[Float], [n].[Guid], [n].[Int], [n].[Long], [n].[Short], [n].[String], [n].[TimeOnly], [n].[TimeSpan] +FROM [NullableBasicTypesEntities] AS [n] +WHERE [n].[Int] > 1 +"""); + } + + public override async Task Conditional_uncoalesce_with_equality_left(bool async) + { + await base.Conditional_uncoalesce_with_equality_left(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE NULLIF([b].[Int], 9) > 1 +"""); + } + + public override async Task Conditional_uncoalesce_with_equality_right(bool async) + { + await base.Conditional_uncoalesce_with_equality_right(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE NULLIF([b].[Int], 9) > 1 +"""); + } + + public override async Task Conditional_uncoalesce_with_unequality_left(bool async) + { + await base.Conditional_uncoalesce_with_unequality_left(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE NULLIF([b].[Int], 9) > 1 +"""); + } + + public override async Task Conditional_uncoalesce_with_inequality_right(bool async) + { + await base.Conditional_uncoalesce_with_inequality_right(async); + + AssertSql( + """ +SELECT [b].[Id], [b].[Bool], [b].[Byte], [b].[ByteArray], [b].[DateOnly], [b].[DateTime], [b].[DateTimeOffset], [b].[Decimal], [b].[Double], [b].[Enum], [b].[FlagsEnum], [b].[Float], [b].[Guid], [b].[Int], [b].[Long], [b].[Short], [b].[String], [b].[TimeOnly], [b].[TimeSpan] +FROM [BasicTypesEntities] AS [b] +WHERE NULLIF([b].[Int], 9) > 1 +"""); + } + + #endregion Conditional + #region Bitwise public override async Task Bitwise_or(bool async) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 6038f2f2c53..c2107c10d5c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -2618,9 +2618,7 @@ public override async Task Select_null_propagation_works_for_multiple_navigation AssertSql( """ -SELECT CASE - WHEN "c"."Name" IS NOT NULL THEN "c"."Name" -END +SELECT "c"."Name" FROM "Tags" AS "t" LEFT JOIN "Gears" AS "g" ON "t"."GearNickName" = "g"."Nickname" AND "t"."GearSquadId" = "g"."SquadId" LEFT JOIN "Tags" AS "t0" ON ("g"."Nickname" = "t0"."GearNickName" OR ("g"."Nickname" IS NULL AND "t0"."GearNickName" IS NULL)) AND ("g"."SquadId" = "t0"."GearSquadId" OR ("g"."SquadId" IS NULL AND "t0"."GearSquadId" IS NULL)) @@ -5600,9 +5598,7 @@ public override async Task Select_null_conditional_with_inheritance(bool async) AssertSql( """ -SELECT CASE - WHEN "f"."CommanderName" IS NOT NULL THEN "f"."CommanderName" -END +SELECT "f"."CommanderName" FROM "Factions" AS "f" """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/Translations/OperatorTranslationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/Translations/OperatorTranslationsSqliteTest.cs index cbedc68377b..b8da96607b9 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/Translations/OperatorTranslationsSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/Translations/OperatorTranslationsSqliteTest.cs @@ -12,6 +12,82 @@ public OperatorTranslationsSqliteTest(BasicTypesQuerySqliteFixture fixture, ITes Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + #region Conditional + + public override async Task Conditional_simplifiable_equality(bool async) + { + await base.Conditional_simplifiable_equality(async); + + AssertSql( + """ +SELECT "n"."Id", "n"."Bool", "n"."Byte", "n"."ByteArray", "n"."DateOnly", "n"."DateTime", "n"."DateTimeOffset", "n"."Decimal", "n"."Double", "n"."Enum", "n"."FlagsEnum", "n"."Float", "n"."Guid", "n"."Int", "n"."Long", "n"."Short", "n"."String", "n"."TimeOnly", "n"."TimeSpan" +FROM "NullableBasicTypesEntities" AS "n" +WHERE "n"."Int" > 1 +"""); + } + + public override async Task Conditional_simplifiable_inequality(bool async) + { + await base.Conditional_simplifiable_inequality(async); + + AssertSql( + """ +SELECT "n"."Id", "n"."Bool", "n"."Byte", "n"."ByteArray", "n"."DateOnly", "n"."DateTime", "n"."DateTimeOffset", "n"."Decimal", "n"."Double", "n"."Enum", "n"."FlagsEnum", "n"."Float", "n"."Guid", "n"."Int", "n"."Long", "n"."Short", "n"."String", "n"."TimeOnly", "n"."TimeSpan" +FROM "NullableBasicTypesEntities" AS "n" +WHERE "n"."Int" > 1 +"""); + } + + public override async Task Conditional_uncoalesce_with_equality_left(bool async) + { + await base.Conditional_uncoalesce_with_equality_left(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE NULLIF("b"."Int", 9) > 1 +"""); + } + + public override async Task Conditional_uncoalesce_with_equality_right(bool async) + { + await base.Conditional_uncoalesce_with_equality_right(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE NULLIF("b"."Int", 9) > 1 +"""); + } + + public override async Task Conditional_uncoalesce_with_unequality_left(bool async) + { + await base.Conditional_uncoalesce_with_unequality_left(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE NULLIF("b"."Int", 9) > 1 +"""); + } + + public override async Task Conditional_uncoalesce_with_inequality_right(bool async) + { + await base.Conditional_uncoalesce_with_inequality_right(async); + + AssertSql( + """ +SELECT "b"."Id", "b"."Bool", "b"."Byte", "b"."ByteArray", "b"."DateOnly", "b"."DateTime", "b"."DateTimeOffset", "b"."Decimal", "b"."Double", "b"."Enum", "b"."FlagsEnum", "b"."Float", "b"."Guid", "b"."Int", "b"."Long", "b"."Short", "b"."String", "b"."TimeOnly", "b"."TimeSpan" +FROM "BasicTypesEntities" AS "b" +WHERE NULLIF("b"."Int", 9) > 1 +"""); + } + + #endregion Conditional + #region Bitwise public override async Task Bitwise_or(bool async)