Skip to content

Commit 0337960

Browse files
authored
Use XOR to translate some == and != expressions (#34124)
* Use XOR to translate some `==` and `!=` expressions When the parent expression is not a predicate, translate `x != y` to: ```sql x ^ y ``` instead of ```sql CASE WHEN x <> y THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END ``` Similarly, translate `x == y` to: ```sql x ^ y ^ 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 for simple cases (comparison of BIT expressions).
1 parent 4649fb3 commit 0337960

File tree

3 files changed

+51
-97
lines changed

3 files changed

+51
-97
lines changed

src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,32 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres
346346

347347
_isSearchCondition = parentIsSearchCondition;
348348

349+
if (!parentIsSearchCondition
350+
&& newLeft.Type == typeof(bool) && newRight.Type == typeof(bool)
351+
&& sqlBinaryExpression.OperatorType is ExpressionType.NotEqual or ExpressionType.Equal)
352+
{
353+
// on BIT, "lhs != rhs" is the same as "lhs ^ rhs", except that the
354+
// first is a boolean, the second is a BIT
355+
var result = _sqlExpressionFactory.MakeBinary(
356+
ExpressionType.ExclusiveOr,
357+
newLeft,
358+
newRight,
359+
sqlBinaryExpression.TypeMapping)!;
360+
361+
// "lhs == rhs" is the same as "NOT(lhs == rhs)" aka "lhs ^ rhs ^ 1"
362+
if (sqlBinaryExpression.OperatorType is ExpressionType.Equal)
363+
{
364+
result = _sqlExpressionFactory.MakeBinary(
365+
ExpressionType.ExclusiveOr,
366+
result,
367+
_sqlExpressionFactory.Constant(true, result.TypeMapping),
368+
result.TypeMapping
369+
)!;
370+
}
371+
372+
return result;
373+
}
374+
349375
sqlBinaryExpression = sqlBinaryExpression.Update(newLeft, newRight);
350376
var condition = sqlBinaryExpression.OperatorType is ExpressionType.AndAlso
351377
or ExpressionType.OrElse

test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,12 +1396,9 @@ public override async Task Where_bool_member_and_parameter_compared_to_binary_ex
13961396
SELECT [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock]
13971397
FROM [Products] AS [p]
13981398
WHERE [p].[Discontinued] = CASE
1399-
WHEN CASE
1400-
WHEN [p].[ProductID] > 50 THEN CAST(1 AS bit)
1401-
ELSE CAST(0 AS bit)
1402-
END <> @__prm_0 THEN CAST(1 AS bit)
1399+
WHEN [p].[ProductID] > 50 THEN CAST(1 AS bit)
14031400
ELSE CAST(0 AS bit)
1404-
END
1401+
END ^ @__prm_0
14051402
""");
14061403
}
14071404

test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs

Lines changed: 23 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,7 @@ public override async Task Rewrite_compare_bool_with_bool(bool async)
252252

253253
AssertSql(
254254
"""
255-
SELECT [e].[Id], CASE
256-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
257-
ELSE CAST(0 AS bit)
258-
END AS [X]
255+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) AS [X]
259256
FROM [Entities1] AS [e]
260257
""",
261258
//
@@ -280,10 +277,7 @@ FROM [Entities1] AS [e]
280277
""",
281278
//
282279
"""
283-
SELECT [e].[Id], CASE
284-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
285-
ELSE CAST(0 AS bit)
286-
END AS [X]
280+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] AS [X]
287281
FROM [Entities1] AS [e]
288282
""",
289283
//
@@ -364,10 +358,7 @@ FROM [Entities1] AS [e]
364358
""",
365359
//
366360
"""
367-
SELECT [e].[Id], CASE
368-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
369-
ELSE CAST(0 AS bit)
370-
END AS [X]
361+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] AS [X]
371362
FROM [Entities1] AS [e]
372363
""",
373364
//
@@ -392,10 +383,7 @@ WHERE [e].[BoolA] <> [e].[NullableBoolB]
392383
""",
393384
//
394385
"""
395-
SELECT [e].[Id], CASE
396-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
397-
ELSE CAST(0 AS bit)
398-
END AS [X]
386+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) AS [X]
399387
FROM [Entities1] AS [e]
400388
""",
401389
//
@@ -476,10 +464,7 @@ FROM [Entities1] AS [e]
476464
""",
477465
//
478466
"""
479-
SELECT [e].[Id], CASE
480-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
481-
ELSE CAST(0 AS bit)
482-
END AS [X]
467+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] AS [X]
483468
FROM [Entities1] AS [e]
484469
""",
485470
//
@@ -504,10 +489,7 @@ WHERE [e].[BoolA] <> [e].[NullableBoolB] OR [e].[NullableBoolB] IS NULL
504489
""",
505490
//
506491
"""
507-
SELECT [e].[Id], CASE
508-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
509-
ELSE CAST(0 AS bit)
510-
END AS [X]
492+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) AS [X]
511493
FROM [Entities1] AS [e]
512494
""",
513495
//
@@ -588,10 +570,7 @@ FROM [Entities1] AS [e]
588570
""",
589571
//
590572
"""
591-
SELECT [e].[Id], CASE
592-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
593-
ELSE CAST(0 AS bit)
594-
END AS [X]
573+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) AS [X]
595574
FROM [Entities1] AS [e]
596575
""",
597576
//
@@ -616,10 +595,7 @@ FROM [Entities1] AS [e]
616595
""",
617596
//
618597
"""
619-
SELECT [e].[Id], CASE
620-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
621-
ELSE CAST(0 AS bit)
622-
END AS [X]
598+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] AS [X]
623599
FROM [Entities1] AS [e]
624600
""",
625601
//
@@ -700,10 +676,7 @@ FROM [Entities1] AS [e]
700676
""",
701677
//
702678
"""
703-
SELECT [e].[Id], CASE
704-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
705-
ELSE CAST(0 AS bit)
706-
END AS [X]
679+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] AS [X]
707680
FROM [Entities1] AS [e]
708681
""",
709682
//
@@ -728,10 +701,7 @@ WHERE [e].[BoolA] <> [e].[NullableBoolB] OR [e].[NullableBoolB] IS NULL
728701
""",
729702
//
730703
"""
731-
SELECT [e].[Id], CASE
732-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
733-
ELSE CAST(0 AS bit)
734-
END AS [X]
704+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) AS [X]
735705
FROM [Entities1] AS [e]
736706
""",
737707
//
@@ -812,10 +782,7 @@ FROM [Entities1] AS [e]
812782
""",
813783
//
814784
"""
815-
SELECT [e].[Id], CASE
816-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
817-
ELSE CAST(0 AS bit)
818-
END AS [X]
785+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) AS [X]
819786
FROM [Entities1] AS [e]
820787
""",
821788
//
@@ -840,10 +807,7 @@ FROM [Entities1] AS [e]
840807
""",
841808
//
842809
"""
843-
SELECT [e].[Id], CASE
844-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
845-
ELSE CAST(0 AS bit)
846-
END AS [X]
810+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] AS [X]
847811
FROM [Entities1] AS [e]
848812
""",
849813
//
@@ -924,10 +888,7 @@ FROM [Entities1] AS [e]
924888
""",
925889
//
926890
"""
927-
SELECT [e].[Id], CASE
928-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
929-
ELSE CAST(0 AS bit)
930-
END AS [X]
891+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) AS [X]
931892
FROM [Entities1] AS [e]
932893
""",
933894
//
@@ -952,10 +913,7 @@ FROM [Entities1] AS [e]
952913
""",
953914
//
954915
"""
955-
SELECT [e].[Id], CASE
956-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
957-
ELSE CAST(0 AS bit)
958-
END AS [X]
916+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] AS [X]
959917
FROM [Entities1] AS [e]
960918
""",
961919
//
@@ -1036,10 +994,7 @@ FROM [Entities1] AS [e]
1036994
""",
1037995
//
1038996
"""
1039-
SELECT [e].[Id], CASE
1040-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
1041-
ELSE CAST(0 AS bit)
1042-
END AS [X]
997+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] AS [X]
1043998
FROM [Entities1] AS [e]
1044999
""",
10451000
//
@@ -1064,10 +1019,7 @@ WHERE [e].[BoolA] <> [e].[NullableBoolB] AND [e].[NullableBoolB] IS NOT NULL
10641019
""",
10651020
//
10661021
"""
1067-
SELECT [e].[Id], CASE
1068-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
1069-
ELSE CAST(0 AS bit)
1070-
END AS [X]
1022+
SELECT [e].[Id], [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) AS [X]
10711023
FROM [Entities1] AS [e]
10721024
""",
10731025
//
@@ -1756,10 +1708,7 @@ public override async Task Compare_complex_equal_equal_equal(bool async)
17561708
"""
17571709
SELECT [e].[Id]
17581710
FROM [Entities1] AS [e]
1759-
WHERE CASE
1760-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
1761-
ELSE CAST(0 AS bit)
1762-
END = CASE
1711+
WHERE [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) = CASE
17631712
WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit)
17641713
ELSE CAST(0 AS bit)
17651714
END
@@ -1798,10 +1747,7 @@ public override async Task Compare_complex_equal_not_equal_equal(bool async)
17981747
"""
17991748
SELECT [e].[Id]
18001749
FROM [Entities1] AS [e]
1801-
WHERE CASE
1802-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
1803-
ELSE CAST(0 AS bit)
1804-
END <> CASE
1750+
WHERE [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit) <> CASE
18051751
WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit)
18061752
ELSE CAST(0 AS bit)
18071753
END
@@ -1840,10 +1786,7 @@ public override async Task Compare_complex_not_equal_equal_equal(bool async)
18401786
"""
18411787
SELECT [e].[Id]
18421788
FROM [Entities1] AS [e]
1843-
WHERE CASE
1844-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
1845-
ELSE CAST(0 AS bit)
1846-
END = CASE
1789+
WHERE [e].[BoolA] ^ [e].[BoolB] = CASE
18471790
WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit)
18481791
ELSE CAST(0 AS bit)
18491792
END
@@ -1882,10 +1825,7 @@ public override async Task Compare_complex_not_equal_not_equal_equal(bool async)
18821825
"""
18831826
SELECT [e].[Id]
18841827
FROM [Entities1] AS [e]
1885-
WHERE CASE
1886-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
1887-
ELSE CAST(0 AS bit)
1888-
END <> CASE
1828+
WHERE [e].[BoolA] ^ [e].[BoolB] <> CASE
18891829
WHEN [e].[IntA] = [e].[IntB] THEN CAST(1 AS bit)
18901830
ELSE CAST(0 AS bit)
18911831
END
@@ -1924,10 +1864,7 @@ public override async Task Compare_complex_not_equal_equal_not_equal(bool async)
19241864
"""
19251865
SELECT [e].[Id]
19261866
FROM [Entities1] AS [e]
1927-
WHERE CASE
1928-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
1929-
ELSE CAST(0 AS bit)
1930-
END = CASE
1867+
WHERE [e].[BoolA] ^ [e].[BoolB] = CASE
19311868
WHEN [e].[IntA] <> [e].[IntB] THEN CAST(1 AS bit)
19321869
ELSE CAST(0 AS bit)
19331870
END
@@ -1966,10 +1903,7 @@ public override async Task Compare_complex_not_equal_not_equal_not_equal(bool as
19661903
"""
19671904
SELECT [e].[Id]
19681905
FROM [Entities1] AS [e]
1969-
WHERE CASE
1970-
WHEN [e].[BoolA] <> [e].[BoolB] THEN CAST(1 AS bit)
1971-
ELSE CAST(0 AS bit)
1972-
END <> CASE
1906+
WHERE [e].[BoolA] ^ [e].[BoolB] <> CASE
19731907
WHEN [e].[IntA] <> [e].[IntB] THEN CAST(1 AS bit)
19741908
ELSE CAST(0 AS bit)
19751909
END
@@ -4512,10 +4446,7 @@ public override async Task Is_null_on_column_followed_by_OrElse_optimizes_nullab
45124446
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]
45134447
FROM [Entities1] AS [e]
45144448
WHERE CASE
4515-
WHEN [e].[NullableBoolA] IS NULL THEN CASE
4516-
WHEN [e].[BoolA] = [e].[BoolB] THEN CAST(1 AS bit)
4517-
ELSE CAST(0 AS bit)
4518-
END
4449+
WHEN [e].[NullableBoolA] IS NULL THEN [e].[BoolA] ^ [e].[BoolB] ^ CAST(1 AS bit)
45194450
WHEN [e].[NullableBoolC] IS NULL THEN CASE
45204451
WHEN ([e].[NullableBoolA] <> [e].[NullableBoolC] OR [e].[NullableBoolA] IS NULL OR [e].[NullableBoolC] IS NULL) AND ([e].[NullableBoolA] IS NOT NULL OR [e].[NullableBoolC] IS NOT NULL) THEN CAST(1 AS bit)
45214452
ELSE CAST(0 AS bit)

0 commit comments

Comments
 (0)