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

Implement non-boolean bitwise complement #18789

Merged
merged 1 commit into from
Nov 7, 2019
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
@@ -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():
roji marked this conversation as resolved.
Show resolved Hide resolved
{
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