Skip to content

Commit

Permalink
Query: DRY joins inside SelectExpression
Browse files Browse the repository at this point in the history
pre-work for #17112
  • Loading branch information
smitpatel committed Aug 15, 2019
1 parent 2271170 commit d0a4294
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 306 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,6 @@ internal static IQueryable<TEntity> FromSqlOnQueryable<TEntity>(
[NotParameterized] string sql,
[NotNull] params object[] parameters)
where TEntity : class
=> throw new NotSupportedException();
=> throw new InvalidOperationException();
}
}
20 changes: 10 additions & 10 deletions src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ protected virtual void GenerateSetOperation(SelectExpression setOperationExpress
Debug.Assert(setOperationExpression.Tables.Count == 2,
$"{nameof(SelectExpression)} with {setOperationExpression.Tables.Count} tables, must be 2");

static string GenerateSetOperationType(SetOperationType setOperationType)
=> setOperationType switch
{
SetOperationType.Union => "UNION",
SetOperationType.UnionAll => "UNION ALL",
SetOperationType.Intersect => "INTERSECT",
SetOperationType.Except => "EXCEPT",
_ => throw new InvalidOperationException($"Invalid {nameof(SetOperationType)}: {setOperationType}")
};

GenerateSetOperationOperand(setOperationExpression, (SelectExpression)setOperationExpression.Tables[0]);

_relationalCommandBuilder
Expand All @@ -205,16 +215,6 @@ protected virtual void GenerateSetOperation(SelectExpression setOperationExpress
GenerateLimitOffset(setOperationExpression);
}

private static string GenerateSetOperationType(SetOperationType setOperationType)
=> setOperationType switch
{
SetOperationType.Union => "UNION",
SetOperationType.UnionAll => "UNION ALL",
SetOperationType.Intersect => "INTERSECT",
SetOperationType.Except => "EXCEPT",
_ => throw new NotSupportedException($"Invalid {nameof(SetOperationType)}: {setOperationType}")
};

protected virtual void GenerateSetOperationOperand(
SelectExpression setOperationExpression,
SelectExpression operandExpression)
Expand Down
238 changes: 61 additions & 177 deletions src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -959,51 +959,35 @@ private bool ContainsTableReference(TableExpressionBase table)
? ((SelectExpression)Tables[0]).ContainsTableReference(table)
: Tables.Any(te => ReferenceEquals(te is JoinExpressionBase jeb ? jeb.Table : te, table));

public void AddInnerJoin(SelectExpression innerSelectExpression, SqlExpression joinPredicate, Type transparentIdentifierType)
private enum JoinType
{
if (Limit != null || Offset != null || IsDistinct || IsSetOperation || GroupBy.Count > 1)
{
joinPredicate = new SqlRemappingVisitor(PushdownIntoSubquery(), (SelectExpression)Tables[0])
.Remap(joinPredicate);
}

// TODO: write a test which has distinct on outer so that we can verify pushdown
if (innerSelectExpression.Orderings.Any()
|| innerSelectExpression.Limit != null
|| innerSelectExpression.Offset != null
|| innerSelectExpression.IsDistinct
// TODO: Predicate can be lifted in inner join
|| innerSelectExpression.Predicate != null
|| innerSelectExpression.Tables.Count > 1
|| innerSelectExpression.GroupBy.Count > 1)
{
joinPredicate = new SqlRemappingVisitor(
innerSelectExpression.PushdownIntoSubquery(), (SelectExpression)innerSelectExpression.Tables[0])
.Remap(joinPredicate);
}

_identifier.AddRange(innerSelectExpression._identifier);
var joinTable = new InnerJoinExpression(innerSelectExpression.Tables.Single(), joinPredicate);
_tables.Add(joinTable);

var outerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Outer");
var projectionMapping = new Dictionary<ProjectionMember, Expression>();
foreach (var projection in _projectionMapping)
{
projectionMapping[projection.Key.Prepend(outerMemberInfo)] = projection.Value;
}
InnerJoin,
LeftJoin,
CrossJoin,
InnerJoinLateral,
LeftJoinLateral
}

var innerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Inner");
foreach (var projection in innerSelectExpression._projectionMapping)
private void AddJoin(
JoinType joinType,
SelectExpression innerSelectExpression,
Type transparentIdentifierType,
SqlExpression joinPredicate = null)
{
// Try to convert lateral join to normal join
if (joinType == JoinType.InnerJoinLateral || joinType == JoinType.LeftJoinLateral)
{
projectionMapping[projection.Key.Prepend(innerMemberInfo)] = projection.Value;
joinPredicate = TryExtractJoinKey(innerSelectExpression);
// TODO: Make sure that innerSelectExpression does not contain any reference from this SelectExpression
if (joinPredicate != null)
{
AddJoin(joinType == JoinType.InnerJoinLateral ? JoinType.InnerJoin : JoinType.LeftJoin,
innerSelectExpression, transparentIdentifierType, joinPredicate);
return;
}
}

_projectionMapping = projectionMapping;
}

public void AddLeftJoin(SelectExpression innerSelectExpression, SqlExpression joinPredicate, Type transparentIdentifierType)
{
// Verify what are the cases of pushdown for inner & outer both sides
if (Limit != null || Offset != null || IsDistinct || IsSetOperation || GroupBy.Count > 1)
{
joinPredicate = new SqlRemappingVisitor(PushdownIntoSubquery(), (SelectExpression)Tables[0])
Expand All @@ -1023,7 +1007,21 @@ public void AddLeftJoin(SelectExpression innerSelectExpression, SqlExpression jo
.Remap(joinPredicate);
}

var joinTable = new LeftJoinExpression(innerSelectExpression.Tables.Single(), joinPredicate);
if (joinType != JoinType.LeftJoin)
{
_identifier.AddRange(innerSelectExpression._identifier);
}
var innerTable = innerSelectExpression.Tables.Single();
var joinTable = (TableExpressionBase)(joinType switch
{
JoinType.InnerJoin => new InnerJoinExpression(innerTable, joinPredicate),
JoinType.LeftJoin => new LeftJoinExpression(innerTable, joinPredicate),
JoinType.CrossJoin => new CrossJoinExpression(innerTable),
JoinType.InnerJoinLateral => new InnerJoinLateralExpression(innerTable),
JoinType.LeftJoinLateral => new LeftJoinLateralExpression(innerTable),
_ => throw new InvalidOperationException($"Invalid {nameof(joinType)}: {joinType}")
});

_tables.Add(joinTable);

if (transparentIdentifierType != null)
Expand All @@ -1036,165 +1034,51 @@ public void AddLeftJoin(SelectExpression innerSelectExpression, SqlExpression jo
}

var innerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Inner");
var innerNullable = joinType == JoinType.LeftJoin || joinType == JoinType.LeftJoinLateral;
foreach (var projection in innerSelectExpression._projectionMapping)
{
var projectionToAdd = projection.Value;
if (projectionToAdd is EntityProjectionExpression entityProjection)
{
projectionToAdd = entityProjection.MakeNullable();
}
else if (projectionToAdd is ColumnExpression column)
if (innerNullable)
{
projectionToAdd = column.MakeNullable();
if (projectionToAdd is EntityProjectionExpression entityProjection)
{
projectionToAdd = entityProjection.MakeNullable();
}
else if (projectionToAdd is ColumnExpression column)
{
projectionToAdd = column.MakeNullable();
}
}

projectionMapping[projection.Key.Prepend(innerMemberInfo)] = projectionToAdd;
}

_projectionMapping = projectionMapping;
}
}

public void AddCrossJoin(SelectExpression innerSelectExpression, Type transparentIdentifierType)
public void AddInnerJoin(SelectExpression innerSelectExpression, SqlExpression joinPredicate, Type transparentIdentifierType)
{
if (Limit != null || Offset != null || IsDistinct || Predicate != null || IsSetOperation || GroupBy.Count > 1)
{
PushdownIntoSubquery();
}

if (innerSelectExpression.Orderings.Any()
|| innerSelectExpression.Limit != null
|| innerSelectExpression.Offset != null
|| innerSelectExpression.IsDistinct
|| innerSelectExpression.Predicate != null
|| innerSelectExpression.Tables.Count > 1
|| innerSelectExpression.GroupBy.Count > 1)
{
innerSelectExpression.PushdownIntoSubquery();
}

_identifier.AddRange(innerSelectExpression._identifier);
var joinTable = new CrossJoinExpression(innerSelectExpression.Tables.Single());
_tables.Add(joinTable);

var outerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Outer");
var projectionMapping = new Dictionary<ProjectionMember, Expression>();
foreach (var projection in _projectionMapping)
{
projectionMapping[projection.Key.Prepend(outerMemberInfo)] = projection.Value;
}
AddJoin(JoinType.InnerJoin, innerSelectExpression, transparentIdentifierType, joinPredicate);
}

var innerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Inner");
foreach (var projection in innerSelectExpression._projectionMapping)
{
projectionMapping[projection.Key.Prepend(innerMemberInfo)] = projection.Value;
}
public void AddLeftJoin(SelectExpression innerSelectExpression, SqlExpression joinPredicate, Type transparentIdentifierType)
{
AddJoin(JoinType.LeftJoin, innerSelectExpression, transparentIdentifierType, joinPredicate);
}

_projectionMapping = projectionMapping;
public void AddCrossJoin(SelectExpression innerSelectExpression, Type transparentIdentifierType)
{
AddJoin(JoinType.CrossJoin, innerSelectExpression, transparentIdentifierType);
}

public void AddInnerJoinLateral(SelectExpression innerSelectExpression, Type transparentIdentifierType)
{
var joinPredicate = TryExtractJoinKey(innerSelectExpression);
if (joinPredicate != null)
{
// TODO: Make sure that innerSelectExpression does not contain any reference from this SelectExpression
AddInnerJoin(innerSelectExpression, joinPredicate, transparentIdentifierType);
return;
}

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

if (innerSelectExpression.Orderings.Any()
|| innerSelectExpression.Limit != null
|| innerSelectExpression.Offset != null
|| innerSelectExpression.IsDistinct
|| innerSelectExpression.Predicate != null
|| innerSelectExpression.Tables.Count > 1
|| innerSelectExpression.GroupBy.Count > 1)
{
innerSelectExpression.PushdownIntoSubquery();
}

_identifier.AddRange(innerSelectExpression._identifier);
var joinTable = new InnerJoinLateralExpression(innerSelectExpression.Tables.Single());
_tables.Add(joinTable);

var outerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Outer");
var projectionMapping = new Dictionary<ProjectionMember, Expression>();
foreach (var projection in _projectionMapping)
{
projectionMapping[projection.Key.Prepend(outerMemberInfo)] = projection.Value;
}

var innerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Inner");
foreach (var projection in innerSelectExpression._projectionMapping)
{
projectionMapping[projection.Key.Prepend(innerMemberInfo)] = projection.Value;
}

_projectionMapping = projectionMapping;
AddJoin(JoinType.InnerJoinLateral, innerSelectExpression, transparentIdentifierType);
}

public void AddLeftJoinLateral(SelectExpression innerSelectExpression, Type transparentIdentifierType)
{
var joinPredicate = TryExtractJoinKey(innerSelectExpression);
if (joinPredicate != null)
{
// TODO: Make sure that innerSelectExpression does not contain any reference from this SelectExpression
AddLeftJoin(innerSelectExpression, joinPredicate, transparentIdentifierType);
return;
}

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

if (innerSelectExpression.Orderings.Any()
|| innerSelectExpression.Limit != null
|| innerSelectExpression.Offset != null
|| innerSelectExpression.IsDistinct
|| innerSelectExpression.Predicate != null
|| innerSelectExpression.Tables.Count > 1
|| innerSelectExpression.GroupBy.Count > 1)
{
innerSelectExpression.PushdownIntoSubquery();
}

_identifier.AddRange(innerSelectExpression._identifier);
var joinTable = new LeftJoinLateralExpression(innerSelectExpression.Tables.Single());
_tables.Add(joinTable);

var outerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Outer");
var projectionMapping = new Dictionary<ProjectionMember, Expression>();
foreach (var projection in _projectionMapping)
{
projectionMapping[projection.Key.Prepend(outerMemberInfo)] = projection.Value;
}

var innerMemberInfo = transparentIdentifierType.GetTypeInfo().GetDeclaredField("Inner");
foreach (var projection in innerSelectExpression._projectionMapping)
{
var projectionToAdd = projection.Value;
if (projectionToAdd is EntityProjectionExpression entityProjection)
{
projectionToAdd = entityProjection.MakeNullable();
}
else if (projectionToAdd is ColumnExpression column)
{
projectionToAdd = column.MakeNullable();
}

projectionMapping[projection.Key.Prepend(innerMemberInfo)] = projectionToAdd;
}

_projectionMapping = projectionMapping;
AddJoin(JoinType.LeftJoinLateral, innerSelectExpression, transparentIdentifierType);
}

private class SqlRemappingVisitor : ExpressionVisitor
Expand Down
1 change: 0 additions & 1 deletion src/EFCore/Query/ExpressionPrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,6 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp
}
else
{
////throw new NotSupportedException(CoreStrings.InvalidMemberInitBinding);
AppendLine(CoreStrings.InvalidMemberInitBinding);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,19 +379,19 @@ static Func<IQueryable<Order>, IQueryable<object>> ExpressionGenerator(string ex
case "ScalarSubquery":
return os => os.Select(o => (object)o.OrderDetails.Count());
default:
throw new NotSupportedException();
throw new InvalidOperationException();
}
}
}

