Skip to content

Commit

Permalink
Query: Convert Apply to Join for collection projection (#21295)
Browse files Browse the repository at this point in the history
This change makes Collection projection path for single query to use AddJoin method which does conversion.
Now filtered include with Skip/Take works on Sqlite for single query scenario also.

Resolves #20608
  • Loading branch information
smitpatel committed Jun 18, 2020
1 parent 412f372 commit 0fa2d3c
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 401 deletions.
154 changes: 51 additions & 103 deletions src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1185,7 +1185,8 @@ public Expression ApplyCollectionJoin(
var outerOrdering = Orderings[i];
if (outerOrdering.Expression.Equals(_identifier[identifierIndex].Column))
{
innerSelectExpression.AppendOrdering(new OrderingExpression(innerSelectExpression._identifier[identifierIndex].Column, ascending: true));
innerSelectExpression.AppendOrdering(new OrderingExpression(
innerSelectExpression._identifier[identifierIndex].Column, ascending: true));
identifierIndex++;
}
else
Expand All @@ -1211,77 +1212,85 @@ public Expression ApplyCollectionJoin(
}
else
{

var (parentIdentifier, parentIdentifierValueComparers) = GetIdentifierAccessor(_identifier);
var (outerIdentifier, outerIdentifierValueComparers) = GetIdentifierAccessor(_identifier.Concat(_childIdentifiers));
var parentIdentifierList = _identifier.Except(_childIdentifiers).ToList();
var (parentIdentifier, parentIdentifierValueComparers) = GetIdentifierAccessor(parentIdentifierList);
var (outerIdentifier, outerIdentifierValueComparers) = GetIdentifierAccessor(_identifier);
var innerClientEval = innerSelectExpression.Projection.Count > 0;
innerSelectExpression.ApplyProjection();

var (selfIdentifier, selfIdentifierValueComparers) = innerSelectExpression.GetIdentifierAccessor(innerSelectExpression._identifier);

if (collectionIndex == 0)
{
foreach (var identifier in _identifier)
foreach (var identifier in parentIdentifierList)
{
AppendOrdering(new OrderingExpression(identifier.Column, ascending: true));
}
}

var joinPredicate = TryExtractJoinKey(innerSelectExpression);
var containsOuterReference = new SelectExpressionCorrelationFindingExpressionVisitor(this)
.ContainsOuterReference(innerSelectExpression);
if (containsOuterReference && joinPredicate != null)
{
innerSelectExpression.ApplyPredicate(joinPredicate);
joinPredicate = null;
}

if (innerSelectExpression.Offset != null
|| innerSelectExpression.Limit != null
|| innerSelectExpression.IsDistinct
|| innerSelectExpression.Predicate != null
|| innerSelectExpression.Tables.Count > 1
|| innerSelectExpression.GroupBy.Count > 0)
{
var sqlRemappingVisitor = new SqlRemappingVisitor(
innerSelectExpression.PushdownIntoSubquery(),
(SelectExpression)innerSelectExpression.Tables[0]);
joinPredicate = sqlRemappingVisitor.Remap(joinPredicate);
}

var joinExpression = joinPredicate == null
? (TableExpressionBase)new OuterApplyExpression(innerSelectExpression.Tables.Single())
: new LeftJoinExpression(innerSelectExpression.Tables.Single(), joinPredicate);
_tables.Add(joinExpression);
AddJoin(JoinType.OuterApply, ref innerSelectExpression);

foreach (var ordering in innerSelectExpression.Orderings)
{
AppendOrdering(ordering.Update(MakeNullable(ordering.Expression)));
}

var remapper = new ProjectionBindingExpressionRemappingExpressionVisitor(this);
var innerProjectionCount = innerSelectExpression.Projection.Count;
var indexMap = new int[innerProjectionCount];
for (var i = 0; i < innerProjectionCount; i++)
{
indexMap[i] = AddToProjection(MakeNullable(innerSelectExpression.Projection[i].Expression));
}

foreach (var identifier in innerSelectExpression._identifier.Concat(innerSelectExpression._childIdentifiers))
if (innerClientEval)
{
innerShaper = remapper.RemapIndex(innerShaper, indexMap, pendingCollectionOffset: 0);
}
else
{
var mapping = new Dictionary<ProjectionMember, object>();
foreach (var projection in innerSelectExpression._projectionMapping)
{
var value = ((ConstantExpression)projection.Value).Value;
object mappedValue = null;
if (value is int index)
{
mappedValue = indexMap[index];
}
else if (value is IDictionary<IProperty, int> entityIndexMap)
{
var newEntityIndexMap = new Dictionary<IProperty, int>();
foreach (var item in entityIndexMap)
{
newEntityIndexMap[item.Key] = indexMap[item.Value];
}
mappedValue = newEntityIndexMap;
}
mapping[projection.Key] = mappedValue;
}

innerShaper = remapper.RemapProjectionMember(innerShaper, mapping, pendingCollectionOffset: 0);
}

innerShaper = new EntityShaperNullableMarkingExpressionVisitor().Visit(innerShaper);

var (selfIdentifier, selfIdentifierValueComparers) = GetIdentifierAccessor(
innerSelectExpression._identifier
.Except(innerSelectExpression._childIdentifiers)
.Select(e => (e.Column.MakeNullable(), e.Comparer)));

foreach (var identifier in innerSelectExpression._identifier)
{
var updatedColumn = identifier.Column.MakeNullable();
_childIdentifiers.Add((updatedColumn, identifier.Comparer));
AppendOrdering(new OrderingExpression(updatedColumn, ascending: true));
}

// Inner should not have pendingCollection since we apply them first.
// Shaper should not have CollectionShaperExpression as any collection would get converted to RelationalCollectionShaperExpression.
var shaperRemapper = new ShaperRemappingExpressionVisitor(this, innerSelectExpression, indexMap, pendingCollectionOffset: 0);
innerShaper = shaperRemapper.Visit(innerShaper);
selfIdentifier = shaperRemapper.Visit(selfIdentifier);

return new RelationalCollectionShaperExpression(
var result = new RelationalCollectionShaperExpression(
collectionId, parentIdentifier, outerIdentifier, selfIdentifier,
parentIdentifierValueComparers, outerIdentifierValueComparers, selfIdentifierValueComparers,
innerShaper, navigation, elementType);

return result;
}
}

Expand Down Expand Up @@ -1764,68 +1773,6 @@ private ProjectionBindingExpression CreateNewBinding(object binding, Type type)
};
}

