From 1d03d8324aa0e0aef4f7c325b6365fb9e0585ac1 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 8 Jun 2024 23:04:36 +0200 Subject: [PATCH 1/4] Cleanup whitespace --- 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 988e62e9ad0..0ab75f288f7 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -2032,7 +2032,7 @@ protected virtual SqlExpression OptimizeNonNullableNotExpression(SqlUnaryExpress sqlBinaryOperand.TypeMapping)!; } } - break; + break; } return sqlUnaryExpression; @@ -2369,7 +2369,7 @@ private SqlExpression ProcessNullNotNull(SqlUnaryExpression sqlUnaryExpression, return result; } } - break; + break; } return sqlUnaryExpression; From 8326702240dd8ca2eec7c14604ae16bced33b367 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sun, 9 Jun 2024 14:01:21 +0200 Subject: [PATCH 2/4] Add test The new test explicitly constructs a negation of a ternary operator, which is translated as a `NOT (CASE ... END)`. --- .../Query/NorthwindWhereQueryCosmosTest.cs | 14 ++++++++++++++ .../Query/NorthwindWhereQueryTestBase.cs | 9 +++++++++ .../Query/NorthwindWhereQuerySqlServerTest.cs | 15 +++++++++++++++ .../Query/NorthwindWhereQuerySqliteTest.cs | 15 +++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index 9193055635d..7b339e1b598 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -2100,6 +2100,20 @@ public override Task Where_ternary_boolean_condition_with_false_as_result_false( SELECT c FROM root c WHERE ((c["Discriminator"] = "Product") AND false) +"""); + }); + + public override Task Where_ternary_boolean_condition_negated(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_ternary_boolean_condition_negated(a); + + AssertSql( + """ +SELECT c +FROM root c +WHERE ((c["Discriminator"] = "Product") AND NOT(((c["UnitsInStock"] >= 20) ? false : true))) """); }); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index 5805fb6fa45..a50dd017052 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -1465,6 +1465,15 @@ public virtual Task Where_ternary_boolean_condition_with_false_as_result_false(b assertEmpty: true); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_ternary_boolean_condition_negated(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(p => !(p.UnitsInStock >= 20 ? false : true))); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_compare_constructed_equal(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 5b4c0ba2e7f..a5a2689229b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -1706,6 +1706,21 @@ FROM [Products] AS [p] """); } + public override async Task Where_ternary_boolean_condition_negated(bool async) + { + await base.Where_ternary_boolean_condition_negated(async); + + AssertSql( + """ +SELECT [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] +FROM [Products] AS [p] +WHERE CASE + WHEN [p].[UnitsInStock] >= CAST(20 AS smallint) THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(0 AS bit) +"""); + } + public override async Task Where_compare_constructed_equal(bool async) { // Anonymous type to constant comparison. Issue #14672. diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs index 0c225b48f72..32ff2f323ee 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs @@ -14,6 +14,21 @@ public NorthwindWhereQuerySqliteTest(NorthwindQuerySqliteFixture= 20 THEN 0 + ELSE 1 +END) +"""); + } + public override Task Where_datetimeoffset_now_component(bool async) => AssertTranslationFailed(() => base.Where_datetimeoffset_now_component(async)); From b9b1704388691efce54ae9ebd0c26797c6f2a7ad Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 8 Jun 2024 23:04:41 +0200 Subject: [PATCH 3/4] Simplify NOT(CASE ... THEN const) Fixes #33857. --- .../Query/SqlNullabilityProcessor.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 0ab75f288f7..c8fb53f61cf 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -2033,6 +2033,29 @@ protected virtual SqlExpression OptimizeNonNullableNotExpression(SqlUnaryExpress } } break; + + case CaseExpression caseExpression: + { + if (caseExpression.Type == typeof(bool) + && caseExpression.ElseResult is SqlConstantExpression elseResult + && caseExpression.WhenClauses.All(clause => clause.Result is SqlConstantExpression) + ) + { + var clauses = caseExpression.WhenClauses + .Select(clause => new CaseWhenClause( + clause.Test, + _sqlExpressionFactory.Constant(!(bool)(clause.Result as SqlConstantExpression)!.Value!, clause.Result.TypeMapping)) + ) + .ToList(); + var newElseResult = _sqlExpressionFactory.Constant(!(bool)elseResult.Value!, elseResult.TypeMapping); + + return caseExpression.Operand is null + ? _sqlExpressionFactory.Case(clauses, newElseResult) + : _sqlExpressionFactory.Case(caseExpression.Operand, clauses, newElseResult); + } + } + + break; } return sqlUnaryExpression; From d26568f1fc3589acaccd5ff6504223081046a3c6 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 8 Jun 2024 20:19:08 +0200 Subject: [PATCH 4/4] Update tests --- .../ComplexNavigationsQuerySqlServerTest.cs | 6 ++-- ...NavigationsSharedTypeQuerySqlServerTest.cs | 6 ++-- .../Query/NorthwindWhereQuerySqlServerTest.cs | 6 ++-- .../Query/NullSemanticsQuerySqlServerTest.cs | 24 +++++++------- .../NorthwindFunctionsQuerySqliteTest.cs | 16 +++++----- .../Query/NorthwindWhereQuerySqliteTest.cs | 8 ++--- .../Query/NullSemanticsQuerySqliteTest.cs | 32 +++++++++---------- 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 76a2eae5416..45d923f908e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -4127,9 +4127,9 @@ FROM [LevelTwo] AS [l0] ) AS [l1] GROUP BY [l1].[Key] ) AS [l2] ON [l].[Id] = [l2].[Key] AND CASE - WHEN [l2].[Sum] <= 10 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [l2].[Sum] <= 10 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index bc8b3af0b95..fb0cc9ffd47 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -975,9 +975,9 @@ WHERE [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] ) AS [s] GROUP BY [s].[Key] ) AS [s1] ON [l].[Id] = [s1].[Key] AND CASE - WHEN [s1].[Sum] <= 10 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [s1].[Sum] <= 10 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index a5a2689229b..1ba0c1ad5a3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -1715,9 +1715,9 @@ public override async Task Where_ternary_boolean_condition_negated(bool async) SELECT [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] FROM [Products] AS [p] WHERE CASE - WHEN [p].[UnitsInStock] >= CAST(20 AS smallint) THEN CAST(0 AS bit) - ELSE CAST(1 AS bit) -END = CAST(0 AS bit) + WHEN [p].[UnitsInStock] >= CAST(20 AS smallint) 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 f49e1126ef5..8bb7449f6e5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -2543,9 +2543,9 @@ public override async Task Negated_order_comparison_on_nullable_arguments_doesnt SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE - WHEN [e].[NullableIntA] > @__i_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [e].[NullableIntA] > @__i_0 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """, // """ @@ -2554,9 +2554,9 @@ ELSE CAST(0 AS bit) SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE - WHEN [e].[NullableIntA] >= @__i_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [e].[NullableIntA] >= @__i_0 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """, // """ @@ -2565,9 +2565,9 @@ ELSE CAST(0 AS bit) SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE - WHEN [e].[NullableIntA] < @__i_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [e].[NullableIntA] < @__i_0 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """, // """ @@ -2576,9 +2576,9 @@ ELSE CAST(0 AS bit) SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE - WHEN [e].[NullableIntA] <= @__i_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [e].[NullableIntA] <= @__i_0 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs index 46b2730852d..b5552e1a602 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs @@ -878,10 +878,10 @@ public override async Task String_Contains_negated_in_predicate(bool async) """ SELECT "c"."CustomerID", "c"."Address", "c"."City", "c"."CompanyName", "c"."ContactName", "c"."ContactTitle", "c"."Country", "c"."Fax", "c"."Phone", "c"."PostalCode", "c"."Region" FROM "Customers" AS "c" -WHERE NOT (CASE - WHEN instr("c"."CompanyName", "c"."ContactName") > 0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN instr("c"."CompanyName", "c"."ContactName") > 0 THEN 0 + ELSE 1 +END """); } @@ -891,10 +891,10 @@ public override async Task String_Contains_negated_in_projection(bool async) AssertSql( """ -SELECT "c"."CustomerID" AS "Id", NOT (CASE - WHEN instr("c"."CompanyName", "c"."ContactName") > 0 THEN 1 - ELSE 0 -END) AS "Value" +SELECT "c"."CustomerID" AS "Id", CASE + WHEN instr("c"."CompanyName", "c"."ContactName") > 0 THEN 0 + ELSE 1 +END AS "Value" FROM "Customers" AS "c" """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs index 32ff2f323ee..31a2155adb1 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs @@ -22,10 +22,10 @@ public override async Task Where_ternary_boolean_condition_negated(bool async) """ SELECT "p"."ProductID", "p"."Discontinued", "p"."ProductName", "p"."SupplierID", "p"."UnitPrice", "p"."UnitsInStock" FROM "Products" AS "p" -WHERE NOT (CASE - WHEN "p"."UnitsInStock" >= 20 THEN 0 - ELSE 1 -END) +WHERE CASE + WHEN "p"."UnitsInStock" >= 20 THEN 1 + ELSE 0 +END """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs index 4daaae16702..d5e16911667 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs @@ -475,10 +475,10 @@ public override async Task Negated_order_comparison_on_nullable_arguments_doesnt SELECT "e"."Id" FROM "Entities1" AS "e" -WHERE NOT (CASE - WHEN "e"."NullableIntA" > @__i_0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN "e"."NullableIntA" > @__i_0 THEN 0 + ELSE 1 +END """, // """ @@ -486,10 +486,10 @@ ELSE 0 SELECT "e"."Id" FROM "Entities1" AS "e" -WHERE NOT (CASE - WHEN "e"."NullableIntA" >= @__i_0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN "e"."NullableIntA" >= @__i_0 THEN 0 + ELSE 1 +END """, // """ @@ -497,10 +497,10 @@ ELSE 0 SELECT "e"."Id" FROM "Entities1" AS "e" -WHERE NOT (CASE - WHEN "e"."NullableIntA" < @__i_0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN "e"."NullableIntA" < @__i_0 THEN 0 + ELSE 1 +END """, // """ @@ -508,10 +508,10 @@ ELSE 0 SELECT "e"."Id" FROM "Entities1" AS "e" -WHERE NOT (CASE - WHEN "e"."NullableIntA" <= @__i_0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN "e"."NullableIntA" <= @__i_0 THEN 0 + ELSE 1 +END """); }