-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
SqlServer: Generate False predicate when skip/take both are 0 #23192
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Linq.Expressions; | ||
using JetBrains.Annotations; | ||
using Microsoft.EntityFrameworkCore.Query; | ||
using Microsoft.EntityFrameworkCore.Query.SqlExpressions; | ||
using Microsoft.EntityFrameworkCore.Utilities; | ||
|
||
#nullable enable | ||
|
||
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal | ||
{ | ||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public class SkipTakeCollapsingExpressionVisitor : ExpressionVisitor | ||
{ | ||
private readonly ISqlExpressionFactory _sqlExpressionFactory; | ||
|
||
private IReadOnlyDictionary<string, object> _parameterValues; | ||
private bool _canCache; | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public SkipTakeCollapsingExpressionVisitor([NotNull] ISqlExpressionFactory sqlExpressionFactory) | ||
{ | ||
Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); | ||
|
||
_sqlExpressionFactory = sqlExpressionFactory; | ||
_parameterValues = null!; | ||
} | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public virtual SelectExpression Process( | ||
[NotNull] SelectExpression selectExpression, | ||
[NotNull] IReadOnlyDictionary<string, object> parametersValues, | ||
out bool canCache) | ||
{ | ||
Check.NotNull(selectExpression, nameof(selectExpression)); | ||
Check.NotNull(parametersValues, nameof(parametersValues)); | ||
|
||
_parameterValues = parametersValues; | ||
_canCache = true; | ||
|
||
var result = (SelectExpression)Visit(selectExpression); | ||
|
||
canCache = _canCache; | ||
|
||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
protected override Expression VisitExtension(Expression extensionExpression) | ||
{ | ||
if (extensionExpression is SelectExpression selectExpression) | ||
{ | ||
if (IsZero(selectExpression.Limit) | ||
&& IsZero(selectExpression.Offset)) | ||
{ | ||
return selectExpression.Update( | ||
selectExpression.Projection, | ||
selectExpression.Tables, | ||
selectExpression.GroupBy.Count > 0 ? selectExpression.Predicate : _sqlExpressionFactory.Constant(false), | ||
selectExpression.GroupBy, | ||
selectExpression.GroupBy.Count > 0 ? _sqlExpressionFactory.Constant(false) : null, | ||
new List<OrderingExpression>(0), | ||
limit: null, | ||
offset: null); | ||
} | ||
|
||
bool IsZero(SqlExpression? sqlExpression) | ||
{ | ||
switch (sqlExpression) | ||
{ | ||
case SqlConstantExpression constant: | ||
return ((int)constant.Value) == 0; | ||
case SqlParameterExpression parameter: | ||
_canCache = false; | ||
return ((int)_parameterValues[parameter.Name]) == 0; | ||
|
||
default: | ||
return false; | ||
} | ||
} | ||
} | ||
|
||
return base.VisitExtension(extensionExpression); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1288,9 +1288,10 @@ public virtual Task Element_selector_with_case_block_repeated_inside_another_cas | |
async, | ||
ss => from order in ss.Set<Order>() | ||
group new | ||
{ | ||
IsAlfki = order.CustomerID == "ALFKI", OrderId = order.OrderID > 1000 ? order.OrderID : -order.OrderID | ||
} by | ||
{ | ||
IsAlfki = order.CustomerID == "ALFKI", | ||
OrderId = order.OrderID > 1000 ? order.OrderID : -order.OrderID | ||
} by | ||
new { order.OrderID } | ||
into g | ||
select new { g.Key.OrderID, Aggregate = g.Sum(s => s.IsAlfki ? s.OrderId : -s.OrderId) }); | ||
|
@@ -2326,6 +2327,35 @@ public virtual Task GroupBy_aggregate_join_another_GroupBy_aggregate(bool async) | |
elementSorter: o => o.Key); | ||
} | ||
|
||
[ConditionalTheory] | ||
[MemberData(nameof(IsAsyncData))] | ||
public virtual Task GroupBy_aggregate_after_skip_0_take_0(bool async) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure, but as this is a SQL Server problem, should these tests live exclusively in the SQL Server test suite? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it works in other providers then what is the issue verifying that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just the fact of adding unnecessary tests to all providers for something that is only a problem for one. I have a lot of PG-only tests which aren't added to the base classes only because EFCore.PG isn't in the EF Core repo, and SQL Server is. It's also a form of documentation for someone reviewing the tests, to note which provider is actually affected by it etc. Just putting it in the right place. But no strong feelings here. |
||
{ | ||
return AssertQuery( | ||
async, | ||
ss => ss.Set<Order>() | ||
.Skip(0) | ||
.Take(0) | ||
.GroupBy(o => o.CustomerID) | ||
.Select(g => new { g.Key, Total = g.Count() }), | ||
elementSorter: o => o.Key); | ||
} | ||
|
||
[ConditionalTheory] | ||
[MemberData(nameof(IsAsyncData))] | ||
public virtual Task GroupBy_skip_0_take_0_aggregate(bool async) | ||
{ | ||
return AssertQuery( | ||
async, | ||
ss => ss.Set<Order>() | ||
.Where(e => e.OrderID > 10500) | ||
.GroupBy(o => o.CustomerID) | ||
.Skip(0) | ||
.Take(0) | ||
.Select(g => new { g.Key, Total = g.Count() }), | ||
elementSorter: o => o.Key); | ||
} | ||
|
||
#endregion | ||
|
||
#region GroupByAggregateChainComposition | ||
|
@@ -2523,7 +2553,8 @@ public virtual Task GroupBy_group_Distinct_Select_Distinct_aggregate(bool async) | |
g => | ||
new | ||
{ | ||
g.Key, Max = g.Distinct().Select(e => e.OrderDate).Distinct().Max(), | ||
g.Key, | ||
Max = g.Distinct().Select(e => e.OrderDate).Distinct().Max(), | ||
}), | ||
elementSorter: e => e.Key); | ||
} | ||
|
@@ -2540,7 +2571,8 @@ public virtual Task GroupBy_group_Where_Select_Distinct_aggregate(bool async) | |
g => | ||
new | ||
{ | ||
g.Key, Max = g.Where(e => e.OrderDate.HasValue).Select(e => e.OrderDate).Distinct().Max(), | ||
g.Key, | ||
Max = g.Where(e => e.OrderDate.HasValue).Select(e => e.OrderDate).Distinct().Max(), | ||
}), | ||
elementSorter: e => e.Key); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking change, including in obsolete method above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not breaking change if you follow the bases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AndriySvyryd - Is this breaking change? Not sure how this missed API review.
For the obsolete one, it added additional NotNulls but the overload is already obsolete, not sure what would be the way to unbreak it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type change is indeed only a provider-facing binary breaking change only, which should be fine. But if this method used to work for null parameters and doesn't any more, that is more problematic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not this method. This method was always marked with NotNull and has Check.NotNull for all the non-nullable arguments.