Skip to content

Commit

Permalink
Simplify AND and OR (#34133)
Browse files Browse the repository at this point in the history
When constructing `AndAlso` and `OrElse` expressions, perform some basic local
simplifications.
  • Loading branch information
ranma42 authored Jul 2, 2024
1 parent bbf1ae4 commit 4649fb3
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 114 deletions.
4 changes: 3 additions & 1 deletion src/EFCore.Relational/Query/ISqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ public interface ISqlExpressionFactory
/// <param name="left">The left operand of binary operation.</param>
/// <param name="right">The right operand of binary operation.</param>
/// <param name="typeMapping">A type mapping to be assigned to the created expression.</param>
/// <param name="existingExpr">An optional expression that can be re-used if it matches the new expression.</param>
/// <returns>A <see cref="SqlExpression" /> with the given arguments.</returns>
SqlExpression? MakeBinary(
ExpressionType operatorType,
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping);
RelationalTypeMapping? typeMapping,
SqlExpression? existingExpr = null);

// Comparison
/// <summary>
Expand Down
90 changes: 87 additions & 3 deletions src/EFCore.Relational/Query/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,17 @@ private SqlExpression ApplyTypeMappingOnJsonScalar(
ExpressionType operatorType,
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping)
RelationalTypeMapping? typeMapping,
SqlExpression? existingExpr = null)
{
switch (operatorType)
{
case ExpressionType.AndAlso:
return ApplyTypeMapping(AndAlso(left, right, existingExpr), typeMapping);
case ExpressionType.OrElse:
return ApplyTypeMapping(OrElse(left, right, existingExpr), typeMapping);
}

if (!SqlBinaryExpression.IsValidOperator(operatorType))
{
return null;
Expand All @@ -411,8 +420,6 @@ private SqlExpression ApplyTypeMappingOnJsonScalar(
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.NotEqual:
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
returnType = typeof(bool);
break;
}
Expand Down Expand Up @@ -449,10 +456,87 @@ public virtual SqlExpression LessThanOrEqual(SqlExpression left, SqlExpression r
public virtual SqlExpression AndAlso(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.AndAlso, left, right, null)!;

private SqlExpression AndAlso(SqlExpression left, SqlExpression right, SqlExpression? existingExpr)
{
// false && x -> false
// x && true -> x
// x && x -> x
if (left is SqlConstantExpression { Value: false }
|| right is SqlConstantExpression { Value: true }
|| left.Equals(right))
{
return left;
}
// true && x -> x
// x && false -> false
else if (left is SqlConstantExpression { Value: true } || right is SqlConstantExpression { Value: false })
{
return right;
}
// x is null && x is not null -> false
// x is not null && x is null -> false
else if (left is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary
&& right is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } rightUnary
&& leftUnary.Operand.Equals(rightUnary.Operand))
{
// the case in which left and right are the same expression is handled above
return Constant(false);
}
else if (existingExpr is SqlBinaryExpression { OperatorType: ExpressionType.AndAlso } binaryExpr
&& left == binaryExpr.Left
&& right == binaryExpr.Right)
{
return existingExpr;
}
else
{
return new SqlBinaryExpression(ExpressionType.AndAlso, left, right, typeof(bool), null);
}
}

/// <inheritdoc />
public virtual SqlExpression OrElse(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.OrElse, left, right, null)!;

private SqlExpression OrElse(SqlExpression left, SqlExpression right, SqlExpression? existingExpr = null)
{
// true || x -> true
// x || false -> x
// x || x -> x
if (left is SqlConstantExpression { Value: true }
|| right is SqlConstantExpression { Value: false }
|| left.Equals(right))
{
return left;
}
// false || x -> x
// x || true -> true
else if (left is SqlConstantExpression { Value: false }
|| right is SqlConstantExpression { Value: true })
{
return right;
}
// x is null || x is not null -> true
// x is not null || x is null -> true
else if (left is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary
&& right is SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } rightUnary
&& leftUnary.Operand.Equals(rightUnary.Operand))
{
// the case in which left and right are the same expression is handled above
return Constant(true);
}
else if (existingExpr is SqlBinaryExpression { OperatorType: ExpressionType.OrElse } binaryExpr
&& left == binaryExpr.Left
&& right == binaryExpr.Right)
{
return existingExpr;
}
else
{
return new SqlBinaryExpression(ExpressionType.OrElse, left, right, typeof(bool), null);
}
}

/// <inheritdoc />
public virtual SqlExpression Add(SqlExpression left, SqlExpression right, RelationalTypeMapping? typeMapping = null)
=> MakeBinary(ExpressionType.Add, left, right, typeMapping)!;
Expand Down
139 changes: 36 additions & 103 deletions src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1358,8 +1358,14 @@ or ExpressionType.LessThan
}

return result is SqlBinaryExpression sqlBinaryResult
&& sqlBinaryExpression.OperatorType is ExpressionType.AndAlso or ExpressionType.OrElse
? SimplifyLogicalSqlBinaryExpression(sqlBinaryResult)
&& sqlBinaryResult.OperatorType is ExpressionType.AndAlso or ExpressionType.OrElse
? _sqlExpressionFactory.MakeBinary( // invoke MakeBinary simplifications
sqlBinaryResult.OperatorType,
sqlBinaryResult.Left,
sqlBinaryResult.Right,
sqlBinaryResult.TypeMapping,
sqlBinaryResult
)!
: result;

