Skip to content

Commit

Permalink
Optimize SQL based on parameter nullability
Browse files Browse the repository at this point in the history
This is part of #17543 - Queries really slow due to null checks

Problem was that at the time we performed null semantics and related optimizations we didn't know the value of parameters, so we always had to assume they can be nullable, which resulted in additional IS NULL checks being created.
However, later in the pipeline we know the parameter values and we can optimize out those checks for parameters whose values are not null.
  • Loading branch information
maumar committed Oct 21, 2019
1 parent bf62b29 commit 4741dbe
Show file tree
Hide file tree
Showing 26 changed files with 711 additions and 480 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal
{
public class SqlExpressionOptimizingExpressionVisitor : ExpressionVisitor
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly bool _useRelationalNulls;

private static bool TryNegate(ExpressionType expressionType, out ExpressionType result)
Expand All @@ -26,12 +25,15 @@ private static bool TryNegate(ExpressionType expressionType, out ExpressionType
};

result = negated ?? default;

return negated.HasValue;
}

protected virtual ISqlExpressionFactory SqlExpressionFactory { get; }

public SqlExpressionOptimizingExpressionVisitor(ISqlExpressionFactory sqlExpressionFactory, bool useRelationalNulls)
{
_sqlExpressionFactory = sqlExpressionFactory;
SqlExpressionFactory = sqlExpressionFactory;
_useRelationalNulls = useRelationalNulls;
}

Expand All @@ -43,7 +45,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
_ => base.VisitExtension(extensionExpression),
};

private Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression)
protected virtual Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression)
{
if (sqlUnaryExpression.OperatorType == ExpressionType.Not)
{
Expand All @@ -55,15 +57,15 @@ private Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression
if (sqlUnaryExpression.OperatorType == ExpressionType.Equal
&& sqlUnaryExpression.Operand is SqlConstantExpression innerConstantNull1)
{
return _sqlExpressionFactory.Constant(innerConstantNull1.Value == null, sqlUnaryExpression.TypeMapping);
return SqlExpressionFactory.Constant(innerConstantNull1.Value == null, sqlUnaryExpression.TypeMapping);
}

// NULL IS NOT NULL -> false
// non_nullable_constant IS NOT NULL -> true
if (sqlUnaryExpression.OperatorType == ExpressionType.NotEqual
&& sqlUnaryExpression.Operand is SqlConstantExpression innerConstantNull2)
{
return _sqlExpressionFactory.Constant(innerConstantNull2.Value != null, sqlUnaryExpression.TypeMapping);
return SqlExpressionFactory.Constant(innerConstantNull2.Value != null, sqlUnaryExpression.TypeMapping);
}

if (sqlUnaryExpression.Operand is SqlUnaryExpression innerUnary)
Expand All @@ -72,14 +74,14 @@ private Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression
if (sqlUnaryExpression.OperatorType == ExpressionType.Equal
&& innerUnary.OperatorType == ExpressionType.Not)
{
return Visit(_sqlExpressionFactory.IsNull(innerUnary.Operand));
return Visit(SqlExpressionFactory.IsNull(innerUnary.Operand));
}

// (!a) IS NOT NULL <==> a IS NOT NULL
if (sqlUnaryExpression.OperatorType == ExpressionType.NotEqual
&& innerUnary.OperatorType == ExpressionType.Not)
{
return Visit(_sqlExpressionFactory.IsNotNull(innerUnary.Operand));
return Visit(SqlExpressionFactory.IsNotNull(innerUnary.Operand));
}
}

Expand All @@ -95,7 +97,7 @@ private Expression VisitNot(SqlUnaryExpression sqlUnaryExpression)
if (sqlUnaryExpression.Operand is SqlConstantExpression innerConstantBool
&& innerConstantBool.Value is bool value)
{
return _sqlExpressionFactory.Constant(!value, sqlUnaryExpression.TypeMapping);
return SqlExpressionFactory.Constant(!value, sqlUnaryExpression.TypeMapping);
}

if (sqlUnaryExpression.Operand is InExpression inExpression)
Expand All @@ -114,13 +116,13 @@ private Expression VisitNot(SqlUnaryExpression sqlUnaryExpression)
if (innerUnary.OperatorType == ExpressionType.Equal)
{
//!(a IS NULL) -> a IS NOT NULL
return Visit(_sqlExpressionFactory.IsNotNull(innerUnary.Operand));
return Visit(SqlExpressionFactory.IsNotNull(innerUnary.Operand));
}

//!(a IS NOT NULL) -> a IS NULL
if (innerUnary.OperatorType == ExpressionType.NotEqual)
{
return Visit(_sqlExpressionFactory.IsNull(innerUnary.Operand));
return Visit(SqlExpressionFactory.IsNull(innerUnary.Operand));
}
}

