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: Improve the logic of null expansion in join conditions with an… #7826

Merged
merged 1 commit into from
Mar 10, 2017
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 @@ -484,6 +484,14 @@ private static Expression UnfoldStructuralComparison(ExpressionType expressionTy
&& rightExpressions != null
&& leftExpressions.Length == rightExpressions.Length)
{
if (leftExpressions.Length == 1
&& expressionType == ExpressionType.Equal)
{
var translatedExpression = TransformNullComparison(leftExpressions[0], rightExpressions[0], binaryExpression.NodeType)
?? Expression.MakeBinary(expressionType, leftExpressions[0], rightExpressions[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use ExpressionType.Equal rather than expressionType here, just so its more clear

return Expression.AndAlso(translatedExpression, Expression.Constant(true, translatedExpression.Type));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expression.Constant(true), since translatedExpression is guaranteed to be bool

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually forgot to submit updated. Will fix in future PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is TranslatedExpression is guaranteed to return bool type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in #7843

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be but it's not that clear. I guess it is safer to leave it as is after all

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I concluded this way:
TransformNullComparison returns either IsNullExpression or Not(IsNullExpression).
IsNullExpression as type hardcoded to bool. Not expression takes type from the inner expression so it have to bool only in this case.

}

return leftExpressions
.Zip(rightExpressions, (l, r) =>
TransformNullComparison(l, r, binaryExpression.NodeType)
Expand Down
74 changes: 47 additions & 27 deletions src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public class DefaultQuerySqlGenerator : ThrowingExpressionVisitor, ISqlExpressio
private IReadOnlyDictionary<string, object> _parametersValues;
private ParameterNameGenerator _parameterNameGenerator;
private RelationalTypeMapping _typeMapping;
private RelationalNullsExpandingVisitor _relationalNullsExpandingVisitor;
private PredicateReductionExpressionOptimizer _predicateReductionExpressionOptimizer;
private PredicateNegationExpressionOptimizer _predicateNegationExpressionOptimizer;
private ReducingExpressionVisitor _reducingExpressionVisitor;
private BooleanExpressionTranslatingVisitor _booleanExpressionTranslatingVisitor;

private static readonly Dictionary<ExpressionType, string> _operatorMap = new Dictionary<ExpressionType, string>
{
Expand Down Expand Up @@ -266,48 +271,63 @@ var newExpression
= new NullComparisonTransformingVisitor(_parametersValues)
.Visit(expression);

var binaryExpression = newExpression as BinaryExpression;
var relationalNullsOptimizedExpandingVisitor = new RelationalNullsOptimizedExpandingVisitor();
var relationalNullsExpandingVisitor = new RelationalNullsExpandingVisitor();
if (_relationalNullsExpandingVisitor == null)
{
_relationalNullsExpandingVisitor = new RelationalNullsExpandingVisitor();
}

if (joinCondition
&& binaryExpression != null)
if (_predicateReductionExpressionOptimizer == null)
{
var optimizedLeftExpression = relationalNullsOptimizedExpandingVisitor.Visit(binaryExpression.Left);
_predicateReductionExpressionOptimizer = new PredicateReductionExpressionOptimizer();
}

optimizedLeftExpression
= relationalNullsOptimizedExpandingVisitor.IsOptimalExpansion
? optimizedLeftExpression
: relationalNullsExpandingVisitor.Visit(binaryExpression.Left);
if (_predicateNegationExpressionOptimizer == null)
{
_predicateNegationExpressionOptimizer = new PredicateNegationExpressionOptimizer();
}

relationalNullsOptimizedExpandingVisitor = new RelationalNullsOptimizedExpandingVisitor();
var optimizedRightExpression = relationalNullsOptimizedExpandingVisitor.Visit(binaryExpression.Right);
if (_reducingExpressionVisitor == null)
{
_reducingExpressionVisitor = new ReducingExpressionVisitor();
}

optimizedRightExpression
= relationalNullsOptimizedExpandingVisitor.IsOptimalExpansion
? optimizedRightExpression
: relationalNullsExpandingVisitor.Visit(binaryExpression.Right);
if (_booleanExpressionTranslatingVisitor == null)
{
_booleanExpressionTranslatingVisitor = new BooleanExpressionTranslatingVisitor();
}

newExpression = Expression.MakeBinary(binaryExpression.NodeType, optimizedLeftExpression, optimizedRightExpression);
if (joinCondition
&& newExpression is BinaryExpression binaryExpression
&& binaryExpression.NodeType == ExpressionType.Equal)
{
newExpression = Expression.MakeBinary(
binaryExpression.NodeType,
ApplyNullSemantics(binaryExpression.Left),
ApplyNullSemantics(binaryExpression.Right));
}
else
{
var optimizedExpression = relationalNullsOptimizedExpandingVisitor.Visit(newExpression);

newExpression
= relationalNullsOptimizedExpandingVisitor.IsOptimalExpansion
? optimizedExpression
: relationalNullsExpandingVisitor.Visit(newExpression);
newExpression = ApplyNullSemantics(newExpression);
}

newExpression = new PredicateReductionExpressionOptimizer().Visit(newExpression);
newExpression = new PredicateNegationExpressionOptimizer().Visit(newExpression);
newExpression = new ReducingExpressionVisitor().Visit(newExpression);
newExpression = new BooleanExpressionTranslatingVisitor().Translate(newExpression, searchCondition: searchCondition);
newExpression = _predicateReductionExpressionOptimizer.Visit(newExpression);
newExpression = _predicateNegationExpressionOptimizer.Visit(newExpression);
newExpression = _reducingExpressionVisitor.Visit(newExpression);
newExpression = _booleanExpressionTranslatingVisitor.Translate(newExpression, searchCondition: searchCondition);

return newExpression;
}

private Expression ApplyNullSemantics(Expression expression)
{
var relationalNullsOptimizedExpandingVisitor = new RelationalNullsOptimizedExpandingVisitor();
var optimizedRightExpression = relationalNullsOptimizedExpandingVisitor.Visit(expression);

return relationalNullsOptimizedExpandingVisitor.IsOptimalExpansion
? optimizedRightExpression
: _relationalNullsExpandingVisitor.Visit(expression);
}

/// <summary>
/// Visit a single projection in SQL SELECT clause
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3143,7 +3143,7 @@ from l1 in grouping.DefaultIfEmpty()
select new { Id = l2.Id, Nane = l1 != null ? l1.Name : null },
(l1s, l2s) =>
from l2 in l2s
join l1 in l1s.OrderBy(x => Maybe(x.OneToOne_Optional_FK, () => x.OneToOne_Optional_FK.Name)).Take(2)
join l1 in l1s.OrderBy(x => Maybe(x.OneToOne_Optional_FK, () => x.OneToOne_Optional_FK.Name)).Take(2)
on l2.Level1_Optional_Id equals l1.Id into grouping
from l1 in grouping.DefaultIfEmpty()
select new { Id = l2.Id, Nane = l1 != null ? l1.Name : null },
Expand Down Expand Up @@ -3792,6 +3792,56 @@ public virtual void Where_on_multilevel_reference_in_subquery_with_outer_project
.Select(l3 => l3.Name));
}

[ConditionalFact]
public virtual void Join_condition_optimizations_applied_correctly_when_anonymous_type_with_single_property()
{
using (var context = CreateContext())
{
var query = from l1 in context.LevelOne
join l2 in context.LevelTwo
on new
{
A = EF.Property<int?>(l1, "OneToMany_Optional_Self_InverseId"),
}
equals new
{
A = EF.Property<int?>(l2, "Level1_Optional_Id"),
}
select l1;

var result = query.ToList();

// This result is manually verified. Do not change.
Assert.Equal(53, result.Count);
}
}

[ConditionalFact]
public virtual void Join_condition_optimizations_applied_correctly_when_anonymous_type_with_multiple_properties()
{
using (var context = CreateContext())
{
var query = from l1 in context.LevelOne
join l2 in context.LevelTwo
on new
{
A = EF.Property<int?>(l1, "OneToMany_Optional_Self_InverseId"),
B = EF.Property<int?>(l1, "OneToOne_Optional_SelfId")
}
equals new
{
A = EF.Property<int?>(l2, "Level1_Optional_Id"),
B = EF.Property<int?>(l2, "OneToMany_Optional_Self_InverseId"),
}
select l1;

var result = query.ToList();

// This result is manually verified. Do not change.
Assert.Equal(39, result.Count);
}
}

private static TResult Maybe<TResult>(object caller, Func<TResult> expression) where TResult : class
{
if (caller == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2659,6 +2659,28 @@ OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
Sql);
}

public override void Join_condition_optimizations_applied_correctly_when_anonymous_type_with_single_property()
{
base.Join_condition_optimizations_applied_correctly_when_anonymous_type_with_single_property();

Assert.Equal(
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId]
FROM [Level1] AS [l1]
INNER JOIN [Level2] AS [l2] ON ([l1].[OneToMany_Optional_Self_InverseId] = [l2].[Level1_Optional_Id]) OR ([l1].[OneToMany_Optional_Self_InverseId] IS NULL AND [l2].[Level1_Optional_Id] IS NULL)",
Sql);
}

public override void Join_condition_optimizations_applied_correctly_when_anonymous_type_with_multiple_properties()
{
base.Join_condition_optimizations_applied_correctly_when_anonymous_type_with_multiple_properties();

Assert.Equal(
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId]
FROM [Level1] AS [l1]
INNER JOIN [Level2] AS [l2] ON (([l1].[OneToMany_Optional_Self_InverseId] = [l2].[Level1_Optional_Id]) OR ([l1].[OneToMany_Optional_Self_InverseId] IS NULL AND [l2].[Level1_Optional_Id] IS NULL)) AND (([l1].[OneToOne_Optional_SelfId] = [l2].[OneToMany_Optional_Self_InverseId]) OR ([l1].[OneToOne_Optional_SelfId] IS NULL AND [l2].[OneToMany_Optional_Self_InverseId] IS NULL))",
Sql);
}

private const string FileLineEnding = @"
";

Expand Down