Skip to content

Commit f4472db

Browse files
committed
Also simplify a == null ? a : null and various other cleanup
1 parent d598bfb commit f4472db

17 files changed

+416
-266
lines changed

EFCore.sln.DotSettings

+1
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ The .NET Foundation licenses this file to you under the MIT license.
356356
<s:Boolean x:Key="/Default/UserDictionary/Words/=subquery/@EntryIndexedValue">True</s:Boolean>
357357
<s:Boolean x:Key="/Default/UserDictionary/Words/=subquery_0027s/@EntryIndexedValue">True</s:Boolean>
358358
<s:Boolean x:Key="/Default/UserDictionary/Words/=transactionality/@EntryIndexedValue">True</s:Boolean>
359+
<s:Boolean x:Key="/Default/UserDictionary/Words/=uncoalesce/@EntryIndexedValue">True</s:Boolean>
359360
<s:Boolean x:Key="/Default/UserDictionary/Words/=uncoalescing/@EntryIndexedValue">True</s:Boolean>
360361
<s:Boolean x:Key="/Default/UserDictionary/Words/=unconfigured/@EntryIndexedValue">True</s:Boolean>
361362
<s:Boolean x:Key="/Default/UserDictionary/Words/=unequality/@EntryIndexedValue">True</s:Boolean>

src/EFCore.Relational/Query/SqlExpressionFactory.cs

+24-38
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,10 @@ public virtual SqlExpression Case(
825825
elseResult = lastCase.ElseResult;
826826
}
827827

828-
// Optimize:
828+
// Simplify:
829+
// a == null ? null : a -> a
830+
// a != null ? a : null -> a
831+
// And lift:
829832
// a == b ? null : a -> NULLIF(a, b)
830833
// a != b ? a : null -> NULLIF(a, b)
831834
if (operand is null
@@ -835,18 +838,28 @@ public virtual SqlExpression Case(
835838
Test: SqlBinaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } binary,
836839
Result: var result
837840
}
838-
])
841+
]
842+
&& binary.OperatorType switch
843+
{
844+
ExpressionType.Equal when result is SqlConstantExpression { Value: null } && elseResult is not null => elseResult,
845+
ExpressionType.NotEqual when elseResult is null or SqlConstantExpression { Value: null } => result,
846+
_ => null
847+
} is SqlExpression conditionalResult)
839848
{
840-
switch (binary.OperatorType)
849+
var (left, right) = (binary.Left, binary.Right);
850+
851+
if (left.Equals(conditionalResult))
841852
{
842-
case ExpressionType.Equal
843-
when result is SqlConstantExpression { Value: null }
844-
&& elseResult is not null
845-
&& TryTranslateToNullIf(elseResult, out var nullIfTranslation):
846-
case ExpressionType.NotEqual
847-
when elseResult is null or SqlConstantExpression { Value: null }
848-
&& TryTranslateToNullIf(result, out nullIfTranslation):
849-
return nullIfTranslation;
853+
return right is SqlConstantExpression { Value: null }
854+
? left
855+
: Function("NULLIF", [left, right], nullable: true, [false, false], left.Type, left.TypeMapping);
856+
}
857+
858+
if (right.Equals(conditionalResult))
859+
{
860+
return left is SqlConstantExpression { Value: null }
861+
? right
862+
: Function("NULLIF", [right, left], nullable: true, [false, false], right.Type, right.TypeMapping);
850863
}
851864
}
852865

@@ -862,33 +875,6 @@ bool IsSkipped(CaseWhenClause clause)
862875

863876
bool IsMatched(CaseWhenClause clause)
864877
=> operand is null && clause.Test is SqlConstantExpression { Value: true };
865-
866-
bool TryTranslateToNullIf(SqlExpression conditionalResult, [NotNullWhen(true)] out SqlExpression? nullIfTranslation)
867-
{
868-
var (left, right) = (binary.Left, binary.Right);
869-
870-
// If one of sides of the equality is equal to the result of the conditional - a == b ? null : a - convert to
871-
// NULLIF(a, b).
872-
// Specifically refrain from doing so for when the other side is a null constant, as that would transform a == null ? null : a
873-
// to NULLIF(a, NULL), which we don't want.
874-
875-
if (left.Equals(conditionalResult) && right is not SqlConstantExpression { Value: null })
876-
{
877-
nullIfTranslation = Function(
878-
"NULLIF", [left, right], true, [false, false], left.Type, left.TypeMapping);
879-
return true;
880-
}
881-
882-
if (right.Equals(conditionalResult) && left is not SqlConstantExpression { Value: null })
883-
{
884-
nullIfTranslation = Function(
885-
"NULLIF", [right, left], true, [false, false], right.Type, right.TypeMapping);
886-
return true;
887-
}
888-
889-
nullIfTranslation = null;
890-
return false;
891-
}
892878
}
893879