Expand All @@ -130,12 +132,12 @@ private Expression VisitNot(SqlUnaryExpression sqlUnaryExpression)
if (innerBinary.OperatorType == ExpressionType.AndAlso
|| innerBinary.OperatorType == ExpressionType.OrElse)
{
var newLeft = (SqlExpression)Visit(_sqlExpressionFactory.Not(innerBinary.Left));
var newRight = (SqlExpression)Visit(_sqlExpressionFactory.Not(innerBinary.Right));
var newLeft = (SqlExpression)Visit(SqlExpressionFactory.Not(innerBinary.Left));
var newRight = (SqlExpression)Visit(SqlExpressionFactory.Not(innerBinary.Right));

return innerBinary.OperatorType == ExpressionType.AndAlso
? _sqlExpressionFactory.OrElse(newLeft, newRight)
: _sqlExpressionFactory.AndAlso(newLeft, newRight);
? SqlExpressionFactory.OrElse(newLeft, newRight)
: SqlExpressionFactory.AndAlso(newLeft, newRight);
}

// those optimizations are only valid in 2-value logic
Expand All @@ -145,7 +147,7 @@ private Expression VisitNot(SqlUnaryExpression sqlUnaryExpression)
if (!_useRelationalNulls && TryNegate(innerBinary.OperatorType, out var negated))
{
return Visit(
_sqlExpressionFactory.MakeBinary(
SqlExpressionFactory.MakeBinary(
negated,
innerBinary.Left,
innerBinary.Right,
Expand Down Expand Up @@ -215,7 +217,7 @@ private Expression VisitSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpress
{
return (bool)constant.Value == (sqlBinaryExpression.OperatorType == ExpressionType.Equal)
? binary
: _sqlExpressionFactory.MakeBinary(
: SqlExpressionFactory.MakeBinary(
negated,
sqlBinaryExpression.Left,
sqlBinaryExpression.Right,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore.Query
{
public partial class RelationalShapedQueryCompilingExpressionVisitor
{
private class ParameterNullabilitySqlExpressionOptimizingExpressionVisitor : SqlExpressionOptimizingExpressionVisitor
{
private readonly IReadOnlyDictionary<string, object> _parametersValues;

public ParameterNullabilitySqlExpressionOptimizingExpressionVisitor(
ISqlExpressionFactory sqlExpressionFactory,
IReadOnlyDictionary<string, object> parametersValues)
: base(sqlExpressionFactory, useRelationalNulls: true)
{
_parametersValues = parametersValues;
}

protected override Expression VisitExtension(Expression extensionExpression)
{
if (extensionExpression is SelectExpression selectExpression)
{
var newSelectExpression = (SelectExpression)base.VisitExtension(extensionExpression);

return newSelectExpression.Predicate is SqlConstantExpression newSelectPredicateConstant
&& !(selectExpression.Predicate is SqlConstantExpression)
? newSelectExpression.Update(
newSelectExpression.Projection.ToList(),
newSelectExpression.Tables.ToList(),
SqlExpressionFactory.Equal(
newSelectPredicateConstant,
SqlExpressionFactory.Constant(true, newSelectPredicateConstant.TypeMapping)),
newSelectExpression.GroupBy.ToList(),
newSelectExpression.Having,
newSelectExpression.Orderings.ToList(),
newSelectExpression.Limit,
newSelectExpression.Offset,
newSelectExpression.IsDistinct,
newSelectExpression.Alias)
: newSelectExpression;
}

return base.VisitExtension(extensionExpression);
}

protected override Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression)
{
var newOperand = (SqlExpression)Visit(sqlUnaryExpression.Operand);
if (newOperand is SqlParameterExpression parameterOperand)
{
var parameterValue = _parametersValues[parameterOperand.Name];
if (sqlUnaryExpression.OperatorType == ExpressionType.Equal)
{
return SqlExpressionFactory.Constant(parameterValue == null, sqlUnaryExpression.TypeMapping);
}

if (sqlUnaryExpression.OperatorType == ExpressionType.NotEqual)
{
return SqlExpressionFactory.Constant(parameterValue != null, sqlUnaryExpression.TypeMapping);
}
}

return base.VisitSqlUnaryExpression(sqlUnaryExpression);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public SelectExpression Optimize(SelectExpression selectExpression, IReadOnlyDic
var query = new InExpressionValuesExpandingExpressionVisitor(
_sqlExpressionFactory, parametersValues).Visit(selectExpression);

query = new ParameterNullabilitySqlExpressionOptimizingExpressionVisitor(
_sqlExpressionFactory, parametersValues).Visit(query);

query = new FromSqlParameterApplyingExpressionVisitor(
_sqlExpressionFactory,
_parameterNameGeneratorFactory.Create(),
Expand Down
Loading

0 comments on commit 4741dbe

Please sign in to comment.