Skip to content

Commit

Permalink
Implement non-boolean bitwise complement
Browse files Browse the repository at this point in the history
Fixes #18788
  • Loading branch information
roji committed Nov 7, 2019
1 parent 58f1005 commit a41d6a8
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static class CosmosExpressionExtensions
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static bool IsLogicalNot(this SqlUnaryExpression sqlUnaryExpression)
=> sqlUnaryExpression.OperatorType == ExpressionType.Not
&& (sqlUnaryExpression.Type == typeof(bool)
|| sqlUnaryExpression.Type == typeof(bool?));
}
}
9 changes: 7 additions & 2 deletions src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
Expand Down Expand Up @@ -107,16 +108,20 @@ private SqlExpression ApplyTypeMappingOnSqlUnary(
{
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.Not:
case ExpressionType.Not
when sqlUnaryExpression.IsLogicalNot():
{
resultTypeMapping = _boolTypeMapping;
operand = ApplyDefaultTypeMapping(sqlUnaryExpression.Operand);
break;
}

case ExpressionType.Convert:
resultTypeMapping = typeMapping;
operand = ApplyDefaultTypeMapping(sqlUnaryExpression.Operand);
break;

case ExpressionType.Not:
case ExpressionType.Negate:
resultTypeMapping = typeMapping;
operand = ApplyTypeMapping(sqlUnaryExpression.Operand, typeMapping);
Expand Down Expand Up @@ -417,7 +422,7 @@ public virtual SqlUnaryExpression Convert(SqlExpression operand, Type type, Core
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlUnaryExpression Not(SqlExpression operand)
=> MakeUnary(ExpressionType.Not, operand, typeof(bool));
=> MakeUnary(ExpressionType.Not, operand, operand.Type, operand.TypeMapping);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static class RelationalExpressionExtensions
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static bool IsLogicalNot(this SqlUnaryExpression sqlUnaryExpression)
=> sqlUnaryExpression.OperatorType == ExpressionType.Not
&& (sqlUnaryExpression.Type == typeof(bool)
|| sqlUnaryExpression.Type == typeof(bool?));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore.Query.Internal
Expand Down Expand Up @@ -389,8 +390,8 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres
var leftUnary = newLeft as SqlUnaryExpression;
var rightUnary = newRight as SqlUnaryExpression;

var leftNegated = leftUnary?.OperatorType == ExpressionType.Not;
var rightNegated = rightUnary?.OperatorType == ExpressionType.Not;
var leftNegated = leftUnary?.IsLogicalNot() == true;
var rightNegated = rightUnary?.IsLogicalNot() == true;

if (leftNegated)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,16 @@ private SqlExpression SimplifyUnaryExpression(
{
switch (operatorType)
{
case ExpressionType.Not:
case ExpressionType.Not
when type == typeof(bool)
|| type == typeof(bool?):
{
switch (operand)
{
// !(true) -> false
// !(false) -> true
case SqlConstantExpression constantOperand
when constantOperand.Value is bool value:
when constantOperand.Value is bool value:
{
return SqlExpressionFactory.Constant(!value, typeMapping);
}
Expand All @@ -142,6 +145,7 @@ private SqlExpression SimplifyUnaryExpression(
case ExpressionType.NotEqual:
return SqlExpressionFactory.IsNull(unaryOperand.Operand);
}

break;

case SqlBinaryExpression binaryOperand:
Expand All @@ -165,7 +169,8 @@ private SqlExpression SimplifyUnaryExpression(
// those optimizations are only valid in 2-value logic
// they are safe to do here because if we apply null semantics
// because null semantics removes possibility of nulls in the tree when the comparison is wrapped around NOT
if (!_useRelationalNulls && TryNegate(binaryOperand.OperatorType, out var negated))
if (!_useRelationalNulls
&& TryNegate(binaryOperand.OperatorType, out var negated))
{
return SimplifyBinaryExpression(
negated,
Expand All @@ -177,6 +182,7 @@ private SqlExpression SimplifyUnaryExpression(
break;
}
break;
}

case ExpressionType.Equal:
case ExpressionType.NotEqual:
Expand Down
11 changes: 10 additions & 1 deletion src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
Expand Down Expand Up @@ -515,14 +516,22 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio
break;
}

case ExpressionType.Not:
case ExpressionType.Not
when sqlUnaryExpression.IsLogicalNot():
{
_relationalCommandBuilder.Append("NOT (");
Visit(sqlUnaryExpression.Operand);
_relationalCommandBuilder.Append(")");
break;
}

case ExpressionType.Not:
{
_relationalCommandBuilder.Append("~");
Visit(sqlUnaryExpression.Operand);
break;
}

case ExpressionType.Equal:
{
Visit(sqlUnaryExpression.Operand);
Expand Down
9 changes: 7 additions & 2 deletions src/EFCore.Relational/Query/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
Expand Down Expand Up @@ -105,16 +106,20 @@ private SqlExpression ApplyTypeMappingOnSqlUnary(
{
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.Not:
case ExpressionType.Not
when sqlUnaryExpression.IsLogicalNot():
{
resultTypeMapping = _boolTypeMapping;
operand = ApplyDefaultTypeMapping(sqlUnaryExpression.Operand);
break;
}

case ExpressionType.Convert:
resultTypeMapping = typeMapping;
operand = ApplyDefaultTypeMapping(sqlUnaryExpression.Operand);
break;

case ExpressionType.Not:
case ExpressionType.Negate:
resultTypeMapping = typeMapping;
operand = ApplyTypeMapping(sqlUnaryExpression.Operand, typeMapping);
Expand Down Expand Up @@ -283,7 +288,7 @@ public virtual SqlUnaryExpression Convert(SqlExpression operand, Type type, Rela
=> MakeUnary(ExpressionType.Convert, operand, type, typeMapping);

public virtual SqlUnaryExpression Not(SqlExpression operand)
=> MakeUnary(ExpressionType.Not, operand, typeof(bool));
=> MakeUnary(ExpressionType.Not, operand, operand.Type, operand.TypeMapping);

public virtual SqlUnaryExpression Negate(SqlExpression operand)
=> MakeUnary(ExpressionType.Negate, operand, operand.Type, operand.TypeMapping);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

Expand Down Expand Up @@ -220,10 +221,18 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio
bool resultCondition;
switch (sqlUnaryExpression.OperatorType)
{
case ExpressionType.Not:
case ExpressionType.Not
when sqlUnaryExpression.IsLogicalNot():
{
_isSearchCondition = true;
resultCondition = true;
break;
}

case ExpressionType.Not:
_isSearchCondition = false;
resultCondition = false;
break;

case ExpressionType.Convert:
case ExpressionType.Negate:
Expand Down
11 changes: 11 additions & 0 deletions src/EFCore/Extensions/Internal/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,17 @@ public static LambdaExpression GetLambdaOrNull(this Expression expression)
? (LambdaExpression)unary.Operand
: null;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public static bool IsLogicalNot(this UnaryExpression sqlUnaryExpression)
=> sqlUnaryExpression.NodeType == ExpressionType.Not
&& (sqlUnaryExpression.Type == typeof(bool)
|| sqlUnaryExpression.Type == typeof(bool?));

private static Expression RemoveConvert(Expression expression)
{
if (expression is UnaryExpression unaryExpression
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Internal;

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
Expand Down Expand Up @@ -67,6 +68,7 @@ private bool TryExtractEqualityOperands(Expression expression, out Expression le

case MethodCallExpression methodCallExpression
when methodCallExpression.Method.Name == nameof(object.Equals):
{
negated = false;
if (methodCallExpression.Arguments.Count == 1
&& methodCallExpression.Object.Type == methodCallExpression.Arguments[0].Type)
Expand All @@ -80,12 +82,15 @@ private bool TryExtractEqualityOperands(Expression expression, out Expression le
}

return true;
}

case UnaryExpression unaryExpression
when unaryExpression.NodeType == ExpressionType.Not:
when unaryExpression.IsLogicalNot():
{
var result = TryExtractEqualityOperands(unaryExpression.Operand, out left, out right, out negated);
negated = !negated;
return result;
}
}

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

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

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
Expand Down Expand Up @@ -32,7 +33,8 @@ private static ExpressionType Negate(ExpressionType expressionType)

protected override Expression VisitUnary(UnaryExpression unaryExpression)
{
if (unaryExpression.NodeType == ExpressionType.Not)
if (unaryExpression.NodeType == ExpressionType.Not
&& unaryExpression.IsLogicalNot())
{
if (unaryExpression.Operand is ConstantExpression innerConstant
&& innerConstant.Value is bool value)
Expand Down
32 changes: 32 additions & 0 deletions test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2342,6 +2342,38 @@ FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND (((c[""CustomerID""] = ""ALFKI"") & (c[""CustomerID""] = ""ANATR"")) OR (c[""CustomerID""] = ""ANTON"")))");
}

public override async Task Where_bitwise_binary_not(bool isAsync)
{
await base.Where_bitwise_binary_not(isAsync);

AssertSql(
@"@__negatedId_0='-10249'
SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND (~c[""CustomerID""] = @__negatedId_0)");
}

public override async Task Where_bitwise_binary_and(bool isAsync)
{
await base.Where_bitwise_binary_and(isAsync);

AssertSql(
@"SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""CustomerID""] & 10248) = 10248)");
}

public override async Task Where_bitwise_binary_or(bool isAsync)
{
await base.Where_bitwise_binary_or(isAsync);

AssertSql(
@"SELECT c
FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""CustomerID""] | 10248) = 10248)");
}

[ConditionalFact(Skip = "Issue #17246")]
public override void Select_bitwise_or_with_logical_or()
{
Expand Down
Loading

0 comments on commit a41d6a8

Please sign in to comment.