Skip to content

Commit

Permalink
Use IN instead of EXISTS in more places
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Aug 1, 2023
1 parent 82e96d9 commit 0cbba5c
Show file tree
Hide file tree
Showing 15 changed files with 348 additions and 356 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,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 @@ -1304,6 +1320,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 @@ -1340,7 +1359,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 @@ -1353,14 +1374,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 @@ -1464,6 +1478,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 @@ -1472,7 +1489,9 @@ 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 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 @@ -1483,51 +1502,20 @@ static bool AreOtherNonOwnedEntityTypesInTheTable(IEntityType rootType, ITableBa
return null;
}

var outer = (ShapedQueryExpression)Visit(new EntityQueryRootExpression(entityType));
var inner = source;
var outerParameter = Expression.Parameter(entityType.ClrType);
var outerKeySelector = Expression.Lambda(outerParameter.CreateKeyValuesExpression(pk.Properties), outerParameter);
var firstPropertyLambdaExpression = propertyValueLambdaExpressions[0].Item1;
var entitySource = GetEntitySource(RelationalDependencies.Model, firstPropertyLambdaExpression.Body);
var innerKeySelector = Expression.Lambda(
entitySource.CreateKeyValuesExpression(pk.Properties), firstPropertyLambdaExpression.Parameters);

var joinPredicate = CreateJoinPredicate(outer, outerKeySelector, inner, innerKeySelector);

Check.DebugAssert(joinPredicate != null, "Join predicate shouldn't be null");

var outerSelectExpression = (SelectExpression)outer.QueryExpression;
var outerShaperExpression = outerSelectExpression.AddInnerJoin(inner, joinPredicate, outer.ShaperExpression);
outer = outer.UpdateShaperExpression(outerShaperExpression);
var transparentIdentifierType = outer.ShaperExpression.Type;
var transparentIdentifierParameter = Expression.Parameter(transparentIdentifierType);
var propertyReplacement = AccessField(transparentIdentifierType, transparentIdentifierParameter, "Outer");
var valueReplacement = AccessField(transparentIdentifierType, transparentIdentifierParameter, "Inner");
for (var i = 0; i < propertyValueLambdaExpressions.Count; i++)
{
var (propertyExpression, valueExpression) = propertyValueLambdaExpressions[i];
propertyExpression = Expression.Lambda(
ReplacingExpressionVisitor.Replace(
ReplacingExpressionVisitor.Replace(
firstPropertyLambdaExpression.Parameters[0],
propertyExpression.Parameters[0],
entitySource),
propertyReplacement, propertyExpression.Body),
transparentIdentifierParameter);
valueExpression = valueExpression is LambdaExpression lambdaExpression
? Expression.Lambda(
ReplacingExpressionVisitor.Replace(lambdaExpression.Parameters[0], valueReplacement, lambdaExpression.Body),
transparentIdentifierParameter)
: valueExpression;
var clrType = entityType.ClrType;
var entityParameter = Expression.Parameter(clrType);
var predicateBody = Expression.Call(QueryableMethods.Contains.MakeGenericMethod(clrType), source, entityParameter);

propertyValueLambdaExpressions[i] = (propertyExpression, valueExpression);
}
var newSource = (ShapedQueryExpression)Visit(
Expression.Call(
QueryableMethods.Where.MakeGenericMethod(clrType),
new EntityQueryRootExpression(entityType),
Expression.Quote(Expression.Lambda(predicateBody, entityParameter))));

tableExpression = (TableExpression)outerSelectExpression.Tables[0];
selectExpression = (SelectExpression)newSource.QueryExpression;
tableExpression = (TableExpression)selectExpression.Tables[0];

return TranslateSetPropertyExpressions(this, outer, outerSelectExpression, tableExpression, propertyValueLambdaExpressions, null);
return TranslateSetPropertyExpressions(this, newSource, selectExpression, tableExpression, propertyValueLambdaExpressions, null);

static NonQueryExpression? TranslateSetPropertyExpressions(
RelationalQueryableMethodTranslatingExpressionVisitor visitor,
Expand Down Expand Up @@ -1671,25 +1659,6 @@ static bool TryProcessPropertyAccess(
entityShaperExpression = null;
return false;
}
static Expression GetEntitySource(IModel model, Expression propertyAccessExpression)
{
propertyAccessExpression = propertyAccessExpression.UnwrapTypeConversion(out _);
if (propertyAccessExpression is MethodCallExpression mce)
{
if (mce.TryGetEFPropertyArguments(out var source, out _))
{
return source;
}
if (mce.TryGetIndexerArguments(model, out var source2, out _))
{
return source2;
}
}
return ((MemberExpression)propertyAccessExpression).Expression!;
}
}

/// <summary>
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
Loading

0 comments on commit 0cbba5c

Please sign in to comment.