-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing C# null semantics in the new pipeline
Currently we always apply "full" null semantics translation, meaning null values are completely removed from the tree. We could produce slightly simpler translation where it doesn't matter if we return NULL or FALSE (e.g. in predicates) Also, currently when testing if a given subtree is null we do it in naive way, simply adding IsNull call around entire subtree. Instead we can test it's constituents, but to do it properly we need to persist nullability information.
- Loading branch information
Showing
22 changed files
with
1,155 additions
and
126 deletions.
There are no files selected for viewing
519 changes: 519 additions & 0 deletions
519
src/EFCore.Relational/Query/PipeLine/NullSemanticsRewritingVisitor.cs
Large diffs are not rendered by default.
Oops, something went wrong.
202 changes: 202 additions & 0 deletions
202
src/EFCore.Relational/Query/PipeLine/SqlExpressionOptimizingVisitor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
// 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.Expressions; | ||
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline; | ||
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Query.Pipeline | ||
{ | ||
public class SqlExpressionOptimizingVisitor : ExpressionVisitor | ||
{ | ||
private readonly ISqlExpressionFactory _sqlExpressionFactory; | ||
|
||
private readonly Dictionary<ExpressionType, ExpressionType> _expressionTypesNegationMap | ||
= new Dictionary<ExpressionType, ExpressionType> | ||
{ | ||
{ ExpressionType.AndAlso, ExpressionType.OrElse }, | ||
{ ExpressionType.OrElse, ExpressionType.AndAlso }, | ||
{ ExpressionType.Equal, ExpressionType.NotEqual }, | ||
{ ExpressionType.NotEqual, ExpressionType.Equal }, | ||
{ ExpressionType.GreaterThan, ExpressionType.LessThanOrEqual }, | ||
{ ExpressionType.GreaterThanOrEqual, ExpressionType.LessThan }, | ||
{ ExpressionType.LessThan, ExpressionType.GreaterThanOrEqual }, | ||
{ ExpressionType.LessThanOrEqual, ExpressionType.GreaterThan }, | ||
}; | ||
|
||
public SqlExpressionOptimizingVisitor(ISqlExpressionFactory sqlExpressionFactory) | ||
{ | ||
_sqlExpressionFactory = sqlExpressionFactory; | ||
} | ||
|
||
protected override Expression VisitExtension(Expression extensionExpression) | ||
{ | ||
if (extensionExpression is SqlUnaryExpression outerUnary) | ||
{ | ||
var optimized = TryOptimizeSqlUnaryExpression(outerUnary); | ||
if (optimized != null) | ||
{ | ||
return optimized; | ||
} | ||
} | ||
|
||
if (extensionExpression is SqlBinaryExpression outerBinary) | ||
{ | ||
var optimized = TryOptimizeSqlBinaryExpression(outerBinary); | ||
if (optimized != null) | ||
{ | ||
return optimized; | ||
} | ||
} | ||
|
||
return base.VisitExtension(extensionExpression); | ||
} | ||
|
||
private Expression TryOptimizeSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression) | ||
{ | ||
// !(true) -> false | ||
// !(false) -> true | ||
if (sqlUnaryExpression.OperatorType == ExpressionType.Not | ||
&& sqlUnaryExpression.Operand is SqlConstantExpression innerConstantBool | ||
&& innerConstantBool.Value is bool value) | ||
{ | ||
return value | ||
? _sqlExpressionFactory.Constant(false, sqlUnaryExpression.TypeMapping) | ||
: _sqlExpressionFactory.Constant(true, sqlUnaryExpression.TypeMapping); | ||
} | ||
|
||
// NULL IS NULL -> true | ||
// non_nullablee_constant IS NULL -> false | ||
if (sqlUnaryExpression.OperatorType == ExpressionType.Equal | ||
&& sqlUnaryExpression.Operand is SqlConstantExpression innerConstantNull1) | ||
{ | ||
return _sqlExpressionFactory.Constant(innerConstantNull1.Value == null, sqlUnaryExpression.TypeMapping); | ||
} | ||
|
||
// NULL IS NOT NULL -> false | ||
// non_nullablee_constant IS NOT NULL -> true | ||
if (sqlUnaryExpression.OperatorType == ExpressionType.NotEqual | ||
&& sqlUnaryExpression.Operand is SqlConstantExpression innerConstantNull2 | ||
&& innerConstantNull2.Value == null) | ||
{ | ||
return _sqlExpressionFactory.Constant(innerConstantNull2.Value != null, sqlUnaryExpression.TypeMapping); | ||
} | ||
|
||
if (sqlUnaryExpression.Operand is SqlUnaryExpression innerUnary) | ||
{ | ||
if (sqlUnaryExpression.OperatorType == ExpressionType.Not) | ||
{ | ||
// !(!a) -> a | ||
if (innerUnary.OperatorType == ExpressionType.Not) | ||
{ | ||
return Visit(innerUnary.Operand); | ||
} | ||
|
||
if (innerUnary.OperatorType == ExpressionType.Equal) | ||
{ | ||
//!(a IS NULL) -> a IS NOT NULL | ||
return Visit(_sqlExpressionFactory.IsNotNull(innerUnary.Operand)); | ||
} | ||
|
||
//!(a IS NOT NULL) -> a IS NULL | ||
if (innerUnary.OperatorType == ExpressionType.NotEqual) | ||
{ | ||
return Visit(_sqlExpressionFactory.IsNull(innerUnary.Operand)); | ||
} | ||
} | ||
|
||
// (!a) IS NULL <==> a IS NULL | ||
if (sqlUnaryExpression.OperatorType == ExpressionType.Equal | ||
&& innerUnary.OperatorType == ExpressionType.Not) | ||
{ | ||
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)); | ||
} | ||
} | ||
|
||
if (sqlUnaryExpression.Operand is SqlBinaryExpression innerBinary) | ||
{ | ||
// De Morgan's | ||
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)); | ||
|
||
return innerBinary.OperatorType == ExpressionType.AndAlso | ||
? _sqlExpressionFactory.OrElse(newLeft, newRight) | ||
: _sqlExpressionFactory.AndAlso(newLeft, newRight); | ||
} | ||
|
||
// note that those optimizations are only valid in 2-value logic | ||
// they are safe to do here because null semantics removes possibility of nulls in the tree | ||
// however if we decide to do "partial" null semantics (that doesn't distinguish between NULL and FALSE, e.g. for predicates) | ||
// we need to be extra careful here | ||
if (_expressionTypesNegationMap.ContainsKey(innerBinary.OperatorType)) | ||
{ | ||
return Visit( | ||
_sqlExpressionFactory.MakeBinary( | ||
_expressionTypesNegationMap[innerBinary.OperatorType], | ||
innerBinary.Left, | ||
innerBinary.Right, | ||
innerBinary.TypeMapping)); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private Expression TryOptimizeSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpression) | ||
{ | ||
if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso | ||
|| sqlBinaryExpression.OperatorType == ExpressionType.OrElse) | ||
{ | ||
var newLeft = (SqlExpression)Visit(sqlBinaryExpression.Left); | ||
var newRight = (SqlExpression)Visit(sqlBinaryExpression.Right); | ||
|
||
var newLeftConstant = newLeft as SqlConstantExpression; | ||
var newRightConstant = newRight as SqlConstantExpression; | ||
|
||
// true && a -> a | ||
// true || a -> true | ||
// false && a -> false | ||
// false || a -> a | ||
if (newLeftConstant != null) | ||
{ | ||
return sqlBinaryExpression.OperatorType == ExpressionType.AndAlso | ||
? (bool)newLeftConstant.Value | ||
? newRight | ||
: newLeftConstant | ||
: (bool)newLeftConstant.Value | ||
? newLeftConstant | ||
: newRight; | ||
} | ||
else if (newRightConstant != null) | ||
{ | ||
// a && true -> a | ||
// a || true -> true | ||
// a && false -> false | ||
// a || false -> a | ||
return sqlBinaryExpression.OperatorType == ExpressionType.AndAlso | ||
? (bool)newRightConstant.Value | ||
? newLeft | ||
: newRightConstant | ||
: (bool)newRightConstant.Value | ||
? newRightConstant | ||
: newLeft; | ||
} | ||
|
||
return sqlBinaryExpression.Update(newLeft, newRight); | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.