private sealed class ShaperRemappingExpressionVisitor : ExpressionVisitor
{
private readonly SelectExpression _queryExpression;
private readonly SelectExpression _innerSelectExpression;
private readonly int[] _indexMap;
private readonly int _pendingCollectionOffset;

public ShaperRemappingExpressionVisitor(
SelectExpression queryExpression, SelectExpression innerSelectExpression, int[] indexMap, int pendingCollectionOffset)
{
_queryExpression = queryExpression;
_innerSelectExpression = innerSelectExpression;
_pendingCollectionOffset = pendingCollectionOffset;
_indexMap = indexMap;
}

protected override Expression VisitExtension(Expression extensionExpression)
{
Check.NotNull(extensionExpression, nameof(extensionExpression));

switch (extensionExpression)
{
case ProjectionBindingExpression projectionBindingExpression:
return new ProjectionBindingExpression(
_queryExpression, _indexMap[(int)GetProjectionIndex(projectionBindingExpression)], projectionBindingExpression.Type);

case EntityShaperExpression entityShaperExpression:
var oldIndexMap = (IDictionary<IProperty, int>)GetProjectionIndex(
(ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression);
var indexMap = new Dictionary<IProperty, int>();
foreach (var keyValuePair in oldIndexMap)
{
indexMap[keyValuePair.Key] = _indexMap[keyValuePair.Value];
}

return new RelationalEntityShaperExpression(
entityShaperExpression.EntityType,
new ProjectionBindingExpression(_queryExpression, indexMap),
nullable: true);

case CollectionShaperExpression collectionShaperExpression:
var collectionIndex = (int)GetProjectionIndex((ProjectionBindingExpression)collectionShaperExpression.Projection);

return collectionShaperExpression.Update(
new ProjectionBindingExpression(_queryExpression, collectionIndex + _pendingCollectionOffset, typeof(object)),
collectionShaperExpression.InnerShaper);

default:
return base.VisitExtension(extensionExpression);
}
}

private object GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)
{
return projectionBindingExpression.ProjectionMember != null
? ((ConstantExpression)_innerSelectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember)).Value
: (projectionBindingExpression.Index != null
? (object)projectionBindingExpression.Index
: projectionBindingExpression.IndexMap);
}
}

private void AddJoin(
JoinType joinType,
ref SelectExpression innerSelectExpression,
Expand All @@ -1848,6 +1795,7 @@ private void AddJoin(
if (containsOuterReference)
{
innerSelectExpression.ApplyPredicate(joinPredicate);
joinPredicate = null;
if (limit != null)
{
innerSelectExpression.ApplyLimit(limit);
Expand Down
Loading

0 comments on commit 0fa2d3c

Please sign in to comment.