Skip to content

Commit

Permalink
Use IN instead of EXISTS in more places (#31387)
Browse files Browse the repository at this point in the history
Closes #31386
  • Loading branch information
roji authored Aug 3, 2023
1 parent f9139ad commit a1c3aba
Show file tree
Hide file tree
Showing 16 changed files with 189 additions and 155 deletions.
1 change: 1 addition & 0 deletions All.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_USING_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_WHILE_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_USINGS_STMT/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_RAW_LITERAL_STRING/@EntryValue">DO_NOT_CHANGE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,23 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s
|| !TryGetProjection(source, out var projection))
{
// If the item can't be translated, we can't translate to an IN expression.
// However, attempt to translate as Any since that passes through Where predicate translation, which e.g. does entity equality.

// We do attempt one thing: if this is a contains over an entity type which has a single key property (non-composite key),
// we can project its key property (entity equality/containment) and translate to InExpression over that.
if (item is EntityShaperExpression { EntityType: var entityType }
&& entityType.FindPrimaryKey()?.Properties is [var singleKeyProperty])
{
var keySelectorParam = Expression.Parameter(source.Type);

return TranslateContains(
TranslateSelect(
source,
Expression.Lambda(keySelectorParam.CreateEFPropertyExpression(singleKeyProperty), keySelectorParam)),
item.CreateEFPropertyExpression(singleKeyProperty));
}

// Otherwise, attempt to translate as Any since that passes through Where predicate translation. This will e.g. take care of
// entity , which e.g. does entity equality/containment for entities with composite keys.
var anyLambdaParameter = Expression.Parameter(item.Type, "p");
var anyLambda = Expression.Lambda(
Infrastructure.ExpressionExtensions.CreateEqualsExpression(anyLambdaParameter, item),
Expand Down Expand Up @@ -1321,6 +1337,9 @@ protected override ShapedQueryExpression TranslateUnion(ShapedQueryExpression so
return null;
}

// First, check if the provider has a native translation for the delete represented by the select expression.
// The default relational implementation handles simple, universally-supported cases (i.e. no operators except for predicate).
// Providers may override IsValidSelectExpressionForExecuteDelete to add support for more cases via provider-specific DELETE syntax.
var selectExpression = (SelectExpression)source.QueryExpression;
if (IsValidSelectExpressionForExecuteDelete(selectExpression, entityShaperExpression, out var tableExpression))
{
Expand Down Expand Up @@ -1357,7 +1376,9 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa
}
}

// We need to convert to PK predicate
// The provider doesn't natively support the delete.
// As a fallback, we place the original query in a Contains subquery, which will get translated via the regular entity equality/
// containment mechanism (InExpression for non-composite keys, Any for composite keys)
var pk = entityType.FindPrimaryKey();
if (pk == null)
{
Expand All @@ -1370,14 +1391,7 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa

var clrType = entityType.ClrType;
var entityParameter = Expression.Parameter(clrType);
var innerParameter = Expression.Parameter(clrType);
var predicateBody = Expression.Call(
QueryableMethods.AnyWithPredicate.MakeGenericMethod(clrType),
source,
Expression.Quote(
Expression.Lambda(
Infrastructure.ExpressionExtensions.CreateEqualsExpression(innerParameter, entityParameter),
innerParameter)));
var predicateBody = Expression.Call(QueryableMethods.Contains.MakeGenericMethod(clrType), source, entityParameter);

var newSource = Expression.Call(
QueryableMethods.Where.MakeGenericMethod(clrType),
Expand Down Expand Up @@ -1481,6 +1495,9 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa
return null;
}

// First, check if the provider has a native translation for the update represented by the select expression.
// The default relational implementation handles simple, universally-supported cases (i.e. no operators except for predicate).
// Providers may override IsValidSelectExpressionForExecuteDelete to add support for more cases via provider-specific UPDATE syntax.
var selectExpression = (SelectExpression)source.QueryExpression;
if (IsValidSelectExpressionForExecuteUpdate(selectExpression, entityShaperExpression, out var tableExpression))
{
Expand All @@ -1489,7 +1506,10 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa
propertyValueLambdaExpressions, remappedUnwrappedLeftExpressions);
}

// We need to convert to join with original query using PK
// The provider doesn't natively support the update.
// As a fallback, we place the original query in a subquery and user an INNER JOIN on the primary key columns.
// Unlike with ExecuteDelete, we cannot use a Contains subquery (which would produce the simpler WHERE Id IN (SELECT ...) syntax),
// since we allow projecting out to arbitrary shapes (e.g. anonymous types) before the ExecuteUpdate.
var pk = entityType.FindPrimaryKey();
if (pk == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,16 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
"""
DELETE FROM [a]
FROM [Animals] AS [a]
WHERE [a].[CountryId] = 1 AND EXISTS (
SELECT 1
WHERE [a].[CountryId] = 1 AND [a].[Id] IN (
SELECT (
SELECT TOP(1) [a1].[Id]
FROM [Animals] AS [a1]
WHERE [a1].[CountryId] = 1 AND [a0].[CountryId] = [a1].[CountryId])
FROM [Animals] AS [a0]
WHERE [a0].[CountryId] = 1
GROUP BY [a0].[CountryId]
HAVING COUNT(*) < 3 AND (
SELECT TOP(1) [a1].[Id]
FROM [Animals] AS [a1]
WHERE [a1].[CountryId] = 1 AND [a0].[CountryId] = [a1].[CountryId]) = [a].[Id])
HAVING COUNT(*) < 3
)
""");
}

