From 15dba34499ac56d454d758b07078144139859d57 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Fri, 5 Jul 2024 15:07:30 +0200 Subject: [PATCH 1/3] Use XOR to translate some == and != expressions When the parent expression is not a predicate, translate `x != y` to: ```sql CAST(x ^ y AS BIT) ``` instead of ```sql CASE WHEN x <> y THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END ``` Similarly, translate `x == y` to: ```sql CAST(x ^ y AS BIT) ^ CAST(1 AS bit) ``` instead of ```sql CASE WHEN x == y THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END ``` Contributes to #34001. --- .../SearchConditionConvertingExpressionVisitor.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs index 550a78602db..4fed487bd36 100644 --- a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs @@ -347,16 +347,21 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres _isSearchCondition = parentIsSearchCondition; if (!parentIsSearchCondition - && newLeft.Type == typeof(bool) && newRight.Type == typeof(bool) + && (newRight.Type == typeof(bool) || newLeft.Type.IsEnum || newLeft.Type.IsInteger()) && sqlBinaryExpression.OperatorType is ExpressionType.NotEqual or ExpressionType.Equal) { - // on BIT, "lhs != rhs" is the same as "lhs ^ rhs", except that the - // first is a boolean, the second is a BIT + // "lhs != rhs" is the same as "CAST(lhs ^ rhs AS BIT)", except that + // the first is a boolean, the second is a BIT var result = _sqlExpressionFactory.MakeBinary( ExpressionType.ExclusiveOr, newLeft, newRight, - sqlBinaryExpression.TypeMapping)!; + newLeft.TypeMapping)!; + + if (result.Type != typeof(bool)) + { + result = _sqlExpressionFactory.Convert(result, typeof(bool), sqlBinaryExpression.TypeMapping); + } // "lhs == rhs" is the same as "NOT(lhs == rhs)" aka "lhs ^ rhs ^ 1" if (sqlBinaryExpression.OperatorType is ExpressionType.Equal) From 7b29e48a1f534e75f2e3a1de906b1e6b3ab6a81c Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Fri, 5 Jul 2024 15:31:35 +0200 Subject: [PATCH 2/3] Update baselines --- .../Query/GearsOfWarQuerySqlServerTest.cs | 46 +++----------- .../NorthwindDbFunctionsQuerySqlServerTest.cs | 10 +--- .../Query/NullSemanticsQuerySqlServerTest.cs | 60 ++++--------------- .../Query/OwnedQuerySqlServerTest.cs | 5 +- .../Query/TPCGearsOfWarQuerySqlServerTest.cs | 46 +++----------- .../Query/TPTGearsOfWarQuerySqlServerTest.cs | 46 +++----------- .../TemporalGearsOfWarQuerySqlServerTest.cs | 46 +++----------- .../Query/TemporalOwnedQuerySqlServerTest.cs | 5 +- 8 files changed, 48 insertions(+), 216 deletions(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 77fd634944c..de530e0f151 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -553,13 +553,7 @@ public override async Task Bitwise_projects_values_in_select(bool async) AssertSql( """ -SELECT TOP(1) CASE - WHEN [g].[Rank] & 2 = 2 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [BitwiseTrue], CASE - WHEN [g].[Rank] & 2 = 4 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [BitwiseFalse], [g].[Rank] & 2 AS [BitwiseValue] +SELECT TOP(1) CAST(([g].[Rank] & 2) ^ 2 AS bit) ^ CAST(1 AS bit) AS [BitwiseTrue], CAST(([g].[Rank] & 2) ^ 4 AS bit) ^ CAST(1 AS bit) AS [BitwiseFalse], [g].[Rank] & 2 AS [BitwiseValue] FROM [Gears] AS [g] WHERE [g].[Rank] & 2 = 2 """); @@ -722,13 +716,7 @@ public override async Task Select_enum_has_flag(bool async) AssertSql( """ -SELECT TOP(1) CASE - WHEN [g].[Rank] & 2 = 2 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [hasFlagTrue], CASE - WHEN [g].[Rank] & 4 = 4 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [hasFlagFalse] +SELECT TOP(1) CAST(([g].[Rank] & 2) ^ 2 AS bit) ^ CAST(1 AS bit) AS [hasFlagTrue], CAST(([g].[Rank] & 4) ^ 4 AS bit) ^ CAST(1 AS bit) AS [hasFlagFalse] FROM [Gears] AS [g] WHERE [g].[Rank] & 2 = 2 """); @@ -1087,10 +1075,7 @@ public override async Task Select_null_propagation_negative1(bool async) AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] AS [g] @@ -1215,10 +1200,7 @@ public override async Task Select_null_propagation_negative9(bool async) AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] AS [g] @@ -6332,19 +6314,13 @@ public override async Task OrderBy_same_expression_containing_IsNull_correctly_d AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] AS [g] ORDER BY CASE WHEN CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) @@ -7511,20 +7487,14 @@ WHERE [g].[Rank] & @__ranks_0 <> 0 """ @__ranks_0='134' -SELECT CASE - WHEN [g].[Rank] | @__ranks_0 = @__ranks_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(([g].[Rank] | @__ranks_0) ^ @__ranks_0 AS bit) ^ CAST(1 AS bit) FROM [Gears] AS [g] """, // """ @__ranks_0='134' -SELECT CASE - WHEN [g].[Rank] | [g].[Rank] | @__ranks_0 | [g].[Rank] | @__ranks_0 = @__ranks_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(([g].[Rank] | [g].[Rank] | @__ranks_0 | [g].[Rank] | @__ranks_0) ^ @__ranks_0 AS bit) ^ CAST(1 AS bit) FROM [Gears] AS [g] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs index 0676b4a7b45..deaa0264de9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs @@ -888,10 +888,7 @@ await AssertQueryScalar( AssertSql( """ -SELECT CASE - WHEN ISNUMERIC(CONVERT(varchar(100), [o].[OrderDate])) = 1 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(ISNUMERIC(CONVERT(varchar(100), [o].[OrderDate])) ^ 1 AS bit) ^ CAST(1 AS bit) FROM [Orders] AS [o] WHERE ISNUMERIC(CONVERT(varchar(100), [o].[OrderDate])) <> 1 """); @@ -910,10 +907,7 @@ await AssertQueryScalar( AssertSql( """ -SELECT CASE - WHEN ISNUMERIC(CONVERT(varchar(100), [o].[UnitPrice])) = 1 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(ISNUMERIC(CONVERT(varchar(100), [o].[UnitPrice])) ^ 1 AS bit) ^ CAST(1 AS bit) FROM [Order Details] AS [o] WHERE ISNUMERIC(CONVERT(varchar(100), [o].[UnitPrice])) = 1 """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index db5ab5edae1..63b99397333 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -22,10 +22,7 @@ public override async Task Rewrite_compare_int_with_int(bool async) AssertSql( """ -SELECT [e].[Id], CASE - WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [X] +SELECT [e].[Id], CAST([e].[IntA] ^ [e].[IntB] AS bit) ^ CAST(1 AS bit) AS [X] FROM [Entities1] AS [e] """, // @@ -78,10 +75,7 @@ FROM [Entities1] AS [e] """, // """ -SELECT [e].[Id], CASE - WHEN [e].[IntA] <> [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [X] +SELECT [e].[Id], CAST([e].[IntA] ^ [e].[IntB] AS bit) AS [X] FROM [Entities1] AS [e] """, // @@ -134,10 +128,7 @@ FROM [Entities1] AS [e] """, // """ -SELECT [e].[Id], CASE - WHEN [e].[IntA] <> [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [X] +SELECT [e].[Id], CAST([e].[IntA] ^ [e].[IntB] AS bit) AS [X] FROM [Entities1] AS [e] """, // @@ -190,10 +181,7 @@ FROM [Entities1] AS [e] """, // """ -SELECT [e].[Id], CASE - WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [X] +SELECT [e].[Id], CAST([e].[IntA] ^ [e].[IntB] AS bit) ^ CAST(1 AS bit) AS [X] FROM [Entities1] AS [e] """, // @@ -1708,10 +1696,7 @@ public override async Task Compare_complex_equal_equal_equal(bool async) """ SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) = CASE - WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +WHERE [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) = CAST([e].[IntA] ^ [e].[IntB] AS bit) ^ CAST(1 AS bit) """, // """ @@ -1747,10 +1732,7 @@ public override async Task Compare_complex_equal_not_equal_equal(bool async) """ SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) <> CASE - WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +WHERE [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) <> CAST([e].[IntA] ^ [e].[IntB] AS bit) ^ CAST(1 AS bit) """, // """ @@ -1786,10 +1768,7 @@ public override async Task Compare_complex_not_equal_equal_equal(bool async) """ SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[BoolA] ^ [e].[BoolB] = CASE - WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +WHERE [e].[BoolA] ^ [e].[BoolB] = CAST([e].[IntA] ^ [e].[IntB] AS bit) ^ CAST(1 AS bit) """, // """ @@ -1825,10 +1804,7 @@ public override async Task Compare_complex_not_equal_not_equal_equal(bool async) """ SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[BoolA] ^ [e].[BoolB] <> CASE - WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +WHERE [e].[BoolA] ^ [e].[BoolB] <> CAST([e].[IntA] ^ [e].[IntB] AS bit) ^ CAST(1 AS bit) """, // """ @@ -1864,10 +1840,7 @@ public override async Task Compare_complex_not_equal_equal_not_equal(bool async) """ SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[BoolA] ^ [e].[BoolB] = CASE - WHEN [e].[IntA] <> [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +WHERE [e].[BoolA] ^ [e].[BoolB] = CAST([e].[IntA] ^ [e].[IntB] AS bit) """, // """ @@ -1903,10 +1876,7 @@ public override async Task Compare_complex_not_equal_not_equal_not_equal(bool as """ SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[BoolA] ^ [e].[BoolB] <> CASE - WHEN [e].[IntA] <> [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +WHERE [e].[BoolA] ^ [e].[BoolB] <> CAST([e].[IntA] ^ [e].[IntB] AS bit) """, // """ @@ -4279,10 +4249,7 @@ public override async Task Comparison_compared_to_null_check_on_bool(bool async) """ SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] FROM [Entities1] AS [e] -WHERE CASE - WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END <> CASE +WHERE CAST([e].[IntA] ^ [e].[IntB] AS bit) ^ CAST(1 AS bit) <> CASE WHEN [e].[NullableBoolA] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END @@ -4291,10 +4258,7 @@ ELSE CAST(0 AS bit) """ SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] FROM [Entities1] AS [e] -WHERE CASE - WHEN [e].[IntA] <> [e].[IntB] THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CASE +WHERE CAST([e].[IntA] ^ [e].[IntB] AS bit) = CASE WHEN [e].[NullableBoolA] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index 6b28475aee8..df65d197ba4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -273,10 +273,7 @@ public override async Task Navigation_rewrite_on_owned_collection_with_compositi AssertSql( """ SELECT COALESCE(( - SELECT TOP(1) CASE - WHEN [o0].[Id] <> 42 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + SELECT TOP(1) CAST([o0].[Id] ^ 42 AS bit) FROM [Order] AS [o0] WHERE [o].[Id] = [o0].[ClientId] ORDER BY [o0].[Id]), CAST(0 AS bit)) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index 533c570af09..bfbb9d79f08 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -747,13 +747,7 @@ public override async Task Bitwise_projects_values_in_select(bool async) AssertSql( """ -SELECT TOP(1) CASE - WHEN [u].[Rank] & 2 = 2 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [BitwiseTrue], CASE - WHEN [u].[Rank] & 2 = 4 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [BitwiseFalse], [u].[Rank] & 2 AS [BitwiseValue] +SELECT TOP(1) CAST(([u].[Rank] & 2) ^ 2 AS bit) ^ CAST(1 AS bit) AS [BitwiseTrue], CAST(([u].[Rank] & 2) ^ 4 AS bit) ^ CAST(1 AS bit) AS [BitwiseFalse], [u].[Rank] & 2 AS [BitwiseValue] FROM ( SELECT [g].[Rank] FROM [Gears] AS [g] @@ -1072,13 +1066,7 @@ public override async Task Select_enum_has_flag(bool async) AssertSql( """ -SELECT TOP(1) CASE - WHEN [u].[Rank] & 2 = 2 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [hasFlagTrue], CASE - WHEN [u].[Rank] & 4 = 4 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [hasFlagFalse] +SELECT TOP(1) CAST(([u].[Rank] & 2) ^ 2 AS bit) ^ CAST(1 AS bit) AS [hasFlagTrue], CAST(([u].[Rank] & 4) ^ 4 AS bit) ^ CAST(1 AS bit) AS [hasFlagFalse] FROM ( SELECT [g].[Rank] FROM [Gears] AS [g] @@ -1509,10 +1497,7 @@ public override async Task Select_null_propagation_negative1(bool async) AssertSql( """ SELECT CASE - WHEN [u].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([u].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([u].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM ( @@ -1709,10 +1694,7 @@ public override async Task Select_null_propagation_negative9(bool async) AssertSql( """ SELECT CASE - WHEN [u].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([u].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([u].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM ( @@ -8610,10 +8592,7 @@ public override async Task OrderBy_same_expression_containing_IsNull_correctly_d AssertSql( """ SELECT CASE - WHEN [u].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([u].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([u].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM ( @@ -8625,10 +8604,7 @@ FROM [Officers] AS [o] ) AS [u] ORDER BY CASE WHEN CASE - WHEN [u].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([u].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([u].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) @@ -10045,10 +10021,7 @@ WHERE [u].[Rank] & @__ranks_0 <> 0 """ @__ranks_0='134' -SELECT CASE - WHEN [u].[Rank] | @__ranks_0 = @__ranks_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(([u].[Rank] | @__ranks_0) ^ @__ranks_0 AS bit) ^ CAST(1 AS bit) FROM ( SELECT [g].[Rank] FROM [Gears] AS [g] @@ -10061,10 +10034,7 @@ FROM [Officers] AS [o] """ @__ranks_0='134' -SELECT CASE - WHEN [u].[Rank] | [u].[Rank] | @__ranks_0 | [u].[Rank] | @__ranks_0 = @__ranks_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(([u].[Rank] | [u].[Rank] | @__ranks_0 | [u].[Rank] | @__ranks_0) ^ @__ranks_0 AS bit) ^ CAST(1 AS bit) FROM ( SELECT [g].[Rank] FROM [Gears] AS [g] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index e5eb5ca84f1..2421b566d30 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -711,13 +711,7 @@ public override async Task Bitwise_projects_values_in_select(bool async) AssertSql( """ -SELECT TOP(1) CASE - WHEN [g].[Rank] & 2 = 2 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [BitwiseTrue], CASE - WHEN [g].[Rank] & 2 = 4 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [BitwiseFalse], [g].[Rank] & 2 AS [BitwiseValue] +SELECT TOP(1) CAST(([g].[Rank] & 2) ^ 2 AS bit) ^ CAST(1 AS bit) AS [BitwiseTrue], CAST(([g].[Rank] & 2) ^ 4 AS bit) ^ CAST(1 AS bit) AS [BitwiseFalse], [g].[Rank] & 2 AS [BitwiseValue] FROM [Gears] AS [g] WHERE [g].[Rank] & 2 = 2 """); @@ -916,13 +910,7 @@ public override async Task Select_enum_has_flag(bool async) AssertSql( """ -SELECT TOP(1) CASE - WHEN [g].[Rank] & 2 = 2 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [hasFlagTrue], CASE - WHEN [g].[Rank] & 4 = 4 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [hasFlagFalse] +SELECT TOP(1) CAST(([g].[Rank] & 2) ^ 2 AS bit) ^ CAST(1 AS bit) AS [hasFlagTrue], CAST(([g].[Rank] & 4) ^ 4 AS bit) ^ CAST(1 AS bit) AS [hasFlagFalse] FROM [Gears] AS [g] WHERE [g].[Rank] & 2 = 2 """); @@ -1306,10 +1294,7 @@ public override async Task Select_null_propagation_negative1(bool async) AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] AS [g] @@ -1449,10 +1434,7 @@ public override async Task Select_null_propagation_negative9(bool async) AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] AS [g] @@ -7215,19 +7197,13 @@ public override async Task OrderBy_same_expression_containing_IsNull_correctly_d AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] AS [g] ORDER BY CASE WHEN CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) @@ -8531,20 +8507,14 @@ WHERE [g].[Rank] & @__ranks_0 <> 0 """ @__ranks_0='134' -SELECT CASE - WHEN [g].[Rank] | @__ranks_0 = @__ranks_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(([g].[Rank] | @__ranks_0) ^ @__ranks_0 AS bit) ^ CAST(1 AS bit) FROM [Gears] AS [g] """, // """ @__ranks_0='134' -SELECT CASE - WHEN [g].[Rank] | [g].[Rank] | @__ranks_0 | [g].[Rank] | @__ranks_0 = @__ranks_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(([g].[Rank] | [g].[Rank] | @__ranks_0 | [g].[Rank] | @__ranks_0) ^ @__ranks_0 AS bit) ^ CAST(1 AS bit) FROM [Gears] AS [g] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 3f18a794b31..5d33396a945 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -2562,10 +2562,7 @@ public override async Task Select_null_propagation_negative1(bool async) AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] @@ -2660,10 +2657,7 @@ public override async Task Select_null_propagation_negative9(bool async) AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] @@ -3688,20 +3682,14 @@ WHERE [g].[Rank] & @__ranks_0 <> 0 """ @__ranks_0='134' -SELECT CASE - WHEN [g].[Rank] | @__ranks_0 = @__ranks_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(([g].[Rank] | @__ranks_0) ^ @__ranks_0 AS bit) ^ CAST(1 AS bit) FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] """, // """ @__ranks_0='134' -SELECT CASE - WHEN [g].[Rank] | [g].[Rank] | @__ranks_0 | [g].[Rank] | @__ranks_0 = @__ranks_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END +SELECT CAST(([g].[Rank] | [g].[Rank] | @__ranks_0 | [g].[Rank] | @__ranks_0) ^ @__ranks_0 AS bit) ^ CAST(1 AS bit) FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] """); } @@ -7032,13 +7020,7 @@ public override async Task Bitwise_projects_values_in_select(bool async) AssertSql( """ -SELECT TOP(1) CASE - WHEN [g].[Rank] & 2 = 2 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [BitwiseTrue], CASE - WHEN [g].[Rank] & 2 = 4 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [BitwiseFalse], [g].[Rank] & 2 AS [BitwiseValue] +SELECT TOP(1) CAST(([g].[Rank] & 2) ^ 2 AS bit) ^ CAST(1 AS bit) AS [BitwiseTrue], CAST(([g].[Rank] & 2) ^ 4 AS bit) ^ CAST(1 AS bit) AS [BitwiseFalse], [g].[Rank] & 2 AS [BitwiseValue] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] WHERE [g].[Rank] & 2 = 2 """); @@ -8335,19 +8317,13 @@ public override async Task OrderBy_same_expression_containing_IsNull_correctly_d AssertSql( """ SELECT CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] ORDER BY CASE WHEN CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(CAST(LEN([g].[Nickname]) AS int) ^ 5 AS bit) ^ CAST(1 AS bit) ELSE NULL END IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) @@ -9302,13 +9278,7 @@ public override async Task Select_enum_has_flag(bool async) AssertSql( """ -SELECT TOP(1) CASE - WHEN [g].[Rank] & 2 = 2 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [hasFlagTrue], CASE - WHEN [g].[Rank] & 4 = 4 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END AS [hasFlagFalse] +SELECT TOP(1) CAST(([g].[Rank] & 2) ^ 2 AS bit) ^ CAST(1 AS bit) AS [hasFlagTrue], CAST(([g].[Rank] & 4) ^ 4 AS bit) ^ CAST(1 AS bit) AS [hasFlagFalse] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] WHERE [g].[Rank] & 2 = 2 """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalOwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalOwnedQuerySqlServerTest.cs index f1b85d890ab..67aedc24e72 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalOwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalOwnedQuerySqlServerTest.cs @@ -290,10 +290,7 @@ public override async Task Navigation_rewrite_on_owned_collection_with_compositi AssertSql( """ SELECT COALESCE(( - SELECT TOP(1) CASE - WHEN [o0].[Id] <> 42 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + SELECT TOP(1) CAST([o0].[Id] ^ 42 AS bit) FROM [Order] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [o0] WHERE [o].[Id] = [o0].[ClientId] ORDER BY [o0].[Id]), CAST(0 AS bit)) From eda838878e749fc309b40912115f32a2bd929a1e Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Tue, 9 Jul 2024 21:14:29 +0200 Subject: [PATCH 3/3] Improve guard and type propagation --- .../Internal/SearchConditionConvertingExpressionVisitor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs index 4fed487bd36..f60f8e35d77 100644 --- a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs @@ -347,7 +347,8 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres _isSearchCondition = parentIsSearchCondition; if (!parentIsSearchCondition - && (newRight.Type == typeof(bool) || newLeft.Type.IsEnum || newLeft.Type.IsInteger()) + && (newLeft.Type == typeof(bool) || newLeft.Type.IsEnum || newLeft.Type.IsInteger()) + && (newRight.Type == typeof(bool) || newRight.Type.IsEnum || newRight.Type.IsInteger()) && sqlBinaryExpression.OperatorType is ExpressionType.NotEqual or ExpressionType.Equal) { // "lhs != rhs" is the same as "CAST(lhs ^ rhs AS BIT)", except that @@ -356,7 +357,7 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres ExpressionType.ExclusiveOr, newLeft, newRight, - newLeft.TypeMapping)!; + null)!; if (result.Type != typeof(bool)) {