From b2b6f7af53bfd66857ed4baf0fae84f834b6c073 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Mon, 4 Nov 2019 14:00:14 -0800 Subject: [PATCH] Fix to #18725 - Optimize out null checks when translating Enum flags One of the binary expression optimizations is op(a, b) == null -> a == null || b == null. This is valid for all binary operators except logical (AndAlso, OrElse). Before we used to also skip this optimization for bitwise (And, Or). However, this optimization is valid for bitwise ops, so the fix is to enable it. Resolves #18725 --- ...qlExpressionOptimizingExpressionVisitor.cs | 6 +- .../Query/GearsOfWarQueryTestBase.cs | 70 ++++++- .../GearsOfWarModel/MilitaryRank.cs | 17 +- .../Query/GearsOfWarQuerySqlServerTest.cs | 175 ++++++++++++------ 4 files changed, 193 insertions(+), 75 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs index 294d9f5f9d9..47ba1df1242 100644 --- a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs @@ -241,13 +241,11 @@ private SqlExpression SimplifyNullNotNullExpression( // for coalesce: // (a ?? b) == null -> a == null && b == null // (a ?? b) != null -> a != null || b != null - // for AndAlso, OrElse, And, Or we can't do this optimization + // for AndAlso, OrElse we can't do this optimization // we could do something like this, but it seems too complicated: // (a && b) == null -> a == null && b != 0 || a != 0 && b == null if (sqlBinaryOperand.OperatorType != ExpressionType.AndAlso - && sqlBinaryOperand.OperatorType != ExpressionType.OrElse - && sqlBinaryOperand.OperatorType != ExpressionType.And - && sqlBinaryOperand.OperatorType != ExpressionType.Or) + && sqlBinaryOperand.OperatorType != ExpressionType.OrElse) { var newLeft = SimplifyNullNotNullExpression(operatorType, sqlBinaryOperand.Left, typeof(bool), typeMapping); var newRight = SimplifyNullNotNullExpression(operatorType, sqlBinaryOperand.Right, typeof(bool), typeMapping); diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index fc0e77dd47d..1ec629e813f 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -7513,25 +7513,21 @@ public virtual Task OrderBy_StartsWith_with_null_parameter_as_argument(bool isAs public virtual async Task Where_with_enum_flags_parameter(bool isAsync) { MilitaryRank? rank = MilitaryRank.Private; - await AssertQuery( isAsync, ss => ss.Set().Where(g => (g.Rank & rank) == rank)); rank = null; - await AssertQuery( isAsync, ss => ss.Set().Where(g => (g.Rank & rank) == rank)); rank = MilitaryRank.Corporal; - await AssertQuery( isAsync, ss => ss.Set().Where(g => (g.Rank | rank) != rank)); rank = null; - await AssertQuery( isAsync, ss => ss.Set().Where(g => (g.Rank | rank) != rank)); @@ -7547,6 +7543,72 @@ public virtual Task FirstOrDefault_navigation_access_entity_equality_in_where_pr .Where(f => f.Capital == ss.Set().OrderBy(s => s.Nickname).FirstOrDefault().CityOfBirth)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Bitwise_operation_with_non_null_parameter_optimizes_null_checks(bool isAsync) + { + var ranks = MilitaryRank.Corporal | MilitaryRank.Sergeant | MilitaryRank.General; + + await AssertQuery( + isAsync, + ss => ss.Set().Where(g => (g.Rank & ranks) != 0)); + + await AssertQueryScalar( + isAsync, + ss => ss.Set().Select(g => (g.Rank | ranks) == ranks)); + + await AssertQueryScalar( + isAsync, + ss => ss.Set().Select(g => (g.Rank | (g.Rank | (ranks | (g.Rank | ranks)))) == ranks)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Bitwise_operation_with_null_arguments(bool isAsync) + { + await AssertQuery( + isAsync, + ss => ss.Set().Where(w => (w.AmmunitionType & AmmunitionType.Cartridge) == null)); + + await AssertQuery( + isAsync, + ss => ss.Set().Where(w => (w.AmmunitionType | AmmunitionType.Shell) == null)); + + AmmunitionType? prm = null; + await AssertQuery( + isAsync, + ss => ss.Set().Where(w => (w.AmmunitionType | AmmunitionType.Shell) == prm)); + + await AssertQuery( + isAsync, + ss => ss.Set().Where(w => (w.AmmunitionType & prm) == prm)); + + prm = AmmunitionType.Shell; + await AssertQuery( + isAsync, + ss => ss.Set().Where(w => (w.AmmunitionType & prm) != 0)); + + prm = AmmunitionType.Cartridge; + await AssertQuery( + isAsync, + ss => ss.Set().Where(w => (w.AmmunitionType & prm) == prm)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Logical_operation_with_non_null_parameter_optimizes_null_checks(bool isAsync) + { + var prm = true; + await AssertQuery( + isAsync, + ss => ss.Set().Where(g => (g.HasSoulPatch && prm) != prm)); + + prm = false; + await AssertQuery( + isAsync, + ss => ss.Set().Where(g => (g.HasSoulPatch || prm) != prm)); + } + protected async Task AssertTranslationFailed(Func testCode) { Assert.Contains( diff --git a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/MilitaryRank.cs b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/MilitaryRank.cs index b1565a70584..476fbd27897 100644 --- a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/MilitaryRank.cs +++ b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/MilitaryRank.cs @@ -8,13 +8,14 @@ namespace Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel [Flags] public enum MilitaryRank { - Private = 0, - Corporal = 1, - Sergeant = 2, - Lieutenant = 4, - Captain = 8, - Major = 16, - Colonel = 32, - General = 64 + None = 0, + Private = 1, + Corporal = 2, + Sergeant = 4, + Lieutenant = 8, + Captain = 16, + Major = 32, + Colonel = 64, + General = 128 } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 9c45d62ab35..90f9bb60554 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -364,7 +364,7 @@ public override async Task Where_enum(bool isAsync) AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Rank] = 2)"); +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Rank] = 4)"); } public override async Task Where_nullable_enum_with_constant(bool isAsync) @@ -422,11 +422,11 @@ public override async Task Where_bitwise_and_enum(bool isAsync) AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 1) > 0)", +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 2) > 0)", // @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 1) = 1)"); +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 2) = 2)"); } public override async Task Where_bitwise_and_integral(bool isAsync) @@ -504,7 +504,7 @@ public override async Task Where_bitwise_or_enum(bool isAsync) AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] | 1) > 0)"); +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] | 2) > 0)"); } public override async Task Bitwise_projects_values_in_select(bool isAsync) @@ -513,14 +513,14 @@ public override async Task Bitwise_projects_values_in_select(bool isAsync) AssertSql( @"SELECT TOP(1) CASE - WHEN ([g].[Rank] & 1) = 1 THEN CAST(1 AS bit) + WHEN ([g].[Rank] & 2) = 2 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [BitwiseTrue], CASE - WHEN ([g].[Rank] & 1) = 2 THEN CAST(1 AS bit) + WHEN ([g].[Rank] & 2) = 4 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -END AS [BitwiseFalse], [g].[Rank] & 1 AS [BitwiseValue] +END AS [BitwiseFalse], [g].[Rank] & 2 AS [BitwiseValue] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 1) = 1)"); +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 2) = 2)"); } public override async Task Where_enum_has_flag(bool isAsync) @@ -530,11 +530,11 @@ public override async Task Where_enum_has_flag(bool isAsync) AssertSql( @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 1) = 1)", +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 2) = 2)", // @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 9) = 9)", +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 18) = 18)", // @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] @@ -546,7 +546,7 @@ WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 1) = 1)", // @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ((1 & [g].[Rank]) = [g].[Rank])"); +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ((2 & [g].[Rank]) = [g].[Rank])"); } public override async Task Where_enum_has_flag_subquery(bool isAsync) @@ -564,19 +564,15 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ([g].[Rank] & ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL AND ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL))", + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL)", // @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (((1 & ( +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (((2 & ( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -584,15 +580,11 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR (1 & ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL AND ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL))"); + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL)"); } public override async Task Where_enum_has_flag_subquery_with_pushdown(bool isAsync) @@ -610,19 +602,15 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ([g].[Rank] & ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL AND ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL))", + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL)", // @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (((1 & ( +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (((2 & ( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -630,15 +618,11 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR (1 & ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL AND ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL))"); + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL)"); } public override async Task Where_enum_has_flag_subquery_client_eval(bool isAsync) @@ -656,15 +640,11 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ([g].[Rank] & ( - SELECT TOP(1) [g0].[Rank] - FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL AND ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) OR ( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL))"); + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NULL)"); } public override async Task Where_enum_has_flag_with_non_nullable_parameter(bool isAsync) @@ -672,7 +652,7 @@ public override async Task Where_enum_has_flag_with_non_nullable_parameter(bool await base.Where_enum_has_flag_with_non_nullable_parameter(isAsync); AssertSql( - @"@__parameter_0='1' + @"@__parameter_0='2' SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] @@ -684,7 +664,7 @@ public override async Task Where_has_flag_with_nullable_parameter(bool isAsync) await base.Where_has_flag_with_nullable_parameter(isAsync); AssertSql( - @"@__parameter_0='1' (Nullable = true) + @"@__parameter_0='2' (Nullable = true) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] @@ -697,14 +677,14 @@ public override async Task Select_enum_has_flag(bool isAsync) AssertSql( @"SELECT TOP(1) CASE - WHEN ([g].[Rank] & 1) = 1 THEN CAST(1 AS bit) + WHEN ([g].[Rank] & 2) = 2 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [hasFlagTrue], CASE - WHEN ([g].[Rank] & 2) = 2 THEN CAST(1 AS bit) + WHEN ([g].[Rank] & 4) = 4 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [hasFlagFalse] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 1) = 1)"); +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & 2) = 2)"); } public override async Task Where_count_subquery_without_collision(bool isAsync) @@ -7458,29 +7438,25 @@ public override async Task Where_with_enum_flags_parameter(bool isAsync) await base.Where_with_enum_flags_parameter(isAsync); AssertSql( - @"@__rank_0='0' (Nullable = true) + @"@__rank_0='1' (Nullable = true) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & @__rank_0) = @__rank_0)", // - @"@__rank_0=NULL (DbType = Int32) - -SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND [g].[Rank] & @__rank_0 IS NULL", +WHERE [g].[Discriminator] IN (N'Gear', N'Officer')", // - @"@__rank_0='1' (Nullable = true) + @"@__rank_0='2' (Nullable = true) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ((([g].[Rank] | @__rank_0) <> @__rank_0) OR [g].[Rank] | @__rank_0 IS NULL)", +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] | @__rank_0) <> @__rank_0)", // - @"@__rank_0=NULL (DbType = Int32) - -SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND [g].[Rank] | @__rank_0 IS NOT NULL"); +WHERE CAST(0 AS bit) = CAST(1 AS bit)"); } public override async Task FirstOrDefault_navigation_access_entity_equality_in_where_predicate_apply_peneding_selector(bool isAsync) @@ -7504,6 +7480,87 @@ WHERE [g].[Discriminator] IN (N'Gear', N'Officer') ORDER BY [g].[Nickname]) IS NULL))"); } + public override async Task Bitwise_operation_with_non_null_parameter_optimizes_null_checks(bool isAsync) + { + await base.Bitwise_operation_with_non_null_parameter_optimizes_null_checks(isAsync); + + AssertSql( + @"@__ranks_0='134' + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([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 +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer')", + // + @"@__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 +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer')"); + } + + public override async Task Bitwise_operation_with_null_arguments(bool isAsync) + { + await base.Bitwise_operation_with_null_arguments(isAsync); + + AssertSql( + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapons] AS [w] +WHERE [w].[AmmunitionType] IS NULL", + // + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapons] AS [w] +WHERE [w].[AmmunitionType] IS NULL", + // + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapons] AS [w] +WHERE [w].[AmmunitionType] IS NULL", + // + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapons] AS [w]", + // + @"@__prm_0='2' (Nullable = true) + +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapons] AS [w] +WHERE (([w].[AmmunitionType] & @__prm_0) <> 0) OR [w].[AmmunitionType] IS NULL", + // + @"@__prm_0='1' (Nullable = true) + +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapons] AS [w] +WHERE ([w].[AmmunitionType] & @__prm_0) = @__prm_0"); + } + + public override async Task Logical_operation_with_non_null_parameter_optimizes_null_checks(bool isAsync) + { + await base.Logical_operation_with_non_null_parameter_optimizes_null_checks(isAsync); + + AssertSql( + @"@__prm_0='True' + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[HasSoulPatch] <> @__prm_0)", + // + @"@__prm_0='False' + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[HasSoulPatch] <> @__prm_0)"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); }