Expand All @@ -122,16 +123,13 @@ public override async Task Delete_where_hierarchy_subquery(bool async)
DELETE FROM [a]
FROM [Animals] AS [a]
WHERE EXISTS (
SELECT 1
FROM (
SELECT [a0].[Id], [a0].[CountryId], [a0].[Discriminator], [a0].[Name], [a0].[Species], [a0].[EagleId], [a0].[IsFlightless], [a0].[Group], [a0].[FoundOn]
FROM [Animals] AS [a0]
WHERE [a0].[CountryId] = 1 AND [a0].[Name] = N'Great spotted kiwi'
ORDER BY [a0].[Name]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
) AS [t]
WHERE [t].[Id] = [a].[Id])
WHERE [a].[Id] IN (
SELECT [a0].[Id]
FROM [Animals] AS [a0]
WHERE [a0].[CountryId] = 1 AND [a0].[Name] = N'Great spotted kiwi'
ORDER BY [a0].[Name]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
)
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,15 @@ public override async Task Delete_GroupBy_Where_Select_First_3(bool async)
"""
DELETE FROM [a]
FROM [Animals] AS [a]
WHERE EXISTS (
SELECT 1
FROM [Animals] AS [a0]
GROUP BY [a0].[CountryId]
HAVING COUNT(*) < 3 AND (
WHERE [a].[Id] IN (
SELECT (
SELECT TOP(1) [a1].[Id]
FROM [Animals] AS [a1]
WHERE [a0].[CountryId] = [a1].[CountryId]) = [a].[Id])
WHERE [a0].[CountryId] = [a1].[CountryId])
FROM [Animals] AS [a0]
GROUP BY [a0].[CountryId]
HAVING COUNT(*) < 3
)
""");
}

