From cc8e0b55648504ac29714aa255ce8c28c11c412f Mon Sep 17 00:00:00 2001 From: maumar Date: Wed, 1 Jul 2020 02:01:46 -0700 Subject: [PATCH] Fix to #20003 - Convert multiple equality/in-equality on same column joined by Or/Else into SQL IN expression Various optimizations to queries with IN: - a == X || a == Y -> a IN (X, Y) - a != X && a != Y -> a NOT IN (X, Y) - a IN (X) -> a == X - a IN (X, Y) || a IN (Z, W) -> a IN (X, Y, Z, W) - a IN (X, Y, Z) && a IN (Y, Z, W) -> a IN (Y, Z) Fixes #20003 --- .../CaseSimplifyingExpressionVisitor.cs | 216 ---------- ...lExpressionSimplifyingExpressionVisitor.cs | 399 ++++++++++++++++++ ...RelationalQueryTranslationPostprocessor.cs | 2 +- .../Query/SqlNullabilityProcessor.cs | 35 +- .../Query/NorthwindWhereQueryTestBase.cs | 148 +++++++ .../ComplexNavigationsQuerySqlServerTest.cs | 6 +- .../Query/GearsOfWarQuerySqlServerTest.cs | 10 +- ...eteMappingInheritanceQuerySqlServerTest.cs | 6 +- ...indAggregateOperatorsQuerySqlServerTest.cs | 16 +- .../NorthwindCompiledQuerySqlServerTest.cs | 4 +- .../NorthwindGroupByQuerySqlServerTest.cs | 4 +- .../NorthwindIncludeQuerySqlServerTest.cs | 10 +- ...orthwindMiscellaneousQuerySqlServerTest.cs | 32 +- .../NorthwindNavigationsQuerySqlServerTest.cs | 6 +- ...NorthwindSplitIncludeQuerySqlServerTest.cs | 18 +- .../Query/NorthwindWhereQuerySqlServerTest.cs | 150 ++++++- .../Query/NullSemanticsQuerySqlServerTest.cs | 14 +- .../Query/QueryBugsTest.cs | 4 +- .../QueryFilterFuncletizationSqlServerTest.cs | 2 +- 19 files changed, 787 insertions(+), 295 deletions(-) delete mode 100644 src/EFCore.Relational/Query/Internal/CaseSimplifyingExpressionVisitor.cs create mode 100644 src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs diff --git a/src/EFCore.Relational/Query/Internal/CaseSimplifyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/CaseSimplifyingExpressionVisitor.cs deleted file mode 100644 index 8df3d569cbf..00000000000 --- a/src/EFCore.Relational/Query/Internal/CaseSimplifyingExpressionVisitor.cs +++ /dev/null @@ -1,216 +0,0 @@ -// 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; -using System.Linq.Expressions; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Query.Internal -{ - /// - /// 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. - /// - public class CaseSimplifyingExpressionVisitor : ExpressionVisitor - { - private readonly ISqlExpressionFactory _sqlExpressionFactory; - - /// - /// 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. - /// - public CaseSimplifyingExpressionVisitor([NotNull] ISqlExpressionFactory sqlExpressionFactory) - { - _sqlExpressionFactory = sqlExpressionFactory; - } - - /// - /// 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. - /// - protected override Expression VisitExtension(Expression extensionExpression) - { - Check.NotNull(extensionExpression, nameof(extensionExpression)); - - if (extensionExpression is ShapedQueryExpression shapedQueryExpression) - { - return shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression); - } - - // Only applies to 'CASE WHEN condition...' not 'CASE operand WHEN...' - if (extensionExpression is CaseExpression caseExpression - && caseExpression.Operand == null - && caseExpression.ElseResult is CaseExpression nestedCaseExpression - && nestedCaseExpression.Operand == null) - { - return VisitExtension(_sqlExpressionFactory.Case( - caseExpression.WhenClauses.Union(nestedCaseExpression.WhenClauses).ToList(), - nestedCaseExpression.ElseResult)); - } - - if (extensionExpression is SqlBinaryExpression sqlBinaryExpression) - { - var sqlConstantComponent = sqlBinaryExpression.Left as SqlConstantExpression ?? sqlBinaryExpression.Right as SqlConstantExpression; - var caseComponent = sqlBinaryExpression.Left as CaseExpression ?? sqlBinaryExpression.Right as CaseExpression; - - // generic CASE statement comparison optimization: - // (CASE - // WHEN condition1 THEN result1 - // WHEN condition2 THEN result2 - // WHEN ... - // WHEN conditionN THEN resultN) == result1 -> condition1 - if (sqlBinaryExpression.OperatorType == ExpressionType.Equal - && sqlConstantComponent != null - && sqlConstantComponent.Value != null - && caseComponent != null - && caseComponent.Operand == null) - { - var matchingCaseBlock = caseComponent.WhenClauses.FirstOrDefault(wc => sqlConstantComponent.Equals(wc.Result)); - if (matchingCaseBlock != null) - { - return Visit(matchingCaseBlock.Test); - } - } - - // CompareTo specific optimizations - if (sqlConstantComponent != null - && IsCompareTo(caseComponent) - && sqlConstantComponent.Value is int intValue - && (intValue > -2 && intValue < 2) - && (sqlBinaryExpression.OperatorType == ExpressionType.NotEqual - || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThan - || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThanOrEqual - || sqlBinaryExpression.OperatorType == ExpressionType.LessThan - || sqlBinaryExpression.OperatorType == ExpressionType.LessThanOrEqual)) - { - return OptimizeCompareTo( - sqlBinaryExpression, - intValue, - caseComponent); - } - } - - return base.VisitExtension(extensionExpression); - } - - private bool IsCompareTo(CaseExpression caseExpression) - { - if (caseExpression != null - && caseExpression.Operand == null - && caseExpression.ElseResult == null - && caseExpression.WhenClauses.Count == 3 - && caseExpression.WhenClauses.All(c => c.Test is SqlBinaryExpression - && c.Result is SqlConstantExpression constant - && constant.Value is int)) - { - var whenClauses = caseExpression.WhenClauses.Select(c => new - { - test = (SqlBinaryExpression)c.Test, - resultValue = (int)((SqlConstantExpression)c.Result).Value - }).ToList(); - - if (whenClauses[0].test.Left.Equals(whenClauses[1].test.Left) - && whenClauses[1].test.Left.Equals(whenClauses[2].test.Left) - && whenClauses[0].test.Right.Equals(whenClauses[1].test.Right) - && whenClauses[1].test.Right.Equals(whenClauses[2].test.Right) - && whenClauses[0].test.OperatorType == ExpressionType.Equal - && whenClauses[1].test.OperatorType == ExpressionType.GreaterThan - && whenClauses[2].test.OperatorType == ExpressionType.LessThan - && whenClauses[0].resultValue == 0 - && whenClauses[1].resultValue == 1 - && whenClauses[2].resultValue == -1) - { - return true; - } - } - - return false; - } - - private SqlExpression OptimizeCompareTo( - SqlBinaryExpression sqlBinaryExpression, - int intValue, - CaseExpression caseExpression) - { - var testLeft = ((SqlBinaryExpression)caseExpression.WhenClauses[0].Test).Left; - var testRight = ((SqlBinaryExpression)caseExpression.WhenClauses[0].Test).Right; - var operatorType = sqlBinaryExpression.Right is SqlConstantExpression - ? sqlBinaryExpression.OperatorType - : sqlBinaryExpression.OperatorType switch - { - ExpressionType.GreaterThan => ExpressionType.LessThan, - ExpressionType.GreaterThanOrEqual => ExpressionType.LessThanOrEqual, - ExpressionType.LessThan => ExpressionType.GreaterThan, - ExpressionType.LessThanOrEqual => ExpressionType.GreaterThanOrEqual, - _ => sqlBinaryExpression.OperatorType - }; - - switch (operatorType) - { - // CompareTo(a, b) != 0 -> a != b - // CompareTo(a, b) != 1 -> a <= b - // CompareTo(a, b) != -1 -> a >= b - case ExpressionType.NotEqual: - return (SqlExpression)Visit(intValue switch - { - 0 => _sqlExpressionFactory.NotEqual(testLeft, testRight), - 1 => _sqlExpressionFactory.LessThanOrEqual(testLeft, testRight), - _ => _sqlExpressionFactory.GreaterThanOrEqual(testLeft, testRight), - }); - - // CompareTo(a, b) > 0 -> a > b - // CompareTo(a, b) > 1 -> false - // CompareTo(a, b) > -1 -> a >= b - case ExpressionType.GreaterThan: - return (SqlExpression)Visit(intValue switch - { - 0 => _sqlExpressionFactory.GreaterThan(testLeft, testRight), - 1 => _sqlExpressionFactory.Constant(false, sqlBinaryExpression.TypeMapping), - _ => _sqlExpressionFactory.GreaterThanOrEqual(testLeft, testRight), - }); - - // CompareTo(a, b) >= 0 -> a >= b - // CompareTo(a, b) >= 1 -> a > b - // CompareTo(a, b) >= -1 -> true - case ExpressionType.GreaterThanOrEqual: - return (SqlExpression)Visit(intValue switch - { - 0 => _sqlExpressionFactory.GreaterThanOrEqual(testLeft, testRight), - 1 => _sqlExpressionFactory.GreaterThan(testLeft, testRight), - _ => _sqlExpressionFactory.Constant(true, sqlBinaryExpression.TypeMapping), - }); - - // CompareTo(a, b) < 0 -> a < b - // CompareTo(a, b) < 1 -> a <= b - // CompareTo(a, b) < -1 -> false - case ExpressionType.LessThan: - return (SqlExpression)Visit(intValue switch - { - 0 => _sqlExpressionFactory.LessThan(testLeft, testRight), - 1 => _sqlExpressionFactory.LessThanOrEqual(testLeft, testRight), - _ => _sqlExpressionFactory.Constant(false, sqlBinaryExpression.TypeMapping), - }); - - // operatorType == ExpressionType.LessThanOrEqual - // CompareTo(a, b) <= 0 -> a <= b - // CompareTo(a, b) <= 1 -> true - // CompareTo(a, b) <= -1 -> a < b - default: - return (SqlExpression)Visit(intValue switch - { - 0 => _sqlExpressionFactory.LessThanOrEqual(testLeft, testRight), - 1 => _sqlExpressionFactory.Constant(true, sqlBinaryExpression.TypeMapping), - _ => _sqlExpressionFactory.LessThan(testLeft, testRight), - }); - }; - } - } -} diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs new file mode 100644 index 00000000000..445c4c81397 --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs @@ -0,0 +1,399 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + /// + /// 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. + /// + public class SqlExpressionSimplifyingExpressionVisitor : ExpressionVisitor + { + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// 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. + /// + public SqlExpressionSimplifyingExpressionVisitor([NotNull] ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// 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. + /// + protected override Expression VisitExtension(Expression extensionExpression) + { + Check.NotNull(extensionExpression, nameof(extensionExpression)); + + if (extensionExpression is ShapedQueryExpression shapedQueryExpression) + { + return shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression); + } + + // Only applies to 'CASE WHEN condition...' not 'CASE operand WHEN...' + if (extensionExpression is CaseExpression caseExpression + && caseExpression.Operand == null + && caseExpression.ElseResult is CaseExpression nestedCaseExpression + && nestedCaseExpression.Operand == null) + { + return VisitExtension(_sqlExpressionFactory.Case( + caseExpression.WhenClauses.Union(nestedCaseExpression.WhenClauses).ToList(), + nestedCaseExpression.ElseResult)); + } + + if (extensionExpression is SqlBinaryExpression sqlBinaryExpression) + { + return SimplifySqlBinary(sqlBinaryExpression); + } + + return base.VisitExtension(extensionExpression); + } + + private bool IsCompareTo(CaseExpression caseExpression) + { + if (caseExpression != null + && caseExpression.Operand == null + && caseExpression.ElseResult == null + && caseExpression.WhenClauses.Count == 3 + && caseExpression.WhenClauses.All(c => c.Test is SqlBinaryExpression + && c.Result is SqlConstantExpression constant + && constant.Value is int)) + { + var whenClauses = caseExpression.WhenClauses.Select(c => new + { + test = (SqlBinaryExpression)c.Test, + resultValue = (int)((SqlConstantExpression)c.Result).Value + }).ToList(); + + if (whenClauses[0].test.Left.Equals(whenClauses[1].test.Left) + && whenClauses[1].test.Left.Equals(whenClauses[2].test.Left) + && whenClauses[0].test.Right.Equals(whenClauses[1].test.Right) + && whenClauses[1].test.Right.Equals(whenClauses[2].test.Right) + && whenClauses[0].test.OperatorType == ExpressionType.Equal + && whenClauses[1].test.OperatorType == ExpressionType.GreaterThan + && whenClauses[2].test.OperatorType == ExpressionType.LessThan + && whenClauses[0].resultValue == 0 + && whenClauses[1].resultValue == 1 + && whenClauses[2].resultValue == -1) + { + return true; + } + } + + return false; + } + + private SqlExpression OptimizeCompareTo( + SqlBinaryExpression sqlBinaryExpression, + int intValue, + CaseExpression caseExpression) + { + var testLeft = ((SqlBinaryExpression)caseExpression.WhenClauses[0].Test).Left; + var testRight = ((SqlBinaryExpression)caseExpression.WhenClauses[0].Test).Right; + var operatorType = sqlBinaryExpression.Right is SqlConstantExpression + ? sqlBinaryExpression.OperatorType + : sqlBinaryExpression.OperatorType switch + { + ExpressionType.GreaterThan => ExpressionType.LessThan, + ExpressionType.GreaterThanOrEqual => ExpressionType.LessThanOrEqual, + ExpressionType.LessThan => ExpressionType.GreaterThan, + ExpressionType.LessThanOrEqual => ExpressionType.GreaterThanOrEqual, + _ => sqlBinaryExpression.OperatorType + }; + + switch (operatorType) + { + // CompareTo(a, b) != 0 -> a != b + // CompareTo(a, b) != 1 -> a <= b + // CompareTo(a, b) != -1 -> a >= b + case ExpressionType.NotEqual: + return (SqlExpression)Visit(intValue switch + { + 0 => _sqlExpressionFactory.NotEqual(testLeft, testRight), + 1 => _sqlExpressionFactory.LessThanOrEqual(testLeft, testRight), + _ => _sqlExpressionFactory.GreaterThanOrEqual(testLeft, testRight), + }); + + // CompareTo(a, b) > 0 -> a > b + // CompareTo(a, b) > 1 -> false + // CompareTo(a, b) > -1 -> a >= b + case ExpressionType.GreaterThan: + return (SqlExpression)Visit(intValue switch + { + 0 => _sqlExpressionFactory.GreaterThan(testLeft, testRight), + 1 => _sqlExpressionFactory.Constant(false, sqlBinaryExpression.TypeMapping), + _ => _sqlExpressionFactory.GreaterThanOrEqual(testLeft, testRight), + }); + + // CompareTo(a, b) >= 0 -> a >= b + // CompareTo(a, b) >= 1 -> a > b + // CompareTo(a, b) >= -1 -> true + case ExpressionType.GreaterThanOrEqual: + return (SqlExpression)Visit(intValue switch + { + 0 => _sqlExpressionFactory.GreaterThanOrEqual(testLeft, testRight), + 1 => _sqlExpressionFactory.GreaterThan(testLeft, testRight), + _ => _sqlExpressionFactory.Constant(true, sqlBinaryExpression.TypeMapping), + }); + + // CompareTo(a, b) < 0 -> a < b + // CompareTo(a, b) < 1 -> a <= b + // CompareTo(a, b) < -1 -> false + case ExpressionType.LessThan: + return (SqlExpression)Visit(intValue switch + { + 0 => _sqlExpressionFactory.LessThan(testLeft, testRight), + 1 => _sqlExpressionFactory.LessThanOrEqual(testLeft, testRight), + _ => _sqlExpressionFactory.Constant(false, sqlBinaryExpression.TypeMapping), + }); + + // operatorType == ExpressionType.LessThanOrEqual + // CompareTo(a, b) <= 0 -> a <= b + // CompareTo(a, b) <= 1 -> true + // CompareTo(a, b) <= -1 -> a < b + default: + return (SqlExpression)Visit(intValue switch + { + 0 => _sqlExpressionFactory.LessThanOrEqual(testLeft, testRight), + 1 => _sqlExpressionFactory.Constant(true, sqlBinaryExpression.TypeMapping), + _ => _sqlExpressionFactory.LessThan(testLeft, testRight), + }); + }; + } + + private Expression SimplifySqlBinary(SqlBinaryExpression sqlBinaryExpression) + { + var sqlConstantComponent = sqlBinaryExpression.Left as SqlConstantExpression ?? sqlBinaryExpression.Right as SqlConstantExpression; + var caseComponent = sqlBinaryExpression.Left as CaseExpression ?? sqlBinaryExpression.Right as CaseExpression; + + // generic CASE statement comparison optimization: + // (CASE + // WHEN condition1 THEN result1 + // WHEN condition2 THEN result2 + // WHEN ... + // WHEN conditionN THEN resultN) == result1 -> condition1 + if (sqlBinaryExpression.OperatorType == ExpressionType.Equal + && sqlConstantComponent != null + && sqlConstantComponent.Value != null + && caseComponent != null + && caseComponent.Operand == null) + { + var matchingCaseBlock = caseComponent.WhenClauses.FirstOrDefault(wc => sqlConstantComponent.Equals(wc.Result)); + if (matchingCaseBlock != null) + { + return Visit(matchingCaseBlock.Test); + } + } + + // CompareTo specific optimizations + if (sqlConstantComponent != null + && IsCompareTo(caseComponent) + && sqlConstantComponent.Value is int intValue + && (intValue > -2 && intValue < 2) + && (sqlBinaryExpression.OperatorType == ExpressionType.NotEqual + || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThan + || sqlBinaryExpression.OperatorType == ExpressionType.GreaterThanOrEqual + || sqlBinaryExpression.OperatorType == ExpressionType.LessThan + || sqlBinaryExpression.OperatorType == ExpressionType.LessThanOrEqual)) + { + return OptimizeCompareTo( + sqlBinaryExpression, + intValue, + caseComponent); + } + + var left = (SqlExpression)Visit(sqlBinaryExpression.Left); + var right = (SqlExpression)Visit(sqlBinaryExpression.Right); + + if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso + || sqlBinaryExpression.OperatorType == ExpressionType.OrElse) + { + var leftCandidateInfo = GetInExressionCandidateInfo(left); + var rightCandidateInfo = GetInExressionCandidateInfo(right); + if (leftCandidateInfo.optimizeCandidate && rightCandidateInfo.optimizeCandidate + && leftCandidateInfo.columnExpression == rightCandidateInfo.columnExpression + && leftCandidateInfo.operationType == rightCandidateInfo.operationType) + { + var leftConstantIsEnumerable = leftCandidateInfo.constantExpression.Value is IEnumerable + && !(leftCandidateInfo.constantExpression.Value is string) + && !(leftCandidateInfo.constantExpression.Value is byte[]); + + var rightConstantIsEnumerable = rightCandidateInfo.constantExpression.Value is IEnumerable + && !(rightCandidateInfo.constantExpression.Value is string) + && !(rightCandidateInfo.constantExpression.Value is byte[]); + + if ((leftCandidateInfo.operationType == ExpressionType.Equal && sqlBinaryExpression.OperatorType == ExpressionType.OrElse) + || (leftCandidateInfo.operationType == ExpressionType.NotEqual && sqlBinaryExpression.OperatorType == ExpressionType.AndAlso)) + { + object leftValue; + object rightValue; + List resultArray; + + if (!leftConstantIsEnumerable && !rightConstantIsEnumerable) + { + // comparison + comparison + leftValue = leftCandidateInfo.constantExpression.Value; + rightValue = rightCandidateInfo.constantExpression.Value; + + resultArray = ConstructCollection(leftValue, rightValue); + } + else if (leftConstantIsEnumerable && rightConstantIsEnumerable) + { + // in + in + leftValue = leftCandidateInfo.constantExpression.Value; + rightValue = rightCandidateInfo.constantExpression.Value; + resultArray = UnionCollections((IEnumerable)leftValue, (IEnumerable)rightValue); + } + else + { + // in + comparison + leftValue = leftConstantIsEnumerable + ? leftCandidateInfo.constantExpression.Value + : rightCandidateInfo.constantExpression.Value; + + rightValue = leftConstantIsEnumerable + ? rightCandidateInfo.constantExpression.Value + : leftCandidateInfo.constantExpression.Value; + + resultArray = AddToCollection((IEnumerable)leftValue, rightValue); + } + + return _sqlExpressionFactory.In( + leftCandidateInfo.columnExpression, + _sqlExpressionFactory.Constant(resultArray, leftCandidateInfo.constantExpression.TypeMapping), + leftCandidateInfo.operationType == ExpressionType.NotEqual); + } + else if (leftConstantIsEnumerable && rightConstantIsEnumerable) + { + // a IN (1, 2, 3) && a IN (2, 3, 4) -> a IN (2, 3) + // a NOT IN (1, 2, 3) || a NOT IN (2, 3, 4) -> a NOT IN (2, 3) + var resultArray = IntersectCollections( + (IEnumerable)leftCandidateInfo.constantExpression.Value, + (IEnumerable)rightCandidateInfo.constantExpression.Value); + + return _sqlExpressionFactory.In( + leftCandidateInfo.columnExpression, + _sqlExpressionFactory.Constant(resultArray, leftCandidateInfo.constantExpression.TypeMapping), + leftCandidateInfo.operationType == ExpressionType.NotEqual); + } + } + } + + return sqlBinaryExpression.Update(left, right); + } + + private List ConstructCollection(object left, object right) + => new List { left, right }; + + private List AddToCollection(IEnumerable collection, object newElement) + { + var result = BuildListFromEnumerable(collection); + if (!result.Contains(newElement)) + { + result.Add(newElement); + } + + return result; + } + + private List UnionCollections(IEnumerable first, IEnumerable second) + { + var result = BuildListFromEnumerable(first); + foreach (var collectionElement in second) + { + if (!result.Contains(collectionElement)) + { + result.Add(collectionElement); + } + } + + return result; + } + + private List IntersectCollections(IEnumerable first, IEnumerable second) + { + var firstList = BuildListFromEnumerable(first); + var result = new List(); + + foreach (var collectionElement in second) + { + if (firstList.Contains(collectionElement)) + { + result.Add(collectionElement); + } + } + + return result; + } + + private List BuildListFromEnumerable(IEnumerable collection) + { + List result; + if (collection is List list) + { + result = list; + } + else + { + result = new List(); + foreach (var collectionElement in collection) + { + result.Add(collectionElement); + } + } + + return result; + } + + private (bool optimizeCandidate, ColumnExpression columnExpression, SqlConstantExpression constantExpression, ExpressionType operationType) GetInExressionCandidateInfo(SqlExpression sqlExpression) + { + if (sqlExpression is SqlUnaryExpression sqlUnaryExpression + && sqlUnaryExpression.OperatorType == ExpressionType.Not) + { + var result = GetInExressionCandidateInfo(sqlUnaryExpression.Operand); + if (result.optimizeCandidate) + { + return (result.optimizeCandidate, result.columnExpression, result.constantExpression, result.operationType == ExpressionType.Equal ? ExpressionType.NotEqual : ExpressionType.Equal); + } + } + else if (sqlExpression is SqlBinaryExpression sqlBinaryExpression + && (sqlBinaryExpression.OperatorType == ExpressionType.Equal + || sqlBinaryExpression.OperatorType == ExpressionType.NotEqual)) + { + var column = sqlBinaryExpression.Left as ColumnExpression ?? sqlBinaryExpression.Right as ColumnExpression; + var constant = sqlBinaryExpression.Left as SqlConstantExpression ?? sqlBinaryExpression.Right as SqlConstantExpression; + + if (column != null && constant != null) + { + return (true, column, constant, sqlBinaryExpression.OperatorType); + } + } + else if (sqlExpression is InExpression inExpression + && inExpression.Item is ColumnExpression column + && inExpression.Subquery == null + && inExpression.Values is SqlConstantExpression valuesConstant) + { + return (true, column, valuesConstant, inExpression.IsNegated ? ExpressionType.NotEqual : ExpressionType.Equal); + } + + return (false, default, default, default); + } + } +} diff --git a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs index 8c1f6b89e06..92802a0e0f3 100644 --- a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs @@ -42,7 +42,7 @@ public override Expression Process(Expression query) query = new SelectExpressionProjectionApplyingExpressionVisitor().Visit(query); query = new CollectionJoinApplyingExpressionVisitor((RelationalQueryCompilationContext)QueryCompilationContext).Visit(query); query = new TableAliasUniquifyingExpressionVisitor().Visit(query); - query = new CaseSimplifyingExpressionVisitor(RelationalDependencies.SqlExpressionFactory).Visit(query); + query = new SqlExpressionSimplifyingExpressionVisitor(RelationalDependencies.SqlExpressionFactory).Visit(query); query = new RelationalValueConverterCompensatingExpressionVisitor(RelationalDependencies.SqlExpressionFactory).Visit(query); #pragma warning disable 618 diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index bbb8af91cd0..85704a9bdfd 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -64,7 +64,7 @@ public SqlNullabilityProcessor( /// A bool value indicating whether the select expression can be cached. /// An optimized select expression. public virtual SelectExpression Process( - [NotNull] SelectExpression selectExpression, [NotNull] IReadOnlyDictionary parameterValues, out bool canCache) + [NotNull] SelectExpression selectExpression, [NotNull] IReadOnlyDictionary parameterValues, out bool canCache) { Check.NotNull(selectExpression, nameof(selectExpression)); Check.NotNull(parameterValues, nameof(parameterValues)); @@ -560,8 +560,11 @@ protected virtual SqlExpression VisitIn([NotNull] InExpression inExpression, boo nullable = false; return valuesList.Count == 0 - ? (SqlExpression)_sqlExpressionFactory.Constant(false, inExpression.TypeMapping) - : inExpression.Update(item, valuesExpression, subquery: null); + ? _sqlExpressionFactory.Constant(false, inExpression.TypeMapping) + : SimplifyInExpression( + inExpression.Update(item, valuesExpression, subquery: null), + valuesExpression, + valuesList); } // for c# null semantics we need to remove nulls from Values and add IsNull/IsNotNull when necessary @@ -587,6 +590,11 @@ protected virtual SqlExpression VisitIn([NotNull] InExpression inExpression, boo : _sqlExpressionFactory.IsNull(item); } + var simplifiedInExpression = SimplifyInExpression( + inExpression.Update(item, inValuesExpression, subquery: null), + inValuesExpression, + inValuesList); + if (!itemNullable || (allowOptimizedExpansion && !inExpression.IsNegated && !hasNullValue)) { @@ -597,7 +605,7 @@ protected virtual SqlExpression VisitIn([NotNull] InExpression inExpression, boo // non_nullable NOT IN (1, 2) -> non_nullable NOT IN (1, 2) // non_nullable NOT IN (1, 2, NULL) -> non_nullable NOT IN (1, 2) // nullable IN (1, 2) -> nullable IN (1, 2) (optimized) - return inExpression.Update(item, inValuesExpression, subquery: null); + return simplifiedInExpression; } nullable = false; @@ -608,10 +616,10 @@ protected virtual SqlExpression VisitIn([NotNull] InExpression inExpression, boo // nullable NOT IN (1, 2, NULL) -> nullable NOT IN (1, 2) AND nullable IS NOT NULL (full) return inExpression.IsNegated == hasNullValue ? _sqlExpressionFactory.AndAlso( - inExpression.Update(item, inValuesExpression, subquery: null), + simplifiedInExpression, _sqlExpressionFactory.IsNotNull(item)) : _sqlExpressionFactory.OrElse( - inExpression.Update(item, inValuesExpression, subquery: null), + simplifiedInExpression, _sqlExpressionFactory.IsNull(item)); (SqlConstantExpression ProcessedValuesExpression, List ProcessedValuesList, bool HasNullValue) ProcessInExpressionValues(SqlExpression valuesExpression, bool extractNullValues) @@ -648,6 +656,19 @@ protected virtual SqlExpression VisitIn([NotNull] InExpression inExpression, boo return (processedValuesExpression, inValues, hasNullValue); } + + SqlExpression SimplifyInExpression(InExpression inExpression, SqlConstantExpression inValuesExpression, List inValuesList) + { + return inValuesList.Count == 1 + ? inExpression.IsNegated + ? (SqlExpression)_sqlExpressionFactory.NotEqual( + inExpression.Item, + _sqlExpressionFactory.Constant(inValuesList[0], inValuesExpression.TypeMapping)) + : _sqlExpressionFactory.Equal( + inExpression.Item, + _sqlExpressionFactory.Constant(inValuesList[0], inExpression.Values.TypeMapping)) + : inExpression.Update(inExpression.Item, inValuesExpression, subquery: null); + } } /// @@ -920,7 +941,7 @@ protected virtual SqlExpression VisitSqlFunction( return sqlFunctionExpression.IsBuiltIn && string.Equals(sqlFunctionExpression.Name, "SUM", StringComparison.OrdinalIgnoreCase) - ? _sqlExpressionFactory.Coalesce( + ? _sqlExpressionFactory.Coalesce( sqlFunctionExpression.Update(instance, arguments), _sqlExpressionFactory.Constant(0, sqlFunctionExpression.TypeMapping), sqlFunctionExpression.TypeMapping) diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index dc2c782e4a2..aa989d34812 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -2298,5 +2298,153 @@ public virtual Task Where_array_of_object_contains_over_value_type(bool async) .Where(o => orderIds.Contains(o.OrderID)), entryCount: 2); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Multiple_OrElse_on_same_column_converted_to_in_with_overlap(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => c.CustomerID == "ALFKI" || c.CustomerID == "ANATR" || c.CustomerID == "ANTON" || c.CustomerID == "ANATR"), + entryCount: 3); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Multiple_OrElse_on_same_column_with_null_constant_comparison_converted_to_in(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => c.Region == "WA" || c.Region == "OR" || c.Region == null || c.Region == "BC"), + entryCount: 69); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Constant_array_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => new[] { "ALFKI", "ANATR" }.Contains(c.CustomerID) || c.CustomerID == "ANTON"), + entryCount: 3); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Constant_array_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in_with_overlap(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => c.CustomerID == "ANTON" || new[] { "ALFKI", "ANATR" }.Contains(c.CustomerID) || c.CustomerID == "ALFKI"), + entryCount: 3); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Constant_array_Contains_OrElse_another_Contains_gets_combined_to_one_in_with_overlap(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => new[] { "ALFKI", "ANATR" }.Contains(c.CustomerID) || new[] { "ALFKI", "ANTON" }.Contains(c.CustomerID)), + entryCount: 3); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Constant_array_Contains_AndAlso_another_Contains_gets_combined_to_one_in_with_overlap(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => !new[] { "ALFKI", "ANATR" }.Contains(c.CustomerID) && !new[] { "ALFKI", "ANTON" }.Contains(c.CustomerID)), + entryCount: 88); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Multiple_AndAlso_on_same_column_converted_to_in_using_parameters(bool async) + { + var prm1 = "ALFKI"; + var prm2 = "ANATR"; + var prm3 = "ANTON"; + + return AssertQuery( + async, + ss => ss.Set().Where(c => c.CustomerID != prm1 && c.CustomerID != prm2 && c.CustomerID != prm3), + entryCount: 88); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Array_of_parameters_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in(bool async) + { + var prm1 = "ALFKI"; + var prm2 = "ANATR"; + + return AssertQuery( + async, + ss => ss.Set().Where(c => new[] { prm1, prm2 }.Contains(c.CustomerID) || c.CustomerID == "ANTON"), + entryCount: 3); + } + + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Multiple_OrElse_on_same_column_with_null_parameter_comparison_converted_to_in(bool async) + { + string prm = null; + + return AssertQuery( + async, + ss => ss.Set().Where(c => c.Region == "WA" || c.Region == "OR" || c.Region == prm || c.Region == "BC"), + entryCount: 69); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Parameter_array_Contains_OrElse_comparison_with_constant(bool async) + { + var array = new[] { "ALFKI", "ANATR" }; + + return AssertQuery( + async, + ss => ss.Set().Where(c => array.Contains(c.CustomerID) || c.CustomerID == "ANTON"), + entryCount: 3); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Parameter_array_Contains_OrElse_comparison_with_parameter_with_overlap(bool async) + { + var array = new[] { "ALFKI", "ANATR" }; + var prm1 = "ANTON"; + var prm2 = "ALFKI"; + + return AssertQuery( + async, + ss => ss.Set().Where(c => c.CustomerID == prm1 || array.Contains(c.CustomerID) || c.CustomerID == prm2), + entryCount: 3); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Two_sets_of_comparison_combine_correctly(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => new string[] { "ALFKI", "ANATR" }.Contains(c.CustomerID) && (c.CustomerID == "ANATR" || c.CustomerID == "ANTON")), + entryCount: 1); + } + + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Two_sets_of_comparison_combine_correctly2(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => (c.Region != "WA" && c.Region != "OR" && c.Region != null) || (c.Region != "WA" && c.Region != null)), + entryCount: 28); + } + } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index b38cae0d82c..e01b42e2302 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -139,7 +139,7 @@ public override async Task Key_equality_two_conditions_on_same_navigation(bool a @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] FROM [LevelOne] AS [l] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Required_Id] -WHERE ([l0].[Id] = 1) OR ([l0].[Id] = 2)"); +WHERE [l0].[Id] IN (1, 2)"); } public override async Task Key_equality_two_conditions_on_same_navigation2(bool async) @@ -150,7 +150,7 @@ public override async Task Key_equality_two_conditions_on_same_navigation2(bool @"SELECT [l].[Id], [l].[Date], [l].[Level1_Optional_Id], [l].[Level1_Required_Id], [l].[Name], [l].[OneToMany_Optional_Inverse2Id], [l].[OneToMany_Optional_Self_Inverse2Id], [l].[OneToMany_Required_Inverse2Id], [l].[OneToMany_Required_Self_Inverse2Id], [l].[OneToOne_Optional_PK_Inverse2Id], [l].[OneToOne_Optional_Self2Id] FROM [LevelTwo] AS [l] INNER JOIN [LevelOne] AS [l0] ON [l].[Level1_Required_Id] = [l0].[Id] -WHERE ([l0].[Id] = 1) OR ([l0].[Id] = 2)"); +WHERE [l0].[Id] IN (1, 2)"); } public override async Task Multi_level_include_one_to_many_optional_and_one_to_many_optional_produces_valid_sql(bool async) @@ -644,7 +644,7 @@ public override async Task Where_nav_prop_reference_optional1(bool async) @"SELECT [l].[Id] FROM [LevelOne] AS [l] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] -WHERE ([l0].[Name] = N'L2 05') OR ([l0].[Name] = N'L2 07')"); +WHERE [l0].[Name] IN (N'L2 05', N'L2 07')"); } public override async Task Where_nav_prop_reference_optional1_via_DefaultIfEmpty(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index aa765286fa9..b4b5c99c716 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -2281,7 +2281,7 @@ LEFT JOIN ( FROM [Weapons] AS [w] WHERE ([w].[Name] <> N'Lancer') OR [w].[Name] IS NULL ) AS [t] ON [g].[FullName] = [t].[OwnerFullName] -WHERE ([c].[Name] = N'Ephyra') OR ([c].[Name] = N'Hanover') +WHERE [c].[Name] IN (N'Ephyra', N'Hanover') ORDER BY [g].[Nickname], [g].[SquadId], [c].[Name], [t].[Id]"); } @@ -3200,7 +3200,7 @@ public override async Task Contains_on_nullable_array_produces_correct_sql(bool @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] LEFT JOIN [Cities] AS [c] ON [g].[AssignedCityName] = [c].[Name] -WHERE ([g].[SquadId] < 2) AND ([c].[Name] IN (N'Ephyra') OR [c].[Name] IS NULL)"); +WHERE ([g].[SquadId] < 2) AND (([c].[Name] = N'Ephyra') OR [c].[Name] IS NULL)"); } public override async Task Optional_navigation_with_collection_composite_key(bool async) @@ -4647,7 +4647,7 @@ INNER JOIN ( SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[Note], [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank] FROM [Tags] AS [t] LEFT JOIN [Gears] AS [g0] ON ([t].[GearNickName] = [g0].[Nickname]) AND ([t].[GearSquadId] = [g0].[SquadId]) - WHERE ([t].[Note] = N'Cole''s Tag') OR ([t].[Note] = N'Dom''s Tag') + WHERE [t].[Note] IN (N'Cole''s Tag', N'Dom''s Tag') ) AS [t0] ON ([g].[Nickname] = [t0].[Nickname]) AND ([g].[SquadId] = [t0].[SquadId])"); } @@ -5870,7 +5870,7 @@ public override async Task DateTimeOffset_Contains_Less_than_Greater_than(bool a SELECT [m].[Id], [m].[CodeName], [m].[Duration], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] -WHERE ((@__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset)) AND ([m].[Timeline] < @__end_1)) AND [m].[Timeline] IN ('1902-01-02T10:00:00.1234567+01:30')"); +WHERE ((@__start_0 <= CAST(CONVERT(date, [m].[Timeline]) AS datetimeoffset)) AND ([m].[Timeline] < @__end_1)) AND ([m].[Timeline] = '1902-01-02T10:00:00.1234567+01:30')"); } public override async Task Navigation_inside_interpolated_string_expanded(bool async) @@ -7000,7 +7000,7 @@ public override async Task Enum_array_contains(bool async) @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] LEFT JOIN [Weapons] AS [w0] ON [w].[SynergyWithId] = [w0].[Id] -WHERE [w0].[Id] IS NOT NULL AND ([w0].[AmmunitionType] IN (1) OR [w0].[AmmunitionType] IS NULL)"); +WHERE [w0].[Id] IS NOT NULL AND (([w0].[AmmunitionType] = 1) OR [w0].[AmmunitionType] IS NULL)"); } [ConditionalTheory] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs index 18277ce7837..edbff3a2003 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs @@ -144,7 +144,7 @@ public override void Can_use_of_type_bird() AssertSql( @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] FROM [Animal] AS [a] -WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND [a].[Discriminator] IN (N'Eagle', N'Kiwi') +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') ORDER BY [a].[Species]"); } @@ -166,7 +166,7 @@ public override void Can_use_of_type_bird_with_projection() AssertSql( @"SELECT [a].[EagleId] FROM [Animal] AS [a] -WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND [a].[Discriminator] IN (N'Eagle', N'Kiwi')"); +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi')"); } public override void Can_use_of_type_bird_first() @@ -176,7 +176,7 @@ public override void Can_use_of_type_bird_first() AssertSql( @"SELECT TOP(1) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] FROM [Animal] AS [a] -WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND [a].[Discriminator] IN (N'Eagle', N'Kiwi') +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') ORDER BY [a].[Species]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs index 057ebb1cb3a..869d37b29f4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs @@ -736,7 +736,7 @@ WHERE [c].[CustomerID] IN (N'ABCDE', N'ALFKI')", // @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IN (N'ABCDE')"); +WHERE [c].[CustomerID] = N'ABCDE'"); } public override async Task Contains_with_subquery_and_local_array_closure(bool async) @@ -756,7 +756,7 @@ FROM [Customers] AS [c] WHERE EXISTS ( SELECT 1 FROM [Customers] AS [c0] - WHERE [c0].[City] IN (N'London') AND ([c0].[CustomerID] = [c].[CustomerID]))"); + WHERE ([c0].[City] = N'London') AND ([c0].[CustomerID] = [c].[CustomerID]))"); } public override async Task Contains_with_local_uint_array_closure(bool async) @@ -770,7 +770,7 @@ WHERE [e].[EmployeeID] IN (0, 1)", // @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE [e].[EmployeeID] IN (0)"); +WHERE [e].[EmployeeID] = 0"); } public override async Task Contains_with_local_nullable_uint_array_closure(bool async) @@ -784,7 +784,7 @@ WHERE [e].[EmployeeID] IN (0, 1)", // @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE [e].[EmployeeID] IN (0)"); +WHERE [e].[EmployeeID] = 0"); } public override async Task Contains_with_local_array_inline(bool async) @@ -892,7 +892,7 @@ public override async Task Contains_with_local_collection_complex_predicate_and( AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE (([c].[CustomerID] = N'ALFKI') OR ([c].[CustomerID] = N'ABCDE')) AND [c].[CustomerID] IN (N'ABCDE', N'ALFKI')"); +WHERE [c].[CustomerID] IN (N'ALFKI', N'ABCDE') AND [c].[CustomerID] IN (N'ABCDE', N'ALFKI')"); } public override async Task Contains_with_local_collection_complex_predicate_or(bool async) @@ -935,7 +935,7 @@ public override async Task Contains_with_local_collection_sql_injection(bool asy AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IN (N'ALFKI', N'ABC'')); GO; DROP TABLE Orders; GO; --') OR (([c].[CustomerID] = N'ALFKI') OR ([c].[CustomerID] = N'ABCDE'))"); +WHERE [c].[CustomerID] IN (N'ALFKI', N'ABC'')); GO; DROP TABLE Orders; GO; --') OR [c].[CustomerID] IN (N'ALFKI', N'ABCDE')"); } public override async Task Contains_with_local_collection_empty_closure(bool async) @@ -1152,7 +1152,7 @@ public override async Task HashSet_Contains_with_parameter(bool async) AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IN (N'ALFKI')"); +WHERE [c].[CustomerID] = N'ALFKI'"); } public override async Task ImmutableHashSet_Contains_with_parameter(bool async) @@ -1162,7 +1162,7 @@ public override async Task ImmutableHashSet_Contains_with_parameter(bool async) AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IN (N'ALFKI')"); +WHERE [c].[CustomerID] = N'ALFKI'"); } public override async Task Contains_over_entityType_with_null_should_rewrite_to_false(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs index 80eeda3c39d..fdc4a3749ea 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindCompiledQuerySqlServerTest.cs @@ -143,11 +143,11 @@ public override void Query_with_contains() AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IN (N'ALFKI')", +WHERE [c].[CustomerID] = N'ALFKI'", // @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IN (N'ANATR')"); +WHERE [c].[CustomerID] = N'ANATR'"); } public override void Query_with_closure() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs index 9ac442862a9..9d4e4fb2930 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs @@ -985,7 +985,7 @@ ORDER BY [o].[OrderDate] INNER JOIN ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] - WHERE ([c].[CustomerID] <> N'DRACD') AND ([c].[CustomerID] <> N'FOLKO') + WHERE [c].[CustomerID] NOT IN (N'DRACD', N'FOLKO') ORDER BY [c].[City] OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY ) AS [t0] ON [t].[CustomerID] = [t0].[CustomerID] @@ -1072,7 +1072,7 @@ public override async Task GroupJoin_complex_GroupBy_Aggregate(bool async) FROM ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] - WHERE ([c].[CustomerID] <> N'DRACD') AND ([c].[CustomerID] <> N'FOLKO') + WHERE [c].[CustomerID] NOT IN (N'DRACD', N'FOLKO') ORDER BY [c].[City] OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY ) AS [t] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs index e139c8e16e7..f497718f86a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs @@ -1087,13 +1087,13 @@ public override async Task Include_collection_OrderBy_list_contains(bool async) SELECT [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], CASE - WHEN [c].[CustomerID] IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] = N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [c] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%' ORDER BY CASE - WHEN [c].[CustomerID] IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] = N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END OFFSET @__p_1 ROWS @@ -1112,13 +1112,13 @@ public override async Task Include_collection_OrderBy_list_does_not_contains(boo SELECT [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], CASE - WHEN [c].[CustomerID] NOT IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] <> N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [c] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%' ORDER BY CASE - WHEN [c].[CustomerID] NOT IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] <> N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END OFFSET @__p_1 ROWS @@ -1138,7 +1138,7 @@ ELSE CAST(0 AS bit) END, [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title], [e0].[EmployeeID], [e0].[City], [e0].[Country], [e0].[FirstName], [e0].[ReportsTo], [e0].[Title] FROM [Employees] AS [e] LEFT JOIN [Employees] AS [e0] ON [e].[ReportsTo] = [e0].[EmployeeID] -WHERE ([e].[EmployeeID] = 1) OR ([e].[EmployeeID] = 2) +WHERE [e].[EmployeeID] IN (1, 2) ORDER BY [e].[EmployeeID]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 525c1f8bf6e..603118d2b05 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -1545,7 +1545,7 @@ public override async Task Where_select_many_or2(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], [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Customers] AS [c] CROSS JOIN [Employees] AS [e] -WHERE ([c].[City] = N'London') OR ([c].[City] = N'Berlin')"); +WHERE [c].[City] IN (N'London', N'Berlin')"); } public override async Task Where_select_many_or3(bool async) @@ -1556,7 +1556,7 @@ public override async Task Where_select_many_or3(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], [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Customers] AS [c] CROSS JOIN [Employees] AS [e] -WHERE (([c].[City] = N'London') OR ([c].[City] = N'Berlin')) OR ([c].[City] = N'Seattle')"); +WHERE [c].[City] IN (N'London', N'Berlin', N'Seattle')"); } public override async Task Where_select_many_or4(bool async) @@ -1567,7 +1567,7 @@ public override async Task Where_select_many_or4(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], [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Customers] AS [c] CROSS JOIN [Employees] AS [e] -WHERE ((([c].[City] = N'London') OR ([c].[City] = N'Berlin')) OR ([c].[City] = N'Seattle')) OR ([c].[City] = N'Lisboa')"); +WHERE [c].[City] IN (N'London', N'Berlin', N'Seattle', N'Lisboa')"); } public override async Task Where_select_many_or_with_parameter(bool async) @@ -2530,10 +2530,10 @@ public override async Task Select_null_coalesce_operator(bool async) await base.Select_null_coalesce_operator(async); // issue #16038 -// AssertSql( -// @"SELECT [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [Region] -//FROM [Customers] AS [c] -//ORDER BY [Region], [c].[CustomerID]"); + // AssertSql( + // @"SELECT [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [Region] + //FROM [Customers] AS [c] + //ORDER BY [Region], [c].[CustomerID]"); } public override async Task OrderBy_conditional_operator(bool async) @@ -2640,12 +2640,12 @@ public override async Task Select_take_null_coalesce_operator(bool async) await base.Select_take_null_coalesce_operator(async); // issue #16038 -// AssertSql( -// @"@__p_0='5' + // AssertSql( + // @"@__p_0='5' -//SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [Region] -//FROM [Customers] AS [c] -//ORDER BY [Region]"); + //SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [Region] + //FROM [Customers] AS [c] + //ORDER BY [Region]"); } public override async Task Select_take_skip_null_coalesce_operator(bool async) @@ -3621,7 +3621,7 @@ WHERE CONVERT(date, [o].[OrderDate]) IN ('1996-07-04T00:00:00.000', '1996-07-16T // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE CONVERT(date, [o].[OrderDate]) IN ('1996-07-04T00:00:00.000')"); +WHERE CONVERT(date, [o].[OrderDate]) = '1996-07-04T00:00:00.000'"); } public override async Task Contains_with_subquery_involving_join_binds_to_correct_table(bool async) @@ -3896,7 +3896,7 @@ public override async Task Include_with_orderby_skip_preserves_ordering(bool asy FROM ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] - WHERE ([c].[CustomerID] <> N'VAFFE') AND ([c].[CustomerID] <> N'DRACD') + WHERE [c].[CustomerID] NOT IN (N'VAFFE', N'DRACD') ORDER BY [c].[City], [c].[CustomerID] OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY ) AS [t] @@ -5032,7 +5032,7 @@ public override async Task Checked_context_with_case_to_same_nullable_type_does_ public override async Task Entity_equality_with_null_coalesce_client_side(bool async) { - await base.Entity_equality_with_null_coalesce_client_side(async); + await base.Entity_equality_with_null_coalesce_client_side(async); AssertSql( @"@__entity_equality_p_0_CustomerID='ALFKI' (Size = 5) (DbType = StringFixedLength) @@ -5049,7 +5049,7 @@ public override async Task Entity_equality_contains_with_list_of_null(bool async AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IN (N'ALFKI')"); +WHERE [c].[CustomerID] = N'ALFKI'"); } public override async Task MemberInitExpression_NewExpression_is_funcletized_even_when_bindings_are_not_evaluatable(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs index 8b9d13e06b1..03fbc7054d3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs @@ -437,7 +437,7 @@ FROM [Order Details] AS [o] INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] LEFT JOIN [Customers] AS [c] ON [o0].[CustomerID] = [c].[CustomerID] LEFT JOIN [Orders] AS [o1] ON [c].[CustomerID] = [o1].[CustomerID] -WHERE ([o0].[CustomerID] = N'ALFKI') OR ([o0].[CustomerID] = N'ANTON') +WHERE [o0].[CustomerID] IN (N'ALFKI', N'ANTON') ORDER BY [o].[OrderID], [o].[ProductID], [o0].[OrderID], [c].[CustomerID], [o1].[OrderID]"); } @@ -760,7 +760,7 @@ public override async Task Navigation_fk_based_inside_contains(bool async) @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [c].[CustomerID] IN (N'ALFKI')"); +WHERE [c].[CustomerID] = N'ALFKI'"); } public override async Task Navigation_inside_contains(bool async) @@ -854,7 +854,7 @@ SELECT COUNT(*) FROM [Order Details] AS [o0] INNER JOIN [Orders] AS [o1] ON [o0].[OrderID] = [o1].[OrderID] LEFT JOIN [Customers] AS [c0] ON [o1].[CustomerID] = [c0].[CustomerID] - WHERE ([c].[Country] = [c0].[Country]) OR ([c].[Country] IS NULL AND [c0].[Country] IS NULL)) > 0) AND (([o].[OrderID] = 10643) OR ([o].[OrderID] = 10692))"); + WHERE ([c].[Country] = [c0].[Country]) OR ([c].[Country] IS NULL AND [c0].[Country] IS NULL)) > 0) AND [o].[OrderID] IN (10643, 10692)"); } public override async Task Project_single_scalar_value_subquery_is_properly_inlined(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs index 6558463afa9..bb4319a1db9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs @@ -1515,13 +1515,13 @@ public override async Task Include_collection_OrderBy_list_contains(bool async) SELECT [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] FROM ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], CASE - WHEN [c].[CustomerID] IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] = N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [c] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%' ORDER BY CASE - WHEN [c].[CustomerID] IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] = N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END OFFSET @__p_1 ROWS @@ -1533,13 +1533,13 @@ OFFSET @__p_1 ROWS SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [t].[CustomerID] FROM ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], CASE - WHEN [c].[CustomerID] IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] = N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [c] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%' ORDER BY CASE - WHEN [c].[CustomerID] IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] = N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END OFFSET @__p_1 ROWS @@ -1558,13 +1558,13 @@ public override async Task Include_collection_OrderBy_list_does_not_contains(boo SELECT [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] FROM ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], CASE - WHEN [c].[CustomerID] NOT IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] <> N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [c] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%' ORDER BY CASE - WHEN [c].[CustomerID] NOT IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] <> N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END OFFSET @__p_1 ROWS @@ -1576,13 +1576,13 @@ OFFSET @__p_1 ROWS SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [t].[CustomerID] FROM ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], CASE - WHEN [c].[CustomerID] NOT IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] <> N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [c] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%' ORDER BY CASE - WHEN [c].[CustomerID] NOT IN (N'ALFKI') THEN CAST(1 AS bit) + WHEN [c].[CustomerID] <> N'ALFKI' THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END OFFSET @__p_1 ROWS @@ -1602,7 +1602,7 @@ ELSE CAST(0 AS bit) END, [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title], [e0].[EmployeeID], [e0].[City], [e0].[Country], [e0].[FirstName], [e0].[ReportsTo], [e0].[Title] FROM [Employees] AS [e] LEFT JOIN [Employees] AS [e0] ON [e].[ReportsTo] = [e0].[EmployeeID] -WHERE ([e].[EmployeeID] = 1) OR ([e].[EmployeeID] = 2) +WHERE [e].[EmployeeID] IN (1, 2) ORDER BY [e].[EmployeeID]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 2bfab864950..6a7ed6406e6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -950,7 +950,7 @@ public override async Task Where_in_optimization_multiple(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], [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Customers] AS [c] CROSS JOIN [Employees] AS [e] -WHERE ((([c].[City] = N'London') OR ([c].[City] = N'Berlin')) OR ([c].[CustomerID] = N'ALFKI')) OR ([c].[CustomerID] = N'ABCDE')"); +WHERE ([c].[City] IN (N'London', N'Berlin') OR ([c].[CustomerID] = N'ALFKI')) OR ([c].[CustomerID] = N'ABCDE')"); } public override async Task Where_not_in_optimization1(bool async) @@ -972,7 +972,7 @@ public override async Task Where_not_in_optimization2(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], [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Customers] AS [c] CROSS JOIN [Employees] AS [e] -WHERE (([c].[City] <> N'London') OR [c].[City] IS NULL) AND (([c].[City] <> N'Berlin') OR [c].[City] IS NULL)"); +WHERE [c].[City] NOT IN (N'London', N'Berlin') OR [c].[City] IS NULL"); } public override async Task Where_not_in_optimization3(bool async) @@ -983,7 +983,7 @@ public override async Task Where_not_in_optimization3(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], [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Customers] AS [c] CROSS JOIN [Employees] AS [e] -WHERE ((([c].[City] <> N'London') OR [c].[City] IS NULL) AND (([c].[City] <> N'Berlin') OR [c].[City] IS NULL)) AND (([c].[City] <> N'Seattle') OR [c].[City] IS NULL)"); +WHERE [c].[City] NOT IN (N'London', N'Berlin', N'Seattle') OR [c].[City] IS NULL"); } public override async Task Where_not_in_optimization4(bool async) @@ -994,7 +994,7 @@ public override async Task Where_not_in_optimization4(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], [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Customers] AS [c] CROSS JOIN [Employees] AS [e] -WHERE (((([c].[City] <> N'London') OR [c].[City] IS NULL) AND (([c].[City] <> N'Berlin') OR [c].[City] IS NULL)) AND (([c].[City] <> N'Seattle') OR [c].[City] IS NULL)) AND (([c].[City] <> N'Lisboa') OR [c].[City] IS NULL)"); +WHERE [c].[City] NOT IN (N'London', N'Berlin', N'Seattle', N'Lisboa') OR [c].[City] IS NULL"); } public override async Task Where_select_many_and(bool async) @@ -1754,7 +1754,7 @@ public override async Task Generic_Ilist_contains_translates_to_server(bool asyn AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[City] IN (N'Seattle')"); +WHERE [c].[City] = N'Seattle'"); } public override async Task Filter_non_nullable_value_after_FirstOrDefault_on_empty_collection(bool async) @@ -2083,6 +2083,146 @@ FROM [Orders] AS [o] WHERE [o].[OrderID] IN (10248, 10249)"); } + public override async Task Multiple_OrElse_on_same_column_converted_to_in_with_overlap(bool async) + { + await base.Multiple_OrElse_on_same_column_converted_to_in_with_overlap(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] IN (N'ALFKI', N'ANATR', N'ANTON')"); + } + + public override async Task Multiple_OrElse_on_same_column_with_null_constant_comparison_converted_to_in(bool async) + { + await base.Multiple_OrElse_on_same_column_with_null_constant_comparison_converted_to_in(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[Region] IN (N'WA', N'OR', N'BC') OR [c].[Region] IS NULL"); + } + + public override async Task Constant_array_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in(bool async) + { + await base.Constant_array_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] IN (N'ALFKI', N'ANATR', N'ANTON')"); + } + + public override async Task Constant_array_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in_with_overlap(bool async) + { + await base.Constant_array_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in_with_overlap(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] IN (N'ALFKI', N'ANATR', N'ANTON')"); + } + + public override async Task Constant_array_Contains_OrElse_another_Contains_gets_combined_to_one_in_with_overlap(bool async) + { + await base.Constant_array_Contains_OrElse_another_Contains_gets_combined_to_one_in_with_overlap(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] IN (N'ALFKI', N'ANATR', N'ANTON')"); + } + + public override async Task Constant_array_Contains_AndAlso_another_Contains_gets_combined_to_one_in_with_overlap(bool async) + { + await base.Constant_array_Contains_AndAlso_another_Contains_gets_combined_to_one_in_with_overlap(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] NOT IN (N'ALFKI', N'ANATR', N'ANTON')"); + } + + public override async Task Multiple_AndAlso_on_same_column_converted_to_in_using_parameters(bool async) + { + await base.Multiple_AndAlso_on_same_column_converted_to_in_using_parameters(async); + + // issue #21462 + AssertSql( + @"@__prm1_0='ALFKI' (Size = 5) (DbType = StringFixedLength) +@__prm2_1='ANATR' (Size = 5) (DbType = StringFixedLength) +@__prm3_2='ANTON' (Size = 5) (DbType = StringFixedLength) + +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE (([c].[CustomerID] <> @__prm1_0) AND ([c].[CustomerID] <> @__prm2_1)) AND ([c].[CustomerID] <> @__prm3_2)"); + } + + public override async Task Array_of_parameters_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in(bool async) + { + await base.Array_of_parameters_Contains_OrElse_comparison_with_constant_gets_combined_to_one_in(async); + + // issue #21462 + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] IN (N'ALFKI', N'ANATR') OR ([c].[CustomerID] = N'ANTON')"); + } + + public override async Task Multiple_OrElse_on_same_column_with_null_parameter_comparison_converted_to_in(bool async) + { + await base.Multiple_OrElse_on_same_column_with_null_parameter_comparison_converted_to_in(async); + + // issue #21462 + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE ([c].[Region] IN (N'WA', N'OR') OR [c].[Region] IS NULL) OR ([c].[Region] = N'BC')"); + } + + public override async Task Parameter_array_Contains_OrElse_comparison_with_constant(bool async) + { + await base.Parameter_array_Contains_OrElse_comparison_with_constant(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] IN (N'ALFKI', N'ANATR') OR ([c].[CustomerID] = N'ANTON')"); + } + + public override async Task Parameter_array_Contains_OrElse_comparison_with_parameter_with_overlap(bool async) + { + await base.Parameter_array_Contains_OrElse_comparison_with_parameter_with_overlap(async); + + AssertSql( + @"@__prm1_0='ANTON' (Size = 5) (DbType = StringFixedLength) +@__prm2_2='ALFKI' (Size = 5) (DbType = StringFixedLength) + +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE (([c].[CustomerID] = @__prm1_0) OR [c].[CustomerID] IN (N'ALFKI', N'ANATR')) OR ([c].[CustomerID] = @__prm2_2)"); + } + + public override async Task Two_sets_of_comparison_combine_correctly(bool async) + { + await base.Two_sets_of_comparison_combine_correctly(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = N'ANATR'"); + } + + public override async Task Two_sets_of_comparison_combine_correctly2(bool async) + { + await base.Two_sets_of_comparison_combine_correctly2(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE ([c].[Region] <> N'WA') AND [c].[Region] IS NOT NULL"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 2598830f78b..35062fe17f2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -713,7 +713,7 @@ public override async Task Contains_with_local_array_closure_with_null(bool asyn AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[NullableStringA] IN (N'Foo') OR [e].[NullableStringA] IS NULL"); +WHERE ([e].[NullableStringA] = N'Foo') OR [e].[NullableStringA] IS NULL"); } public override async Task Contains_with_local_array_closure_false_with_null(bool async) @@ -723,7 +723,7 @@ public override async Task Contains_with_local_array_closure_false_with_null(boo AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[NullableStringA] NOT IN (N'Foo') AND [e].[NullableStringA] IS NOT NULL"); +WHERE ([e].[NullableStringA] <> N'Foo') AND [e].[NullableStringA] IS NOT NULL"); } public override async Task Contains_with_local_nullable_array_closure_negated(bool async) @@ -733,7 +733,7 @@ public override async Task Contains_with_local_nullable_array_closure_negated(bo AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[NullableStringA] NOT IN (N'Foo') OR [e].[NullableStringA] IS NULL"); +WHERE ([e].[NullableStringA] <> N'Foo') OR [e].[NullableStringA] IS NULL"); } public override async Task Contains_with_local_array_closure_with_multiple_nulls(bool async) @@ -743,7 +743,7 @@ public override async Task Contains_with_local_array_closure_with_multiple_nulls AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE [e].[NullableStringA] IN (N'Foo') OR [e].[NullableStringA] IS NULL"); +WHERE ([e].[NullableStringA] = N'Foo') OR [e].[NullableStringA] IS NULL"); } public override async Task Where_multiple_ors_with_null(bool async) @@ -753,7 +753,7 @@ public override async Task Where_multiple_ors_with_null(bool async) AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE (([e].[NullableStringA] = N'Foo') OR ([e].[NullableStringA] = N'Blah')) OR [e].[NullableStringA] IS NULL"); +WHERE [e].[NullableStringA] IN (N'Foo', N'Blah') OR [e].[NullableStringA] IS NULL"); } public override async Task Where_multiple_ands_with_null(bool async) @@ -763,7 +763,7 @@ public override async Task Where_multiple_ands_with_null(bool async) AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE ((([e].[NullableStringA] <> N'Foo') OR [e].[NullableStringA] IS NULL) AND (([e].[NullableStringA] <> N'Blah') OR [e].[NullableStringA] IS NULL)) AND [e].[NullableStringA] IS NOT NULL"); +WHERE [e].[NullableStringA] NOT IN (N'Foo', N'Blah') AND [e].[NullableStringA] IS NOT NULL"); } public override async Task Where_multiple_ors_with_nullable_parameter(bool async) @@ -1032,7 +1032,7 @@ public override void Where_contains_on_parameter_array_with_just_null_with_relat AssertSql( @"SELECT [e].[NullableStringA] FROM [Entities1] AS [e] -WHERE [e].[NullableStringA] IN (NULL)"); +WHERE [e].[NullableStringA] = NULL"); } public override async Task Where_nullable_bool(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 42b71fa451c..cf934baa43e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -4172,7 +4172,7 @@ public virtual void DateTime_Contains_with_smalldatetime_generates_correct_liter AssertSql( @"SELECT [r].[Id], [r].[MyTime] FROM [ReproEntity] AS [r] -WHERE [r].[MyTime] IN ('2018-10-07T00:00:00')"); +WHERE [r].[MyTime] = '2018-10-07T00:00:00'"); } } @@ -4238,7 +4238,7 @@ public virtual void Nested_contains_with_enum() SELECT [t].[Id], [t].[Type] FROM [Todos] AS [t] WHERE CASE - WHEN [t].[Type] IN (0) THEN @__key_2 + WHEN [t].[Type] = 0 THEN @__key_2 ELSE @__key_2 END IN ('0a47bcb7-a1cb-4345-8944-c58f82d6aac7', '5f221fb9-66f4-442a-92c9-d97ed5989cc7')"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs index 95a6cc94a07..b6634c0ad57 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs @@ -90,7 +90,7 @@ FROM [ListFilter] AS [l] // @"SELECT [l].[Id], [l].[Tenant] FROM [ListFilter] AS [l] -WHERE [l].[Tenant] IN (1)", +WHERE [l].[Tenant] = 1", // @"SELECT [l].[Id], [l].[Tenant] FROM [ListFilter] AS [l]