Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly support LEAST/GREATEST over nullable value types #32458

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -931,15 +931,15 @@ private SqlExpression CreateJoinPredicate(Expression outerKey, Expression innerK
/// <inheritdoc />
protected override ShapedQueryExpression? TranslateMax(ShapedQueryExpression source, LambdaExpression? selector, Type resultType)
=> TryExtractBareInlineCollectionValues(source, out var values)
&& _sqlExpressionFactory.TryCreateGreatest(values, resultType, out var greatestExpression)
&& _sqlExpressionFactory.TryCreateGreatest(values, resultType.UnwrapNullableType(), out var greatestExpression)
? source.Update(_sqlExpressionFactory.Select(greatestExpression), source.ShaperExpression)
: TranslateAggregateWithSelector(
source, selector, t => QueryableMethods.MaxWithoutSelector.MakeGenericMethod(t), throwWhenEmpty: true, resultType);

/// <inheritdoc />
protected override ShapedQueryExpression? TranslateMin(ShapedQueryExpression source, LambdaExpression? selector, Type resultType)
=> TryExtractBareInlineCollectionValues(source, out var values)
&& _sqlExpressionFactory.TryCreateLeast(values, resultType, out var leastExpression)
&& _sqlExpressionFactory.TryCreateLeast(values, resultType.UnwrapNullableType(), out var leastExpression)
? source.Update(_sqlExpressionFactory.Select(leastExpression), source.ShaperExpression)
: TranslateAggregateWithSelector(
source, selector, t => QueryableMethods.MinWithoutSelector.MakeGenericMethod(t), throwWhenEmpty: true, resultType);
Expand Down
Expand Up @@ -966,13 +966,15 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
var elementClrType = newArray.Type.GetElementType()!;

if (genericMethodDefinition == LeastMethodInfo
&& _sqlExpressionFactory.TryCreateLeast(translatedValues, elementClrType, out var leastExpression))
&& _sqlExpressionFactory.TryCreateLeast(
translatedValues, elementClrType.UnwrapNullableType(), out var leastExpression))
{
return leastExpression;
}

if (genericMethodDefinition == GreatestMethodInfo
&& _sqlExpressionFactory.TryCreateGreatest(translatedValues, elementClrType, out var greatestExpression))
&& _sqlExpressionFactory.TryCreateGreatest(
translatedValues, elementClrType.UnwrapNullableType(), out var greatestExpression))
{
return greatestExpression;
}
Expand Down
Expand Up @@ -66,6 +66,22 @@ public virtual Task Greatest(bool async)
ss => ss.Set<OrderDetail>().Where(od => EF.Functions.Greatest(od.OrderID, 10251) == 10251),
ss => ss.Set<OrderDetail>().Where(od => Math.Max(od.OrderID, 10251) == 10251));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Least_with_nullable_value_type(bool async)
=> AssertQuery(
async,
ss => ss.Set<OrderDetail>().Where(od => EF.Functions.Least(od.OrderID, (int?)10251) == 10251),
ss => ss.Set<OrderDetail>().Where(od => Math.Min(od.OrderID, 10251) == 10251));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Greatest_with_nullable_value_type(bool async)
=> AssertQuery(
async,
ss => ss.Set<OrderDetail>().Where(od => EF.Functions.Greatest(od.OrderID, (int?)10251) == 10251),
ss => ss.Set<OrderDetail>().Where(od => Math.Max(od.OrderID, 10251) == 10251));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Least_with_parameter_array_is_not_supported(bool async)
Expand Down
Expand Up @@ -190,6 +190,28 @@ public virtual async Task Inline_collection_Max_with_three_values(bool async)
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => new[] { 30, c.Int, i }.Max() == 35));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Inline_collection_of_nullable_value_type_Min(bool async)
{
int? i = 25;

await AssertQuery(
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => new[] { 30, c.Int, i }.Min() == 25));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Inline_collection_of_nullable_value_type_Max(bool async)
{
int? i = 35;

await AssertQuery(
async,
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => new[] { 30, c.Int, i }.Max() == 35));
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Parameter_collection_Count(bool async)
Expand Down
Expand Up @@ -145,6 +145,32 @@ public override async Task Greatest(bool async)
""");
}

[SqlServerCondition(SqlServerCondition.SupportsFunctions2022)]
public override async Task Least_with_nullable_value_type(bool async)
{
await base.Least_with_nullable_value_type(async);

AssertSql(
"""
SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice]
FROM [Order Details] AS [o]
WHERE LEAST([o].[OrderID], 10251) = 10251
""");
}

[SqlServerCondition(SqlServerCondition.SupportsFunctions2022)]
public override async Task Greatest_with_nullable_value_type(bool async)
{
await base.Greatest_with_nullable_value_type(async);

AssertSql(
"""
SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice]
FROM [Order Details] AS [o]
WHERE GREATEST([o].[OrderID], 10251) = 10251
""");
}

public override async Task Least_with_parameter_array_is_not_supported(bool async)
{
await base.Least_with_parameter_array_is_not_supported(async);
Expand Down
Expand Up @@ -280,6 +280,36 @@ public override async Task Inline_collection_Max_with_three_values(bool async)
"""
@__i_0='35'

SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE GREATEST(30, [p].[Int], @__i_0) = 35
""");
}

[SqlServerCondition(SqlServerCondition.SupportsFunctions2022)]
public override async Task Inline_collection_of_nullable_value_type_Min(bool async)
{
await base.Inline_collection_of_nullable_value_type_Min(async);

AssertSql(
"""
@__i_0='25' (Nullable = true)

SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE LEAST(30, [p].[Int], @__i_0) = 25
""");
}

[SqlServerCondition(SqlServerCondition.SupportsFunctions2022)]
public override async Task Inline_collection_of_nullable_value_type_Max(bool async)
{
await base.Inline_collection_of_nullable_value_type_Max(async);

AssertSql(
"""
@__i_0='35' (Nullable = true)

SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE GREATEST(30, [p].[Int], @__i_0) = 35
Expand Down