private static IEnumerable<object[]> GetSetOperandTestCases()
=> from async in new[] { true, false }
from leftType in SupportedOperandExpressionType
from rightType in SupportedOperandExpressionType
from leftType in _supportedOperandExpressionType
from rightType in _supportedOperandExpressionType
select new object[] { async, leftType, rightType };

// ReSharper disable once StaticMemberInGenericType
private static readonly string[] SupportedOperandExpressionType =
private static readonly string[] _supportedOperandExpressionType =
{
"Column", "Function", "Constant", "Unary", "Binary", "ScalarSubquery"
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ public virtual Task Where_bitwise_and(bool isAsync)
cs => cs.Where(c => c.CustomerID == "ALFKI" & c.CustomerID == "ANATR"));
}

[ConditionalTheory(Skip = "Issue #14935. Cannot eval 'where (([c].CustomerID == \"ALFKI\") ^ True)'")]
[ConditionalTheory(Skip = "Issue #16645. Cannot eval 'where (([c].CustomerID == \"ALFKI\") ^ True)'")]
[InlineData(false)]
public virtual Task Where_bitwise_xor(bool isAsync)
{
Expand Down Expand Up @@ -1898,7 +1898,7 @@ public virtual Task Where_chain(bool isAsync)
.Where(o => o.OrderDate > new DateTime(1998, 1, 1)), entryCount: 8);
}

[ConditionalFact(Skip = "Issue #14935. Cannot eval 'Contains(Property([od], \"OrderID\"))'")]
[ConditionalFact]
public virtual void Where_navigation_contains()
{
using (var context = CreateContext())
Expand Down
Loading

0 comments on commit d0a4294

Please sign in to comment.