SqlExpression AddNullConcatenationProtection(SqlExpression argument, RelationalTypeMapping typeMapping)
Expand Down Expand Up @@ -1806,26 +1812,16 @@ private SqlExpression RewriteNullSemantics(
{
nullable = leftNullable || rightNullable;

return SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.OrElse(
body,
SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.AndAlso(leftIsNull, rightIsNull))));
return _sqlExpressionFactory.OrElse(body, _sqlExpressionFactory.AndAlso(leftIsNull, rightIsNull));
}

// doing a full null semantics rewrite - removing all nulls from truth table
nullable = false;

// (a == b && (a != null && b != null)) || (a == null && b == null)
body = SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.OrElse(
SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.AndAlso(
body,
SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.AndAlso(leftIsNotNull, rightIsNotNull)))),
SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.AndAlso(leftIsNull, rightIsNull))));
body = _sqlExpressionFactory.OrElse(
_sqlExpressionFactory.AndAlso(body, _sqlExpressionFactory.AndAlso(leftIsNotNull, rightIsNotNull)),
_sqlExpressionFactory.AndAlso(leftIsNull, rightIsNull));

if (sqlBinaryExpression.OperatorType == ExpressionType.NotEqual)
{
Expand All @@ -1836,65 +1832,6 @@ private SqlExpression RewriteNullSemantics(
return body;
}

private SqlExpression SimplifyLogicalSqlBinaryExpression(SqlExpression expression)
{
if (expression is not SqlBinaryExpression sqlBinaryExpression)
{
return expression;
}

if (sqlBinaryExpression is
{
Left: SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } leftUnary,
Right: SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual } rightUnary
}
&& leftUnary.Operand.Equals(rightUnary.Operand))
{
// a is null || a is null -> a is null
// a is not null || a is not null -> a is not null
// a is null && a is null -> a is null
// a is not null && a is not null -> a is not null
// a is null || a is not null -> true
// a is null && a is not null -> false
return leftUnary.OperatorType == rightUnary.OperatorType
? leftUnary
: _sqlExpressionFactory.Constant(
sqlBinaryExpression.OperatorType == ExpressionType.OrElse, sqlBinaryExpression.TypeMapping);
}

// true && a -> a
// true || a -> true
// false && a -> false
// false || a -> a
if (sqlBinaryExpression.Left is SqlConstantExpression { Value: bool leftBoolValue } newLeftConstant)
{
return sqlBinaryExpression.OperatorType == ExpressionType.AndAlso
? leftBoolValue
? sqlBinaryExpression.Right
: newLeftConstant
: leftBoolValue
? newLeftConstant
: sqlBinaryExpression.Right;
}

if (sqlBinaryExpression.Right is SqlConstantExpression { Value: bool rightBoolValue } newRightConstant)
{
// a && true -> a
// a || true -> true
// a && false -> false
// a || false -> a
return sqlBinaryExpression.OperatorType == ExpressionType.AndAlso
? rightBoolValue
? sqlBinaryExpression.Left
: newRightConstant
: rightBoolValue
? newRightConstant
: sqlBinaryExpression.Left;
}

return sqlBinaryExpression;
}

/// <summary>
/// Attempts to simplify a unary not operation on a non-nullable operand.
/// </summary>
Expand Down Expand Up @@ -1948,14 +1885,13 @@ protected virtual SqlExpression OptimizeNonNullableNotExpression(SqlExpression e
var left = OptimizeNonNullableNotExpression(_sqlExpressionFactory.Not(sqlBinaryOperand.Left));
var right = OptimizeNonNullableNotExpression(_sqlExpressionFactory.Not(sqlBinaryOperand.Right));

return SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.MakeBinary(
sqlBinaryOperand.OperatorType == ExpressionType.AndAlso
? ExpressionType.OrElse
: ExpressionType.AndAlso,
left,
right,
sqlBinaryOperand.TypeMapping)!);
return _sqlExpressionFactory.MakeBinary(
sqlBinaryOperand.OperatorType == ExpressionType.AndAlso
? ExpressionType.OrElse
: ExpressionType.AndAlso,
left,
right,
sqlBinaryOperand.TypeMapping)!;
}

// use equality where possible
Expand Down Expand Up @@ -2276,14 +2212,13 @@ private SqlExpression ProcessNullNotNull(SqlExpression sqlExpression, bool opera
sqlUnaryExpression.TypeMapping)!,
operandNullable);

return SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.MakeBinary(
sqlUnaryExpression.OperatorType == ExpressionType.Equal
? ExpressionType.OrElse
: ExpressionType.AndAlso,
left,
right,
sqlUnaryExpression.TypeMapping)!);
return _sqlExpressionFactory.MakeBinary(
sqlUnaryExpression.OperatorType == ExpressionType.Equal
? ExpressionType.OrElse
: ExpressionType.AndAlso,
left,
right,
sqlUnaryExpression.TypeMapping)!;
}

