Skip to content
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

Query: DRY joins inside SelectExpression #17163

Merged
merged 1 commit into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1737,6 +1737,7 @@ FROM root c
WHERE (((c[""Discriminator""] = ""Order"") AND (c[""CustomerID""] = ""QUICK"")) AND (c[""OrderDate""] > ""1998-01-01T00:00:00""))");
}

[ConditionalFact(Skip = "Issue #14935")]
public override void Where_navigation_contains()
{
base.Where_navigation_contains();
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 @@ -1895,10 +1895,11 @@ public virtual Task Where_chain(bool isAsync)
isAsync,
order => order
.Where(o => o.CustomerID == "QUICK")
.Where(o => o.OrderDate > new DateTime(1998, 1, 1)), entryCount: 8);
.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