Skip to content
Draft
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
8 changes: 8 additions & 0 deletions src/EFCore.Relational/Query/ISqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ public interface ISqlExpressionFactory
SqlExpression? existingExpression = null);

// Comparison
/// <summary>
/// Creates a <see cref="SqlExpression" /> which represents an IS DISTINCT FROM expression.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns>An expression representing a SQL IS DISTINCT FROM expression.</returns>
SqlExpression IsDistinctFrom(SqlExpression left, SqlExpression right);

/// <summary>
/// Creates a <see cref="SqlExpression" /> which represents an equality comparison.
/// </summary>
Expand Down
54 changes: 53 additions & 1 deletion src/EFCore.Relational/Query/QuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,10 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio
GenerateIn(inExpression, negated: true);
break;

case IsDistinctFromExpression isDistinctFromExpression:
GenerateIsDistinctFrom(isDistinctFromExpression, negated: true);
break;

case ExistsExpression existsExpression:
GenerateExists(existsExpression, negated: true);
break;
Expand Down Expand Up @@ -998,6 +1002,54 @@ protected virtual void GenerateIn(InExpression inExpression, bool negated)
_relationalCommandBuilder.Append(")");
}

/// <summary>
/// Generates SQL for an IS DISTINCT FROM expression.
/// </summary>
/// <param name="isExpression">The <see cref="InExpression" /> for which to generate SQL.</param>
protected sealed override Expression VisitIsDistinctFrom(IsDistinctFromExpression isExpression)
{
GenerateIsDistinctFrom(isExpression, negated: false);

return isExpression;
}

/// <summary>
/// Generates SQL for an IS DISTINCT FROM expression.
/// </summary>
/// <param name="isDistinctFromExpression">The <see cref="IsDistinctFromExpression" /> for which to generate SQL.</param>
/// <param name="negated">Whether the given <paramref name="isDistinctFromExpression" /> is negated.</param>
protected virtual void GenerateIsDistinctFrom(IsDistinctFromExpression isDistinctFromExpression, bool negated)
{
var requiresBrackets = RequiresParentheses(isDistinctFromExpression, isDistinctFromExpression.Left);
if (requiresBrackets)
{
_relationalCommandBuilder.Append("(");
}

Visit(isDistinctFromExpression.Left);
if (requiresBrackets)
{
_relationalCommandBuilder.Append(")");
}

_relationalCommandBuilder.Append(negated
? " IS NOT DISTINCT FROM "
: " IS DISTINCT FROM "
);

requiresBrackets = RequiresParentheses(isDistinctFromExpression, isDistinctFromExpression.Right);
if (requiresBrackets)
{
_relationalCommandBuilder.Append("(");
}

Visit(isDistinctFromExpression.Right);
if (requiresBrackets)
{
_relationalCommandBuilder.Append(")");
}
}

/// <summary>
/// Generates SQL for an AT TIME ZONE expression.
/// </summary>
Expand Down Expand Up @@ -1689,7 +1741,7 @@ protected virtual bool RequiresParentheses(SqlExpression outerExpression, SqlExp
return true;
}

case CollateExpression or LikeExpression or AtTimeZoneExpression or JsonScalarExpression:
case CollateExpression or LikeExpression or AtTimeZoneExpression or JsonScalarExpression or IsDistinctFromExpression:
return !TryGetOperatorInfo(outerExpression, out outerPrecedence, out _)
|| !TryGetOperatorInfo(innerExpression, out innerPrecedence, out _)
|| outerPrecedence >= innerPrecedence;
Expand Down
20 changes: 20 additions & 0 deletions src/EFCore.Relational/Query/SqlExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public SqlExpressionFactory(SqlExpressionFactoryDependencies dependencies)
ColumnExpression e => e.ApplyTypeMapping(typeMapping),
DistinctExpression e => ApplyTypeMappingOnDistinct(e, typeMapping),
InExpression e => ApplyTypeMappingOnIn(e),
IsDistinctFromExpression e => ApplyTypeMappingOnIsDistinctFrom(e),

// We only do type inference for JSON scalar expression which represent a single array indexing operation; we can infer the
// array's mapping from the element or vice versa, allowing e.g. parameter primitive collections to get inferred when an
Expand Down Expand Up @@ -355,6 +356,21 @@ private InExpression ApplyTypeMappingOnIn(InExpression inExpression)
: inExpression.ApplyTypeMapping(_boolTypeMapping);
}

private IsDistinctFromExpression ApplyTypeMappingOnIsDistinctFrom(IsDistinctFromExpression isDistinctFromExpression)
{
var left = isDistinctFromExpression.Left;
var right = isDistinctFromExpression.Right;
var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(left, right)
// We avoid object here since the result does not get typeMapping from outside.
?? _typeMappingSource.FindMapping(
left.Type != typeof(object) ? left.Type : right.Type,
Dependencies.Model);
return new IsDistinctFromExpression(
ApplyTypeMapping(left, inferredTypeMapping),
ApplyTypeMapping(right, inferredTypeMapping),
_boolTypeMapping);
}