case SqlFunctionExpression sqlFunctionExpression:
Expand All @@ -2305,14 +2240,13 @@ private SqlExpression ProcessNullNotNull(SqlExpression sqlExpression, bool opera
sqlUnaryExpression.TypeMapping)!,
operandNullable))
.Aggregate(
(l, r) => SimplifyLogicalSqlBinaryExpression(
_sqlExpressionFactory.MakeBinary(
sqlUnaryExpression.OperatorType == ExpressionType.Equal
? ExpressionType.AndAlso
: ExpressionType.OrElse,
l,
r,
sqlUnaryExpression.TypeMapping)!));
(l, r) => _sqlExpressionFactory.MakeBinary(
sqlUnaryExpression.OperatorType == ExpressionType.Equal
? ExpressionType.AndAlso
: ExpressionType.OrElse,
l,
r,
sqlUnaryExpression.TypeMapping)!);
}

if (!sqlFunctionExpression.IsNullable)
Expand Down Expand Up @@ -2358,10 +2292,9 @@ private SqlExpression ProcessNullNotNull(SqlExpression sqlExpression, bool opera
sqlUnaryExpression.TypeMapping)!,
operandNullable))
.Aggregate(
(r, e) => SimplifyLogicalSqlBinaryExpression(
sqlUnaryExpression.OperatorType == ExpressionType.Equal
? _sqlExpressionFactory.OrElse(r, e)
: _sqlExpressionFactory.AndAlso(r, e)));
(r, e) => sqlUnaryExpression.OperatorType == ExpressionType.Equal
? _sqlExpressionFactory.OrElse(r, e)
: _sqlExpressionFactory.AndAlso(r, e));

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7156,7 +7156,7 @@ public override async Task Query_reusing_parameter_doesnt_declare_duplicate_para
FROM (
SELECT DISTINCT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank]
FROM [Gears] AS [g]
WHERE [g].[Nickname] <> @__prm_Inner_Nickname_0 AND [g].[Nickname] <> @__prm_Inner_Nickname_0
WHERE [g].[Nickname] <> @__prm_Inner_Nickname_0
) AS [g0]
ORDER BY [g0].[FullName]
""");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public override async Task Join_composite_key(bool async)
"""
SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate]
FROM [Customers] AS [c]
INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] AND [c].[CustomerID] = [o].[CustomerID]
INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID]
WHERE [c].[CustomerID] LIKE N'F%'
""");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5185,7 +5185,7 @@ FROM [Customers] AS [c]
WHERE (
SELECT COUNT(*)
FROM [Orders] AS [o]
WHERE [c].[CustomerID] = [o].[CustomerID] AND [c].[CustomerID] = [o].[CustomerID]) > 0
WHERE [c].[CustomerID] = [o].[CustomerID]) > 0
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9528,7 +9528,7 @@ UNION ALL
SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator]
FROM [Officers] AS [o]
) AS [u]
WHERE [u].[Nickname] <> @__prm_Inner_Nickname_0 AND [u].[Nickname] <> @__prm_Inner_Nickname_0
WHERE [u].[Nickname] <> @__prm_Inner_Nickname_0
) AS [u0]
ORDER BY [u0].[FullName]
""");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8137,7 +8137,7 @@ WHEN [o].[Nickname] IS NOT NULL THEN N'Officer'
END AS [Discriminator]
FROM [Gears] AS [g]
LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId]
WHERE [g].[Nickname] <> @__prm_Inner_Nickname_0 AND [g].[Nickname] <> @__prm_Inner_Nickname_0
WHERE [g].[Nickname] <> @__prm_Inner_Nickname_0
) AS [s]
ORDER BY [s].[FullName]
""");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2943,7 +2943,7 @@ public override async Task Query_reusing_parameter_doesnt_declare_duplicate_para
FROM (
SELECT DISTINCT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank]
FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g]
WHERE [g].[Nickname] <> @__prm_Inner_Nickname_0 AND [g].[Nickname] <> @__prm_Inner_Nickname_0
WHERE [g].[Nickname] <> @__prm_Inner_Nickname_0
) AS [g0]
ORDER BY [g0].[FullName]
""");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3864,7 +3864,7 @@ public override async Task Query_reusing_parameter_doesnt_declare_duplicate_para
FROM (
SELECT DISTINCT "g"."Nickname", "g"."SquadId", "g"."AssignedCityName", "g"."CityOfBirthName", "g"."Discriminator", "g"."FullName", "g"."HasSoulPatch", "g"."LeaderNickname", "g"."LeaderSquadId", "g"."Rank"
FROM "Gears" AS "g"
WHERE "g"."Nickname" <> @__prm_Inner_Nickname_0 AND "g"."Nickname" <> @__prm_Inner_Nickname_0
WHERE "g"."Nickname" <> @__prm_Inner_Nickname_0
) AS "g0"
ORDER BY "g0"."FullName"
""");
Expand Down

0 comments on commit 4649fb3

Please sign in to comment.