From e4ef67eab95d2318185af8263cb2700f42978637 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sun, 28 Jul 2024 11:17:05 +0200 Subject: [PATCH 1/2] Propagate `allowOptimizedExpansion` to `CASE` results If the `CASE` is used in a context that allows optimizations (predicates, where NULL and FALSE are equivalent), the same holds for each of its results. --- src/EFCore.Relational/Query/SqlNullabilityProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index ec8ddff2e8b..e9dc788484d 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -531,7 +531,7 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al continue; } - var newResult = Visit(whenClause.Result, out var resultNullable); + var newResult = Visit(whenClause.Result, allowOptimizedExpansion, out var resultNullable); nullable |= resultNullable; whenClauses.Add(new CaseWhenClause(test, newResult)); @@ -554,7 +554,7 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al SqlExpression? elseResult = null; if (!testEvaluatesToTrue) { - elseResult = Visit(caseExpression.ElseResult, out var elseResultNullable); + elseResult = Visit(caseExpression.ElseResult, allowOptimizedExpansion, out var elseResultNullable); nullable |= elseResultNullable; // if there is no 'else' there is a possibility of null, when none of the conditions are met From 45b3dfe7b7a25328aeac10abce2a2fb0fe18dac1 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sun, 28 Jul 2024 11:19:51 +0200 Subject: [PATCH 2/2] Update baselines --- .../Query/GearsOfWarQuerySqlServerTest.cs | 7 ++----- .../Query/NullSemanticsQuerySqlServerTest.cs | 6 +++--- .../Query/TPCGearsOfWarQuerySqlServerTest.cs | 7 ++----- .../Query/TPTGearsOfWarQuerySqlServerTest.cs | 7 ++----- .../Query/TemporalGearsOfWarQuerySqlServerTest.cs | 7 ++----- .../Query/GearsOfWarQuerySqliteTest.cs | 8 ++------ .../Query/NullSemanticsQuerySqliteTest.cs | 2 +- 7 files changed, 14 insertions(+), 30 deletions(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 0a1f82f4c2d..a6a74ec84f3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -956,7 +956,7 @@ public override async Task Null_propagation_optimization2(bool async) FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL - WHEN [g].[LeaderNickname] LIKE N'%us' AND [g].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit) + WHEN [g].[LeaderNickname] LIKE N'%us' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); @@ -7708,10 +7708,7 @@ WHERE CASE WHEN [g].[HasSoulPatch] = @__prm_0 AND ( SELECT TOP(1) [w].[Name] FROM [Weapons] AS [w] - WHERE [w].[Id] = [g].[SquadId]) = @__prm2_1 AND ( - SELECT TOP(1) [w].[Name] - FROM [Weapons] AS [w] - WHERE [w].[Id] = [g].[SquadId]) IS NOT NULL THEN CAST(1 AS bit) + WHERE [w].[Id] = [g].[SquadId]) = @__prm2_1 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 9b6e3004f16..1f8f8b3fd63 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -2823,7 +2823,7 @@ ELSE CAST(0 AS bit) END ELSE [e].[BoolC] END <> [e].[BoolB] THEN [e].[BoolA] - WHEN ([e].[NullableBoolB] = [e].[NullableBoolC] AND [e].[NullableBoolB] IS NOT NULL AND [e].[NullableBoolC] IS NOT NULL) OR ([e].[NullableBoolB] IS NULL AND [e].[NullableBoolC] IS NULL) THEN CAST(1 AS bit) + WHEN [e].[NullableBoolB] = [e].[NullableBoolC] OR ([e].[NullableBoolB] IS NULL AND [e].[NullableBoolC] IS NULL) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """, @@ -4342,7 +4342,7 @@ public override async Task Is_null_on_column_followed_by_OrElse_optimizes_nullab FROM [Entities1] AS [e] WHERE CASE WHEN [e].[NullableStringA] IS NULL OR [e].[NullableStringB] IS NULL THEN CASE - WHEN ([e].[NullableStringA] = [e].[NullableStringB] AND [e].[NullableStringA] IS NOT NULL AND [e].[NullableStringB] IS NOT NULL) OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN CAST(1 AS bit) + WHEN [e].[NullableStringA] = [e].[NullableStringB] OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END WHEN ([e].[NullableStringA] <> [e].[NullableStringB] OR [e].[NullableStringA] IS NULL OR [e].[NullableStringB] IS NULL) AND ([e].[NullableStringA] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL) THEN CAST(1 AS bit) @@ -4361,7 +4361,7 @@ public override async Task Is_null_on_column_followed_by_OrElse_optimizes_nullab FROM [Entities1] AS [e] WHERE CASE WHEN ([e].[NullableStringA] IS NULL OR [e].[NullableStringB] IS NULL) AND [e].[NullableBoolC] IS NULL THEN CASE - WHEN ([e].[NullableStringA] = [e].[NullableStringB] AND [e].[NullableStringA] IS NOT NULL AND [e].[NullableStringB] IS NOT NULL) OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN CAST(1 AS bit) + WHEN [e].[NullableStringA] = [e].[NullableStringB] OR ([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END WHEN ([e].[NullableStringA] <> [e].[NullableStringB] OR [e].[NullableStringA] IS NULL OR [e].[NullableStringB] IS NULL) AND ([e].[NullableStringA] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL) THEN CAST(1 AS bit) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index 0579ce86f3a..a8a4dfce8bc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -1336,7 +1336,7 @@ FROM [Officers] AS [o] ) AS [u] WHERE CASE WHEN [u].[LeaderNickname] IS NULL THEN NULL - WHEN [u].[LeaderNickname] LIKE N'%us' AND [u].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit) + WHEN [u].[LeaderNickname] LIKE N'%us' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); @@ -10264,10 +10264,7 @@ WHERE CASE WHEN [u].[HasSoulPatch] = @__prm_0 AND ( SELECT TOP(1) [w].[Name] FROM [Weapons] AS [w] - WHERE [w].[Id] = [u].[SquadId]) = @__prm2_1 AND ( - SELECT TOP(1) [w].[Name] - FROM [Weapons] AS [w] - WHERE [w].[Id] = [u].[SquadId]) IS NOT NULL THEN CAST(1 AS bit) + WHERE [w].[Id] = [u].[SquadId]) = @__prm2_1 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index ea8825997c5..2a47c350f85 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -1163,7 +1163,7 @@ FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL - WHEN [g].[LeaderNickname] LIKE N'%us' AND [g].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit) + WHEN [g].[LeaderNickname] LIKE N'%us' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); @@ -8724,10 +8724,7 @@ WHERE CASE WHEN [g].[HasSoulPatch] = @__prm_0 AND ( SELECT TOP(1) [w].[Name] FROM [Weapons] AS [w] - WHERE [w].[Id] = [g].[SquadId]) = @__prm2_1 AND ( - SELECT TOP(1) [w].[Name] - FROM [Weapons] AS [w] - WHERE [w].[Id] = [g].[SquadId]) IS NOT NULL THEN CAST(1 AS bit) + WHERE [w].[Id] = [g].[SquadId]) = @__prm2_1 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 1dcddbbdf2e..05f22a73db4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -1452,10 +1452,7 @@ WHERE CASE WHEN [g].[HasSoulPatch] = @__prm_0 AND ( SELECT TOP(1) [w].[Name] FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w] - WHERE [w].[Id] = [g].[SquadId]) = @__prm2_1 AND ( - SELECT TOP(1) [w].[Name] - FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w] - WHERE [w].[Id] = [g].[SquadId]) IS NOT NULL THEN CAST(1 AS bit) + WHERE [w].[Id] = [g].[SquadId]) = @__prm2_1 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); @@ -7768,7 +7765,7 @@ public override async Task Null_propagation_optimization2(bool async) FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL - WHEN [g].[LeaderNickname] LIKE N'%us' AND [g].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit) + WHEN [g].[LeaderNickname] LIKE N'%us' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 5b3204a837a..0c822d8e003 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -3340,7 +3340,7 @@ public override async Task Null_propagation_optimization2(bool async) FROM "Gears" AS "g" WHERE CASE WHEN "g"."LeaderNickname" IS NULL THEN NULL - ELSE "g"."LeaderNickname" LIKE '%us' AND "g"."LeaderNickname" IS NOT NULL + ELSE "g"."LeaderNickname" LIKE '%us' END """); } @@ -6766,11 +6766,7 @@ WHERE CASE SELECT "w"."Name" FROM "Weapons" AS "w" WHERE "w"."Id" = "g"."SquadId" - LIMIT 1) = @__prm2_1 AND ( - SELECT "w"."Name" - FROM "Weapons" AS "w" - WHERE "w"."Id" = "g"."SquadId" - LIMIT 1) IS NOT NULL + LIMIT 1) = @__prm2_1 ELSE 0 END """); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs index a6c6c770ea5..5018e8481a7 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs @@ -988,7 +988,7 @@ WHEN CASE WHEN "e"."BoolA" THEN ("e"."NullableBoolA" <> "e"."NullableBoolB" OR "e"."NullableBoolA" IS NULL OR "e"."NullableBoolB" IS NULL) AND ("e"."NullableBoolA" IS NOT NULL OR "e"."NullableBoolB" IS NOT NULL) ELSE "e"."BoolC" END <> "e"."BoolB" THEN "e"."BoolA" - ELSE ("e"."NullableBoolB" = "e"."NullableBoolC" AND "e"."NullableBoolB" IS NOT NULL AND "e"."NullableBoolC" IS NOT NULL) OR ("e"."NullableBoolB" IS NULL AND "e"."NullableBoolC" IS NULL) + ELSE "e"."NullableBoolB" = "e"."NullableBoolC" OR ("e"."NullableBoolB" IS NULL AND "e"."NullableBoolC" IS NULL) END """, //