private SqlExpression ApplyTypeMappingOnJsonScalar(
JsonScalarExpression jsonScalarExpression,
RelationalTypeMapping? elementMapping)
Expand Down Expand Up @@ -432,6 +448,10 @@ private SqlExpression ApplyTypeMappingOnJsonScalar(
new SqlBinaryExpression(operatorType, left, right, returnType, null), typeMapping);
}

/// <inheritdoc />
public virtual SqlExpression IsDistinctFrom(SqlExpression left, SqlExpression right)
=> ApplyTypeMapping(new IsDistinctFromExpression(left, right, null), null);

/// <inheritdoc />
public virtual SqlExpression Equal(SqlExpression left, SqlExpression right)
=> MakeBinary(ExpressionType.Equal, left, right, null)!;
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore.Relational/Query/SqlExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ShapedQueryExpression shapedQueryExpression
ExistsExpression existsExpression => VisitExists(existsExpression),
FromSqlExpression fromSqlExpression => VisitFromSql(fromSqlExpression),
InExpression inExpression => VisitIn(inExpression),
IsDistinctFromExpression isDistinctFromExpression => VisitIsDistinctFrom(isDistinctFromExpression),
IntersectExpression intersectExpression => VisitIntersect(intersectExpression),
InnerJoinExpression innerJoinExpression => VisitInnerJoin(innerJoinExpression),
LeftJoinExpression leftJoinExpression => VisitLeftJoin(leftJoinExpression),
Expand Down Expand Up @@ -144,6 +145,13 @@ ShapedQueryExpression shapedQueryExpression
/// <returns>The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.</returns>
protected abstract Expression VisitIn(InExpression inExpression);

/// <summary>
/// Visits the children of the IS DISTINCT FROM expression.
/// </summary>
/// <param name="isDistinctFromExpression">The expression to visit.</param>
/// <returns>The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.</returns>
protected abstract Expression VisitIsDistinctFrom(IsDistinctFromExpression isDistinctFromExpression);

/// <summary>
/// Visits the children of the intersect expression.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;

/// <summary>
/// <para>
/// An expression that represents an IS DISTINCT FROM in a SQL tree.
/// </para>
/// <para>
/// This type is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
public class IsDistinctFromExpression : SqlExpression
{
private static ConstructorInfo? _quotingConstructor;

/// <summary>
/// Creates a new instance of the <see cref="IsDistinctFromExpression" /> class.
/// </summary>
/// <param name="left">An expression which is left operand.</param>
/// <param name="right">An expression which is right operand.</param>
/// <param name="typeMapping">The <see cref="RelationalTypeMapping" /> associated with the expression.</param>
public IsDistinctFromExpression(
SqlExpression left,
SqlExpression right,
RelationalTypeMapping? typeMapping)
: base(typeof(bool), typeMapping)
{
Left = left;
Right = right;
}

/// <summary>
/// The left operand.
/// </summary>
public virtual SqlExpression Left { get; }

/// <summary>
/// The right operand.
/// </summary>
public virtual SqlExpression Right { get; }

/// <inheritdoc />
protected override Expression VisitChildren(ExpressionVisitor visitor)
{
var left = (SqlExpression)visitor.Visit(Left);
var right = (SqlExpression)visitor.Visit(Right);

return Update(left, right);
}

/// <summary>
/// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
/// return this expression.
/// </summary>
/// <param name="left">The <see cref="Left" /> property of the result.</param>
/// <param name="right">The <see cref="Right" /> property of the result.</param>
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
public virtual IsDistinctFromExpression Update(SqlExpression left, SqlExpression right)
=> left != Left || right != Right
? new IsDistinctFromExpression(left, right, TypeMapping)
: this;

/// <inheritdoc />
public override Expression Quote()
=> New(
_quotingConstructor ??= typeof(IsDistinctFromExpression).GetConstructor(
[typeof(SqlExpression), typeof(SqlExpression), typeof(RelationalTypeMapping)])!,
Left.Quote(),
Right.Quote(),
RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping));

/// <inheritdoc />
protected override void Print(ExpressionPrinter expressionPrinter)
{
var requiresBrackets = RequiresBrackets(Left);

if (requiresBrackets)
{
expressionPrinter.Append("(");
}

expressionPrinter.Visit(Left);

if (requiresBrackets)
{
expressionPrinter.Append(")");
}

expressionPrinter.Append(" IS DISTINCT FROM ");

requiresBrackets = RequiresBrackets(Right);

if (requiresBrackets)
{
expressionPrinter.Append("(");
}

expressionPrinter.Visit(Right);

if (requiresBrackets)
{
expressionPrinter.Append(")");
}

static bool RequiresBrackets(SqlExpression expression)
=> expression is SqlBinaryExpression or LikeExpression or InExpression;
}

/// <inheritdoc />
public override bool Equals(object? obj)
=> obj != null
&& (ReferenceEquals(this, obj)
|| obj is IsDistinctFromExpression isExpression
&& Equals(isExpression));

private bool Equals(IsDistinctFromExpression isExpression)
=> base.Equals(isExpression)
&& Left.Equals(isExpression.Left)
&& Right.Equals(isExpression.Right);

/// <inheritdoc />
public override int GetHashCode()
=> HashCode.Combine(base.GetHashCode(), Left, Right);
}
Loading