894880
/// <inheritdoc />

test/EFCore.Cosmos.FunctionalTests/Query/Translations/MiscellaneousTranslationsCosmosTest.cs

-60
Original file line numberDiff line numberDiff line change
@@ -175,66 +175,6 @@ public override async Task TimeSpan_Compare_to_simple_zero(bool async, bool comp
175175

176176
#endregion Compare
177177

178-
#region Uncoalescing conditional / NullIf
179-
180-
public override Task Uncoalescing_conditional_with_equality_left(bool async)
181-
=> Fixture.NoSyncTest(
182-
async, async a =>
183-
{
184-
await base.Uncoalescing_conditional_with_equality_left(a);
185-
186-
AssertSql(
187-
"""
188-
SELECT VALUE c
189-
FROM root c
190-
WHERE (((c["Int"] = 9) ? null : c["Int"]) > 1)
191-
""");
192-
});
193-
194-
public override Task Uncoalescing_conditional_with_equality_right(bool async)
195-
=> Fixture.NoSyncTest(
196-
async, async a =>
197-
{
198-
await base.Uncoalescing_conditional_with_equality_right(a);
199-
200-
AssertSql(
201-
"""
202-
SELECT VALUE c
203-
FROM root c
204-
WHERE (((9 = c["Int"]) ? null : c["Int"]) > 1)
205-
""");
206-
});
207-
208-
public override Task Uncoalescing_conditional_with_unequality_left(bool async)
209-
=> Fixture.NoSyncTest(
210-
async, async a =>
211-
{
212-
await base.Uncoalescing_conditional_with_unequality_left(a);
213-
214-
AssertSql(
215-
"""
216-
SELECT VALUE c
217-
FROM root c
218-
WHERE (((c["Int"] != 9) ? c["Int"] : null) > 1)
219-
""");
220-
});
221-
222-
public override Task Uncoalescing_conditional_with_inequality_right(bool async)
223-
=> Fixture.NoSyncTest(
224-
async, async a =>
225-
{
226-
await base.Uncoalescing_conditional_with_inequality_right(a);
227-
228-
AssertSql(
229-
"""
230-
SELECT VALUE c
231-
FROM root c
232-
WHERE (((9 != c["Int"]) ? c["Int"] : null) > 1)
233-
""");
234-
});
235-
236-
#endregion Uncoalescing conditional / NullIf
237-
238178
[ConditionalFact]
239179
public virtual void Check_all_tests_overridden()
240180
=> TestHelpers.AssertAllMethodsOverridden(GetType());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Query.Translations;
5+
6+
public class OperatorTranslationsCosmosTest : OperatorTranslationsTestBase<BasicTypesQueryCosmosFixture>
7+
{
8+
public OperatorTranslationsCosmosTest(BasicTypesQueryCosmosFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture)
9+
{
10+
Fixture.TestSqlLoggerFactory.Clear();
11+
Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
12+
}
13+
14+
#region Conditional
15+
16+
public override Task Conditional_simplifiable_equality(bool async)
17+
=> Fixture.NoSyncTest(
18+
async, async a =>
19+
{
20+
await base.Conditional_simplifiable_equality(a);
21+
22+
AssertSql(
23+
"""
24+
SELECT VALUE c
25+
FROM root c
26+
WHERE (c["Int"] > 1)
27+
""");
28+
});
29+
30+
public override Task Conditional_simplifiable_inequality(bool async)
31+
=> Fixture.NoSyncTest(
32+
async, async a =>
33+
{
34+
await base.Conditional_simplifiable_inequality(a);
35+
36+
AssertSql(
37+
"""
38+
SELECT VALUE c
39+
FROM root c
40+
WHERE (c["Int"] > 1)
41+
""");
42+
});
43+
44+
public override Task Conditional_uncoalesce_with_equality_left(bool async)
45+
=> Fixture.NoSyncTest(
46+
async, async a =>
47+
{
48+
await base.Conditional_uncoalesce_with_equality_left(a);
49+
50+
AssertSql(
51+
"""
52+
SELECT VALUE c
53+
FROM root c
54+
WHERE (((c["Int"] = 9) ? null : c["Int"]) > 1)
55+
""");
56+
});
57+
58+
public override Task Conditional_uncoalesce_with_equality_right(bool async)
59+
=> Fixture.NoSyncTest(
60+
async, async a =>
61+
{
62+
await base.Conditional_uncoalesce_with_equality_right(a);
63+
64+
AssertSql(
65+
"""
66+
SELECT VALUE c
67+
FROM root c
68+
WHERE (((9 = c["Int"]) ? null : c["Int"]) > 1)
69+
""");
70+
});
71+
72+
public override Task Conditional_uncoalesce_with_unequality_left(bool async)
73+
=> Fixture.NoSyncTest(
74+
async, async a =>
75+
{
76+
await base.Conditional_uncoalesce_with_unequality_left(a);
77+
78+
AssertSql(
79+
"""
80+
SELECT VALUE c
81+
FROM root c
82+
WHERE (((c["Int"] != 9) ? c["Int"] : null) > 1)
83+
""");
84+
});
85+
86+
public override Task Conditional_uncoalesce_with_inequality_right(bool async)
87+
=> Fixture.NoSyncTest(
88+
async, async a =>
89+
{
90+
await base.Conditional_uncoalesce_with_inequality_right(a);
91+
92+
AssertSql(
93+
"""
94+
SELECT VALUE c
95+
FROM root c
96+
WHERE (((9 != c["Int"]) ? c["Int"] : null) > 1)
97+
""");
98+
});
99+
100+
#endregion Conditional
101+
102+
[ConditionalFact]
103+
public virtual void Check_all_tests_overridden()
104+
=> TestHelpers.AssertAllMethodsOverridden(GetType());
105+
106+
private void AssertSql(params string[] expected)
107+
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Query.Translations;
5+
6+
public class OperatorTranslationsInMemoryTest(BasicTypesQueryInMemoryFixture fixture)
7+
: OperatorTranslationsTestBase<BasicTypesQueryInMemoryFixture>(fixture);

test/EFCore.Specification.Tests/Query/Translations/MiscellaneousTranslationsTestBase.cs

-34
Original file line numberDiff line numberDiff line change
@@ -429,38 +429,4 @@ await AssertQuery(
429429
}
430430

431431
#endregion
432-
433-
#region Uncoalescing conditional
434-
435-
// In relational providers, x == a ? null : x is translated to SQL NULLIF
436-
437-
[ConditionalTheory]
438-
[MemberData(nameof(IsAsyncData))]
439-
public virtual Task Uncoalescing_conditional_with_equality_left(bool async)
440-
=> AssertQuery(
441-
async,
442-
cs => cs.Set<BasicTypesEntity>().Where(x => (x.Int == 9 ? null : x.Int) > 1));
443-
444-
[ConditionalTheory]
445-
[MemberData(nameof(IsAsyncData))]
446-
public virtual Task Uncoalescing_conditional_with_equality_right(bool async)
447-
=> AssertQuery(
448-
async,
449-
cs => cs.Set<BasicTypesEntity>().Where(x => (9 == x.Int ? null : x.Int) > 1));
450-
451-
[ConditionalTheory]
452-
[MemberData(nameof(IsAsyncData))]
453-
public virtual Task Uncoalescing_conditional_with_unequality_left(bool async)
454-
=> AssertQuery(
455-
async,
456-
cs => cs.Set<BasicTypesEntity>().Where(x => (x.Int != 9 ? x.Int : null) > 1));
457-
458-
[ConditionalTheory]
459-
[MemberData(nameof(IsAsyncData))]
460-
public virtual Task Uncoalescing_conditional_with_inequality_right(bool async)
461-
=> AssertQuery(
462-
async,
463-
cs => cs.Set<BasicTypesEntity>().Where(x => (9 != x.Int ? x.Int : null) > 1));
464-
465-
#endregion Uncoalescing conditional
466432
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.EntityFrameworkCore.TestModels.BasicTypesModel;
5+
6+
namespace Microsoft.EntityFrameworkCore.Query.Translations;
7+
8+
public abstract class OperatorTranslationsTestBase<TFixture>(TFixture fixture) : QueryTestBase<TFixture>(fixture)
9+
where TFixture : BasicTypesQueryFixtureBase, new()
10+
{
11+
#region Conditional
12+
13+
[ConditionalTheory]
14+
[MemberData(nameof(IsAsyncData))]
15+
public virtual Task Conditional_simplifiable_equality(bool async)
16+
=> AssertQuery(
17+
async,
18+
// ReSharper disable once MergeConditionalExpression
19+
cs => cs.Set<NullableBasicTypesEntity>().Where(x => (x.Int == null ? null : x.Int) > 1));
20+
21+
[ConditionalTheory]
22+
[MemberData(nameof(IsAsyncData))]
23+
public virtual Task Conditional_simplifiable_inequality(bool async)
24+
=> AssertQuery(
25+
async,
26+
// ReSharper disable once MergeConditionalExpression
27+
cs => cs.Set<NullableBasicTypesEntity>().Where(x => (x.Int != null ? x.Int : null) > 1));
28+
29+
// In relational providers, x == a ? null : x ("un-coalescing conditional") is translated to SQL NULLIF
30+
31+
[ConditionalTheory]
32+
[MemberData(nameof(IsAsyncData))]
33+
public virtual Task Conditional_uncoalesce_with_equality_left(bool async)
34+
=> AssertQuery(
35+
async,
36+
cs => cs.Set<BasicTypesEntity>().Where(x => (x.Int == 9 ? null : x.Int) > 1));
37+
38+
[ConditionalTheory]
39+
[MemberData(nameof(IsAsyncData))]
40+
public virtual Task Conditional_uncoalesce_with_equality_right(bool async)
41+
=> AssertQuery(
42+
async,
43+
cs => cs.Set<BasicTypesEntity>().Where(x => (9 == x.Int ? null : x.Int) > 1));
44+
45+
[ConditionalTheory]
46+
[MemberData(nameof(IsAsyncData))]
47+
public virtual Task Conditional_uncoalesce_with_unequality_left(bool async)
48+
=> AssertQuery(
49+
async,
50+
cs => cs.Set<BasicTypesEntity>().Where(x => (x.Int != 9 ? x.Int : null) > 1));
51+
52+
[ConditionalTheory]
53+
[MemberData(nameof(IsAsyncData))]
54+
public virtual Task Conditional_uncoalesce_with_inequality_right(bool async)
55+
=> AssertQuery(
56+
async,
57+
cs => cs.Set<BasicTypesEntity>().Where(x => (9 != x.Int ? x.Int : null) > 1));
58+
59+
#endregion Conditional
60+
}

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

+2-6
Original file line numberDiff line numberDiff line change
@@ -855,9 +855,7 @@ public override async Task Select_null_propagation_works_for_multiple_navigation
855855

856856
AssertSql(
857857
"""
858-
SELECT CASE
859-
WHEN [c].[Name] IS NOT NULL THEN [c].[Name]
860-
END
858+
SELECT [c].[Name]
861859
FROM [Tags] AS [t]
862860
LEFT JOIN [Gears] AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId]
863861
LEFT JOIN [Tags] AS [t0] ON ([g].[Nickname] = [t0].[GearNickName] OR ([g].[Nickname] IS NULL AND [t0].[GearNickName] IS NULL)) AND ([g].[SquadId] = [t0].[GearSquadId] OR ([g].[SquadId] IS NULL AND [t0].[GearSquadId] IS NULL))
@@ -3057,9 +3055,7 @@ public override async Task Select_null_conditional_with_inheritance(bool async)
30573055

30583056
AssertSql(
30593057
"""
3060-
SELECT CASE
3061-
WHEN [f].[CommanderName] IS NOT NULL THEN [f].[CommanderName]
3062-
END
3058+
SELECT [f].[CommanderName]
30633059
FROM [Factions] AS [f]
30643060
""");
30653061
}

0 commit comments

Comments
 (0)