Expand All @@ -124,16 +125,13 @@ public override async Task Delete_where_hierarchy_subquery(bool async)
DELETE FROM [a]
FROM [Animals] AS [a]
WHERE EXISTS (
SELECT 1
FROM (
SELECT [a0].[Id], [a0].[CountryId], [a0].[Discriminator], [a0].[Name], [a0].[Species], [a0].[EagleId], [a0].[IsFlightless], [a0].[Group], [a0].[FoundOn]
FROM [Animals] AS [a0]
WHERE [a0].[Name] = N'Great spotted kiwi'
ORDER BY [a0].[Name]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
) AS [t]
WHERE [t].[Id] = [a].[Id])
WHERE [a].[Id] IN (
SELECT [a0].[Id]
FROM [Animals] AS [a0]
WHERE [a0].[Name] = N'Great spotted kiwi'
ORDER BY [a0].[Name]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
)
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,15 @@ public override async Task Delete_Where_predicate_with_GroupBy_aggregate_2(bool
DELETE FROM [o]
FROM [Order Details] AS [o]
INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID]
WHERE EXISTS (
SELECT 1
FROM [Orders] AS [o1]
GROUP BY [o1].[CustomerID]
HAVING COUNT(*) > 9 AND (
WHERE [o0].[OrderID] IN (
SELECT (
SELECT TOP(1) [o2].[OrderID]
FROM [Orders] AS [o2]
WHERE [o1].[CustomerID] = [o2].[CustomerID] OR ([o1].[CustomerID] IS NULL AND [o2].[CustomerID] IS NULL)) = [o0].[OrderID])
WHERE [o1].[CustomerID] = [o2].[CustomerID] OR ([o1].[CustomerID] IS NULL AND [o2].[CustomerID] IS NULL))
FROM [Orders] AS [o1]
GROUP BY [o1].[CustomerID]
HAVING COUNT(*) > 9
)
""");
}

Expand Down Expand Up @@ -948,15 +949,16 @@ public override async Task Update_Where_GroupBy_First_set_constant_3(bool async)
UPDATE [c]
SET [c].[ContactName] = N'Updated'
FROM [Customers] AS [c]
WHERE EXISTS (
SELECT 1
FROM [Orders] AS [o]
GROUP BY [o].[CustomerID]
HAVING COUNT(*) > 11 AND (
WHERE [c].[CustomerID] IN (
SELECT (
SELECT TOP(1) [c0].[CustomerID]
FROM [Orders] AS [o0]
LEFT JOIN [Customers] AS [c0] ON [o0].[CustomerID] = [c0].[CustomerID]
WHERE [o].[CustomerID] = [o0].[CustomerID] OR ([o].[CustomerID] IS NULL AND [o0].[CustomerID] IS NULL)) = [c].[CustomerID])
WHERE [o].[CustomerID] = [o0].[CustomerID] OR ([o].[CustomerID] IS NULL AND [o0].[CustomerID] IS NULL))
FROM [Orders] AS [o]
GROUP BY [o].[CustomerID]
HAVING COUNT(*) > 11
)
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7125,18 +7125,20 @@ public override async Task Query_reusing_parameter_with_inner_query_doesnt_decla
SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank]
FROM [Gears] AS [g]
INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id]
WHERE EXISTS (
SELECT 1
WHERE [s].[Id] IN (
SELECT [s0].[Id]
FROM [Squads] AS [s0]
WHERE [s0].[Id] = @__squadId_0 AND [s0].[Id] = [s].[Id])
WHERE [s0].[Id] = @__squadId_0
)
UNION ALL
SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank]
FROM [Gears] AS [g0]
INNER JOIN [Squads] AS [s1] ON [g0].[SquadId] = [s1].[Id]
WHERE EXISTS (
SELECT 1
WHERE [s1].[Id] IN (
SELECT [s2].[Id]
FROM [Squads] AS [s2]
WHERE [s2].[Id] = @__squadId_0 AND [s2].[Id] = [s1].[Id])
WHERE [s2].[Id] = @__squadId_0
)
) AS [t]
ORDER BY [t].[FullName]
""");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1940,10 +1940,11 @@ FROM [Orders] AS [o]
WHERE EXISTS (
SELECT 1
FROM [Customers] AS [c]
WHERE EXISTS (
SELECT 1
WHERE [o].[OrderID] IN (
SELECT [o0].[OrderID]
FROM [Orders] AS [o0]
WHERE [c].[CustomerID] = [o0].[CustomerID] AND [o0].[OrderID] = [o].[OrderID]))
WHERE [c].[CustomerID] = [o0].[CustomerID]
))
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,16 @@ public override async Task Unwrap_convert_node_over_projection_when_translating_
@__currentUserId_0='1'
SELECT CASE
WHEN EXISTS (
SELECT 1
WHEN [u].[Id] IN (
SELECT [u0].[Id]
FROM [Memberships] AS [m]
INNER JOIN [Users] AS [u0] ON [m].[UserId] = [u0].[Id]
WHERE [m].[GroupId] IN (
SELECT [m0].[GroupId]
FROM [Memberships] AS [m0]
WHERE [m0].[UserId] = @__currentUserId_0
) AND [u0].[Id] = [u].[Id]) THEN CAST(1 AS bit)
)
) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS [HasAccess]
FROM [Users] AS [u]
Expand All @@ -155,16 +156,18 @@ public override async Task Unwrap_convert_node_over_projection_when_translating_
@__currentUserId_0='1'
SELECT CASE
WHEN EXISTS (
SELECT 1
WHEN [u].[Id] IN (
SELECT [u0].[Id]
FROM [Memberships] AS [m]
INNER JOIN [Groups] AS [g] ON [m].[GroupId] = [g].[Id]
INNER JOIN [Users] AS [u0] ON [m].[UserId] = [u0].[Id]
WHERE EXISTS (
SELECT 1
WHERE [g].[Id] IN (
SELECT [g0].[Id]
FROM [Memberships] AS [m0]
INNER JOIN [Groups] AS [g0] ON [m0].[GroupId] = [g0].[Id]
WHERE [m0].[UserId] = @__currentUserId_0 AND [g0].[Id] = [g].[Id]) AND [u0].[Id] = [u].[Id]) THEN CAST(1 AS bit)
WHERE [m0].[UserId] = @__currentUserId_0
)
) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS [HasAccess]
FROM [Users] AS [u]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9476,10 +9476,11 @@ UNION ALL
FROM [Officers] AS [o]
) AS [t]
INNER JOIN [Squads] AS [s] ON [t].[SquadId] = [s].[Id]
WHERE EXISTS (
SELECT 1
WHERE [s].[Id] IN (
SELECT [s0].[Id]
FROM [Squads] AS [s0]
WHERE [s0].[Id] = @__squadId_0 AND [s0].[Id] = [s].[Id])
WHERE [s0].[Id] = @__squadId_0
)
UNION ALL
SELECT [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank], [t1].[Discriminator]
FROM (
Expand All @@ -9490,10 +9491,11 @@ UNION ALL
FROM [Officers] AS [o0]
) AS [t1]
INNER JOIN [Squads] AS [s1] ON [t1].[SquadId] = [s1].[Id]
WHERE EXISTS (
SELECT 1
WHERE [s1].[Id] IN (
SELECT [s2].[Id]
FROM [Squads] AS [s2]
WHERE [s2].[Id] = @__squadId_0 AND [s2].[Id] = [s1].[Id])
WHERE [s2].[Id] = @__squadId_0
)
) AS [t0]
ORDER BY [t0].[FullName]
""");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8160,21 +8160,23 @@ END AS [Discriminator]
FROM [Gears] AS [g]
LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId]
INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id]
WHERE EXISTS (
SELECT 1
WHERE [s].[Id] IN (
SELECT [s0].[Id]
FROM [Squads] AS [s0]
WHERE [s0].[Id] = @__squadId_0 AND [s0].[Id] = [s].[Id])
WHERE [s0].[Id] = @__squadId_0
)
UNION ALL
SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], CASE
WHEN [o0].[Nickname] IS NOT NULL THEN N'Officer'
END AS [Discriminator]
FROM [Gears] AS [g0]
LEFT JOIN [Officers] AS [o0] ON [g0].[Nickname] = [o0].[Nickname] AND [g0].[SquadId] = [o0].[SquadId]
INNER JOIN [Squads] AS [s1] ON [g0].[SquadId] = [s1].[Id]
WHERE EXISTS (
SELECT 1
WHERE [s1].[Id] IN (
SELECT [s2].[Id]
FROM [Squads] AS [s2]
WHERE [s2].[Id] = @__squadId_0 AND [s2].[Id] = [s1].[Id])
WHERE [s2].[Id] = @__squadId_0
)
) AS [t]
ORDER BY [t].[FullName]
""");
Expand Down
Loading

0 comments on commit a1c3aba

Please # to comment.