From 63769c63a4693d1eb8d175b7c8c177298a3c7e0a Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 1 Jun 2023 13:58:22 +0200 Subject: [PATCH] Fixes and improvements to StartsWith/EndsWith/Contains Closes #30493 Closes #11881 Closes #26735 --- .../Query/QuerySqlGenerator.cs | 1 + ...lationalSqlTranslatingExpressionVisitor.cs | 2 +- .../SqlExpressions/SqlParameterExpression.cs | 36 +-- .../Query/SqlNullabilityProcessor.cs | 124 ++++++-- ...qlServerSqlTranslatingExpressionVisitor.cs | 296 +++++++++++++++++- .../SqlServerStringMethodTranslator.cs | 161 ---------- .../SqliteSqlTranslatingExpressionVisitor.cs | 226 +++++++++++++ .../Internal/SqliteStringMethodTranslator.cs | 161 +--------- src/EFCore/DbFunctionsExtensions.cs | 11 +- .../QueryOptimizingExpressionVisitor.cs | 68 +--- .../NorthwindFunctionsQueryCosmosTest.cs | 45 ++- .../NorthwindMiscellaneousQueryCosmosTest.cs | 14 +- .../Query/NorthwindSelectQueryCosmosTest.cs | 14 +- .../Query/NorthwindWhereQueryCosmosTest.cs | 2 +- ...rthwindFunctionsQueryRelationalTestBase.cs | 2 + .../Query/NullSemanticsQueryTestBase.cs | 69 ++++ .../Query/ComplexNavigationsQueryTestBase.cs | 3 + .../Query/FunkyDataQueryTestBase.cs | 146 +++++---- .../Query/NorthwindFunctionsQueryTestBase.cs | 24 ++ .../ExpectedQueryRewritingVisitor.cs | 52 --- .../TestUtilities/TestHelpers.cs | 1 + .../NonSharedModelBulkUpdatesSqlServerTest.cs | 2 +- .../NorthwindBulkUpdatesSqlServerTest.cs | 10 +- ...avigationsCollectionsQuerySqlServerTest.cs | 2 +- ...CollectionsSharedTypeQuerySqlServerTest.cs | 2 +- ...tionsCollectionsSplitQuerySqlServerTest.cs | 4 +- .../ComplexNavigationsQuerySqlServerTest.cs | 10 +- ...NavigationsSharedTypeQuerySqlServerTest.cs | 10 +- .../Query/FunkyDataQuerySqlServerTest.cs | 151 +++++---- .../Query/GearsOfWarQuerySqlServerTest.cs | 6 +- .../ManyToManyNoTrackingQuerySqlServerTest.cs | 6 +- .../Query/ManyToManyQuerySqlServerTest.cs | 6 +- ...indAggregateOperatorsQuerySqlServerTest.cs | 6 +- ...windEFPropertyIncludeQuerySqlServerTest.cs | 14 +- .../NorthwindFunctionsQuerySqlServerTest.cs | 52 ++- .../NorthwindGroupByQuerySqlServerTest.cs | 10 +- ...windIncludeNoTrackingQuerySqlServerTest.cs | 10 +- .../NorthwindIncludeQuerySqlServerTest.cs | 14 +- .../Query/NorthwindJoinQuerySqlServerTest.cs | 6 +- ...orthwindMiscellaneousQuerySqlServerTest.cs | 33 +- .../NorthwindNavigationsQuerySqlServerTest.cs | 4 +- ...NorthwindQueryFiltersQuerySqlServerTest.cs | 96 +++--- .../NorthwindSelectQuerySqlServerTest.cs | 4 +- ...plitIncludeNoTrackingQuerySqlServerTest.cs | 16 +- ...NorthwindSplitIncludeQuerySqlServerTest.cs | 24 +- ...orthwindStringIncludeQuerySqlServerTest.cs | 14 +- .../Query/NullSemanticsQuerySqlServerTest.cs | 91 +++++- .../Query/OperatorsQuerySqlServerTest.cs | 2 +- .../Query/QueryBugsTest.cs | 2 +- .../Query/SimpleQuerySqlServerTest.cs | 6 +- .../Query/TPCGearsOfWarQuerySqlServerTest.cs | 6 +- ...CManyToManyNoTrackingQuerySqlServerTest.cs | 12 +- .../Query/TPCManyToManyQuerySqlServerTest.cs | 14 +- .../Query/TPTGearsOfWarQuerySqlServerTest.cs | 6 +- ...TManyToManyNoTrackingQuerySqlServerTest.cs | 10 +- .../Query/TPTManyToManyQuerySqlServerTest.cs | 10 +- ...avigationsCollectionsQuerySqlServerTest.cs | 2 +- ...CollectionsSharedTypeQuerySqlServerTest.cs | 2 +- .../TemporalGearsOfWarQuerySqlServerTest.cs | 6 +- .../TemporalManyToManyQuerySqlServerTest.cs | 6 +- .../NonSharedModelBulkUpdatesSqliteTest.cs | 2 +- .../NorthwindBulkUpdatesSqliteTest.cs | 4 +- .../Query/FromSqlQuerySqliteTest.cs | 4 +- .../Query/GearsOfWarQuerySqliteTest.cs | 30 +- .../NorthwindFunctionsQuerySqliteTest.cs | 52 ++- .../NorthwindMiscellaneousQuerySqliteTest.cs | 2 +- .../NorthwindQueryFiltersQuerySqliteTest.cs | 4 +- .../Query/OperatorsQuerySqliteTest.cs | 2 +- .../Query/SqlQuerySqliteTest.cs | 4 +- 69 files changed, 1344 insertions(+), 905 deletions(-) diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index a2d0e963fa4..b5981f189a5 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -736,6 +736,7 @@ protected virtual void GenerateLike(LikeExpression likeExpression, bool negated) } _relationalCommandBuilder.Append(" LIKE "); + Visit(likeExpression.Pattern); if (likeExpression.EscapeChar != null) diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 88716980b18..46309019c68 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1029,7 +1029,7 @@ protected override Expression VisitNewArray(NewArrayExpression newArrayExpressio /// protected override Expression VisitParameter(ParameterExpression parameterExpression) => parameterExpression.Name?.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal) == true - ? new SqlParameterExpression(parameterExpression, null) + ? new SqlParameterExpression(parameterExpression.Name, parameterExpression.Type, null) : throw new InvalidOperationException(CoreStrings.TranslationFailed(parameterExpression.Print())); /// diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs index 8540a8646ac..d53f50bbae6 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs @@ -6,32 +6,30 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions; /// /// An expression that represents a parameter in a SQL tree. /// -/// -/// This is a simple wrapper around a in the SQL tree. -/// Instances of this type cannot be constructed by application or database provider code. If this is a problem for your -/// application or provider, then please file an issue at -/// github.com/dotnet/efcore. -/// public sealed class SqlParameterExpression : SqlExpression { - private readonly ParameterExpression _parameterExpression; - private readonly string _name; - - internal SqlParameterExpression(ParameterExpression parameterExpression, RelationalTypeMapping? typeMapping) - : base(parameterExpression.Type.UnwrapNullableType(), typeMapping) + /// + /// Creates a new instance of the class. + /// + /// The parameter name. + /// The of the expression. + /// The associated with the expression. + public SqlParameterExpression(string name, Type type, RelationalTypeMapping? typeMapping) + : this(name, type.UnwrapNullableType(), type.IsNullableType(), typeMapping) { - Check.DebugAssert(parameterExpression.Name != null, "Parameter must have name."); + } - _parameterExpression = parameterExpression; - _name = parameterExpression.Name; - IsNullable = parameterExpression.Type.IsNullableType(); + private SqlParameterExpression(string name, Type type, bool nullable, RelationalTypeMapping? typeMapping) + : base(type, typeMapping) + { + Name = name; + IsNullable = nullable; } /// /// The name of the parameter. /// - public string Name - => _name; + public string Name { get; } /// /// The bool value indicating if this parameter can have null values. @@ -44,7 +42,7 @@ public string Name /// A relational type mapping to apply. /// A new expression which has supplied type mapping. public SqlExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping) - => new SqlParameterExpression(_parameterExpression, typeMapping); + => new SqlParameterExpression(Name, Type, IsNullable, typeMapping); /// protected override Expression VisitChildren(ExpressionVisitor visitor) @@ -52,7 +50,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) /// protected override void Print(ExpressionPrinter expressionPrinter) - => expressionPrinter.Append("@" + _parameterExpression.Name); + => expressionPrinter.Append("@" + Name); /// public override bool Equals(object? obj) diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 90b037fbf93..72dc533b909 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -161,7 +161,7 @@ protected virtual TableExpressionBase Visit(TableExpressionBase tableExpressionB var newTable = Visit(innerJoinExpression.Table); var newJoinPredicate = ProcessJoinPredicate(innerJoinExpression.JoinPredicate); - return TryGetBoolConstantValue(newJoinPredicate) == true + return IsTrue(newJoinPredicate) ? new CrossJoinExpression(newTable) : innerJoinExpression.Update(newTable, newJoinPredicate); } @@ -301,7 +301,7 @@ protected virtual SelectExpression Visit(SelectExpression selectExpression) var predicate = Visit(selectExpression.Predicate, allowOptimizedExpansion: true, out _); changed |= predicate != selectExpression.Predicate; - if (TryGetBoolConstantValue(predicate) == true) + if (IsTrue(predicate)) { predicate = null; changed = true; @@ -333,7 +333,7 @@ protected virtual SelectExpression Visit(SelectExpression selectExpression) var having = Visit(selectExpression.Having, allowOptimizedExpansion: true, out _); changed |= having != selectExpression.Having; - if (TryGetBoolConstantValue(having) == true) + if (IsTrue(having)) { having = null; changed = true; @@ -519,20 +519,17 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al var test = Visit( whenClause.Test, allowOptimizedExpansion: testIsCondition, preserveColumnNullabilityInformation: true, out _); - if (TryGetBoolConstantValue(test) is bool testConstantBool) + if (IsTrue(test)) { - if (testConstantBool) - { - testEvaluatesToTrue = true; - } - else - { - // if test evaluates to 'false' we can remove the WhenClause - RestoreNonNullableColumnsList(currentNonNullableColumnsCount); - RestoreNullValueColumnsList(currentNullValueColumnsCount); + testEvaluatesToTrue = true; + } + else if (IsFalse(test)) + { + // if test evaluates to 'false' we can remove the WhenClause + RestoreNonNullableColumnsList(currentNonNullableColumnsCount); + RestoreNullValueColumnsList(currentNullValueColumnsCount); - continue; - } + continue; } var newResult = Visit(whenClause.Result, out var resultNullable); @@ -570,7 +567,7 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al // if there is only one When clause and it's test evaluates to 'true' AND there is no else block, simply return the result return elseResult == null && whenClauses.Count == 1 - && TryGetBoolConstantValue(whenClauses[0].Test) == true + && IsTrue(whenClauses[0].Test) ? whenClauses[0].Result : caseExpression.Update(operand, whenClauses, elseResult); } @@ -635,7 +632,7 @@ protected virtual SqlExpression VisitExists( // if subquery has predicate which evaluates to false, we can simply return false // if the exists is negated we need to return true instead - return TryGetBoolConstantValue(subquery.Predicate) == false + return IsFalse(subquery.Predicate) ? _sqlExpressionFactory.Constant(false, existsExpression.TypeMapping) : existsExpression.Update(subquery); } @@ -658,7 +655,7 @@ protected virtual SqlExpression VisitIn(InExpression inExpression, bool allowOpt var subquery = Visit(inExpression.Subquery); // a IN (SELECT * FROM table WHERE false) => false - if (TryGetBoolConstantValue(subquery.Predicate) == false) + if (IsFalse(subquery.Predicate)) { nullable = false; @@ -967,9 +964,64 @@ protected virtual SqlExpression VisitLike(LikeExpression likeExpression, bool al var pattern = Visit(likeExpression.Pattern, out var patternNullable); var escapeChar = Visit(likeExpression.EscapeChar, out var escapeCharNullable); - nullable = matchNullable || patternNullable || escapeCharNullable; + SqlExpression result = likeExpression.Update(match, pattern, escapeChar); + + if (UseRelationalNulls) + { + nullable = matchNullable || patternNullable || escapeCharNullable; + + return result; + } + + nullable = false; + + // The null semantics behavior we implement for LIKE is that it only returns true when both sides are non-null and match; any other + // input returns false: + // foo LIKE f% -> true + // foo LIKE null -> false + // null LIKE f% -> false + // null LIKE null -> false + + if (IsNull(match) || IsNull(pattern) || IsNull(escapeChar)) + { + return _sqlExpressionFactory.Constant(false, likeExpression.TypeMapping); + } + + // A constant match-all pattern (%) returns true for all cases, except where the match string is null: + // nullable_foo LIKE % -> foo IS NOT NULL + // non_nullable_foo LIKE % -> true + if (pattern is SqlConstantExpression { Value: "%" }) + { + return matchNullable + ? _sqlExpressionFactory.IsNotNull(match) + : _sqlExpressionFactory.Constant(true, likeExpression.TypeMapping); + } - return likeExpression.Update(match, pattern, escapeChar); + if (!allowOptimizedExpansion) + { + if (matchNullable) + { + result = _sqlExpressionFactory.AndAlso(result, GenerateNotNullCheck(match)); + } + + if (patternNullable) + { + result = _sqlExpressionFactory.AndAlso(result, GenerateNotNullCheck(pattern)); + } + + if (escapeChar is not null && escapeCharNullable) + { + result = _sqlExpressionFactory.AndAlso(result, GenerateNotNullCheck(escapeChar)); + } + } + + return result; + + SqlExpression GenerateNotNullCheck(SqlExpression operand) + => OptimizeNonNullableNotExpression( + _sqlExpressionFactory.Not( + ProcessNullNotNull( + _sqlExpressionFactory.IsNull(operand), operandNullable: true))); } /// @@ -1395,8 +1447,28 @@ protected virtual SqlExpression VisitJsonScalar( /// protected virtual bool PreferExistsToComplexIn => false; - private static bool? TryGetBoolConstantValue(SqlExpression? expression) - => expression is SqlConstantExpression { Value: bool boolValue } ? boolValue : null; + // Note that we can check parameter values for null since we cache by the parameter nullability; but we cannot do the same for bool. + private bool IsNull(SqlExpression? expression) + => expression is SqlConstantExpression { Value: null } + || expression is SqlParameterExpression { Name: string parameterName } && ParameterValues[parameterName] is null; + + private bool IsTrue(SqlExpression? expression) + => expression is SqlConstantExpression { Value: true }; + + private bool IsFalse(SqlExpression? expression) + => expression is SqlConstantExpression { Value: false }; + + private bool TryGetBool(SqlExpression? expression, out bool value) + { + if (expression is SqlConstantExpression { Value: bool b }) + { + value = b; + return true; + } + + value = false; + return false; + } private void RestoreNonNullableColumnsList(int counter) { @@ -1486,7 +1558,7 @@ private SqlExpression OptimizeComparison( return result; } - if (TryGetBoolConstantValue(right) is bool rightBoolValue + if (TryGetBool(right, out var rightBoolValue) && !leftNullable && left.TypeMapping!.Converter == null) { @@ -1502,7 +1574,7 @@ private SqlExpression OptimizeComparison( : left; } - if (TryGetBoolConstantValue(left) is bool leftBoolValue + if (TryGetBool(left, out var leftBoolValue) && !rightNullable && right.TypeMapping!.Converter == null) { @@ -2069,10 +2141,6 @@ private SqlExpression ProcessNullNotNull(SqlUnaryExpression sqlUnaryExpression, private static bool IsLogicalNot(SqlUnaryExpression? sqlUnaryExpression) => sqlUnaryExpression is { OperatorType: ExpressionType.Not } && sqlUnaryExpression.Type == typeof(bool); - private bool IsNull(SqlExpression expression) - => expression is SqlConstantExpression { Value: null } - || expression is SqlParameterExpression { Name: string parameterName } && ParameterValues[parameterName] is null; - // ?a == ?b -> [(a == b) && (a != null && b != null)] || (a == null && b == null)) // // a | b | F1 = a == b | F2 = (a != null && b != null) | F3 = F1 && F2 | diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs index e3b99f42894..17204e8123e 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; +using System.Text; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; @@ -13,6 +16,9 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; /// public class SqlServerSqlTranslatingExpressionVisitor : RelationalSqlTranslatingExpressionVisitor { + private readonly QueryCompilationContext _queryCompilationContext; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + private static readonly HashSet DateTimeDataTypes = new() { @@ -43,6 +49,21 @@ private static readonly HashSet ArithmeticOperatorTypes ExpressionType.Modulo }; + private static readonly MethodInfo StringStartsWithMethodInfo + = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) })!; + + private static readonly MethodInfo StringEndsWithMethodInfo + = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) })!; + + private static readonly MethodInfo StringContainsMethodInfo + = typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) })!; + + private static readonly MethodInfo EscapeLikePatternParameterMethod = + typeof(SqlServerSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ConstructLikePatternParameter))!; + + private const char LikeEscapeChar = '\\'; + private const string LikeEscapeString = "\\"; + /// /// 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 @@ -55,6 +76,8 @@ public SqlServerSqlTranslatingExpressionVisitor( QueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor) : base(dependencies, queryCompilationContext, queryableMethodTranslatingExpressionVisitor) { + _queryCompilationContext = queryCompilationContext; + _sqlExpressionFactory = dependencies.SqlExpressionFactory; } /// @@ -142,17 +165,280 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) /// protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { - if (methodCallExpression is { Method: { IsGenericMethod: true } } genericMethodCall - && genericMethodCall.Method.GetGenericMethodDefinition() == EnumerableMethods.ElementAt - && genericMethodCall.Arguments[0].Type == typeof(byte[])) + var method = methodCallExpression.Method; + + if (method.IsGenericMethod + && method.GetGenericMethodDefinition() == EnumerableMethods.ElementAt + && methodCallExpression.Arguments[0].Type == typeof(byte[])) { return TranslateByteArrayElementAccess( - genericMethodCall.Arguments[0], - genericMethodCall.Arguments[1], + methodCallExpression.Arguments[0], + methodCallExpression.Arguments[1], methodCallExpression.Type); } + if (method == StringStartsWithMethodInfo + && TryTranslateStartsEndsWithContains( + methodCallExpression.Object!, methodCallExpression.Arguments[0], StartsEndsWithContains.StartsWith, out var translation1)) + { + return translation1; + } + + if (method == StringEndsWithMethodInfo + && TryTranslateStartsEndsWithContains( + methodCallExpression.Object!, methodCallExpression.Arguments[0], StartsEndsWithContains.EndsWith, out var translation2)) + { + return translation2; + } + + if (method == StringContainsMethodInfo + && TryTranslateStartsEndsWithContains( + methodCallExpression.Object!, methodCallExpression.Arguments[0], StartsEndsWithContains.Contains, out var translation3)) + { + return translation3; + } + return base.VisitMethodCall(methodCallExpression); + + bool TryTranslateStartsEndsWithContains( + Expression instance, Expression pattern, StartsEndsWithContains methodType, [NotNullWhen(true)] out SqlExpression? translation) + { + if (Visit(instance) is not SqlExpression translatedInstance + || Visit(pattern) is not SqlExpression translatedPattern) + { + translation = null; + return false; + } + + var stringTypeMapping = ExpressionExtensions.InferTypeMapping(translatedInstance, translatedPattern); + + translatedInstance = _sqlExpressionFactory.ApplyTypeMapping(translatedInstance, stringTypeMapping); + translatedPattern = _sqlExpressionFactory.ApplyTypeMapping(translatedPattern, stringTypeMapping); + + switch (translatedPattern) + { + case SqlConstantExpression patternConstant: + { + // The pattern is constant. Aside from null and empty string, we escape all special characters (%, _, \) and send a + // simple LIKE + translation = patternConstant.Value switch + { + null => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant(null, stringTypeMapping)), + + // In .NET, all strings start with/end with/contain the empty string, but SQL LIKE return false for empty patterns. + // Return % which always matches instead. + // Note that we don't just return a true constant, since null strings shouldn't match even an empty string + // (but SqlNullabilityProcess will convert this to a true constant if the instance is non-nullable) + "" => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant("%")), + + string s => s.Any(IsLikeWildChar) + ? _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant( + methodType switch + { + StartsEndsWithContains.StartsWith => EscapeLikePattern(s) + '%', + StartsEndsWithContains.EndsWith => '%' + EscapeLikePattern(s), + StartsEndsWithContains.Contains => $"%{EscapeLikePattern(s)}%", + + _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null) + }), + _sqlExpressionFactory.Constant(LikeEscapeString)) + : _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant( + methodType switch + { + StartsEndsWithContains.StartsWith => s + '%', + StartsEndsWithContains.EndsWith => '%' + s, + StartsEndsWithContains.Contains => $"%{s}%", + + _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null) + })), + + _ => throw new UnreachableException() + }; + + return true; + } + + case SqlParameterExpression patternParameter + when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal): + { + // The pattern is a parameter, register a runtime parameter that will contain the rewritten LIKE pattern, where + // all special characters have been escaped. + var lambda = Expression.Lambda( + Expression.Call( + EscapeLikePatternParameterMethod, + QueryCompilationContext.QueryContextParameter, + Expression.Constant(patternParameter.Name), + Expression.Constant(methodType)), + QueryCompilationContext.QueryContextParameter); + + var escapedPatternParameter = + _queryCompilationContext.RegisterRuntimeParameter(patternParameter.Name + "_rewritten", lambda); + + translation = _sqlExpressionFactory.Like( + translatedInstance, + new SqlParameterExpression(escapedPatternParameter.Name!, escapedPatternParameter.Type, stringTypeMapping), + _sqlExpressionFactory.Constant(LikeEscapeString)); + + return true; + } + + default: + // The pattern is a column or a complex expression; the possible special characters in the pattern cannot be escaped, + // preventing us from translating to LIKE. + translation = methodType switch + { + // For StartsWith/EndsWith, use LEFT or RIGHT instead to extract substring and compare: + // WHERE instance IS NOT NULL AND pattern IS NOT NULL AND LEFT(instance, LEN(pattern)) = pattern + // This is less efficient than LIKE (i.e. StartsWith does an index scan instead of seek), but we have no choice. + // Note that we compensate for the case where both the instance and the pattern are null (null.StartsWith(null)); a + // simple equality would yield true in that case, but we want false. We technically + StartsEndsWithContains.StartsWith or StartsEndsWithContains.EndsWith + => _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(translatedInstance), + _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(translatedPattern), + _sqlExpressionFactory.Equal( + _sqlExpressionFactory.Function( + methodType is StartsEndsWithContains.StartsWith ? "LEFT" : "RIGHT", + new[] + { + translatedInstance, + _sqlExpressionFactory.Function( + "LEN", + new[] { translatedPattern }, + nullable: true, + argumentsPropagateNullability: new[] { true }, + typeof(int)) + }, + nullable: true, + argumentsPropagateNullability: new[] { true, true }, + typeof(string), + stringTypeMapping), + translatedPattern))), + + // For Contains, just use CHARINDEX and check if the result is greater than 0. + // Add a check to return null when the pattern is an empty string (and the string isn't null) + StartsEndsWithContains.Contains + => _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(translatedInstance), + _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(translatedPattern), + _sqlExpressionFactory.OrElse( + _sqlExpressionFactory.GreaterThan( + _sqlExpressionFactory.Function( + "CHARINDEX", + new[] { translatedPattern, translatedInstance }, + nullable: true, + argumentsPropagateNullability: new[] { true, true }, + typeof(int)), + _sqlExpressionFactory.Constant(0)), + _sqlExpressionFactory.Like( + translatedPattern, + _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping))))), + + // StartsEndsWithContains.Contains + // => _sqlExpressionFactory.OrElse( + // _sqlExpressionFactory.GreaterThan( + // _sqlExpressionFactory.Function( + // "CHARINDEX", + // new[] { translatedPattern, translatedInstance }, + // nullable: true, + // argumentsPropagateNullability: new[] { true, true }, + // typeof(int)), + // _sqlExpressionFactory.Constant(0)), + // _sqlExpressionFactory.AndAlso( + // _sqlExpressionFactory.Like( + // translatedPattern, + // _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)), + // _sqlExpressionFactory.IsNotNull(translatedInstance))), + + _ => throw new UnreachableException() + }; + + return true; + } + } + } + + private static string? ConstructLikePatternParameter( + QueryContext queryContext, string baseParameterName, StartsEndsWithContains methodType) + => queryContext.ParameterValues[baseParameterName] switch + { + null => null, + + // In .NET, all strings start/end with the empty string, but SQL LIKE return false for empty patterns. + // Return % which always matches instead. + "" => "%", + + string s => methodType switch + { + StartsEndsWithContains.StartsWith => EscapeLikePattern(s) + '%', + StartsEndsWithContains.EndsWith => '%' + EscapeLikePattern(s), + StartsEndsWithContains.Contains => $"%{EscapeLikePattern(s)}%", + _ => throw new ArgumentOutOfRangeException(nameof(methodType), methodType, null) + }, + + _ => throw new UnreachableException() + }; + + private enum StartsEndsWithContains + { + StartsWith, + EndsWith, + Contains + } + + /// + /// 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. + /// + private static bool IsLikeWildChar(char c) + => c is '%' or '_' or '['; // See https://docs.microsoft.com/en-us/sql/t-sql/language-elements/like-transact-sql + + /// + /// 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. + /// + private static string EscapeLikePattern(string pattern) + { + int i; + for (i = 0; i < pattern.Length; i++) + { + var c = pattern[i]; + if (IsLikeWildChar(c) || c == LikeEscapeChar) + { + break; + } + } + + if (i == pattern.Length) // No special characters were detected, just return the original pattern string + { + return pattern; + } + + var builder = new StringBuilder(pattern, 0, i, pattern.Length + 10); + + for (; i < pattern.Length; i++) + { + var c = pattern[i]; + if (IsLikeWildChar(c) + || c == LikeEscapeChar) + { + builder.Append(LikeEscapeChar); + } + + builder.Append(c); + } + + return builder.ToString(); } private Expression TranslateByteArrayElementAccess(Expression array, Expression index, Type resultType) diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs index 306648dec52..317bccf1604 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs @@ -62,15 +62,6 @@ private static readonly MethodInfo TrimEndMethodInfoWithCharArrayArg private static readonly MethodInfo TrimMethodInfoWithCharArrayArg = typeof(string).GetRuntimeMethod(nameof(string.Trim), new[] { typeof(char[]) })!; - private static readonly MethodInfo StartsWithMethodInfo - = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) })!; - - private static readonly MethodInfo ContainsMethodInfo - = typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) })!; - - private static readonly MethodInfo EndsWithMethodInfo - = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) })!; - private static readonly MethodInfo FirstOrDefaultMethodInfoWithoutArgs = typeof(Enumerable).GetRuntimeMethods().Single( m => m.Name == nameof(Enumerable.FirstOrDefault) @@ -83,9 +74,6 @@ private static readonly MethodInfo LastOrDefaultMethodInfoWithoutArgs private readonly ISqlExpressionFactory _sqlExpressionFactory; - private const char LikeEscapeChar = '\\'; - private const string LikeEscapeString = "\\"; - /// /// 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 @@ -243,59 +231,6 @@ public SqlServerStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactor instance.Type, instance.TypeMapping); } - - if (ContainsMethodInfo.Equals(method)) - { - var pattern = arguments[0]; - var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern); - instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping); - pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping); - - if (pattern is SqlConstantExpression constantPattern) - { - if (!(constantPattern.Value is string patternValue)) - { - return _sqlExpressionFactory.Like( - instance, - _sqlExpressionFactory.Constant(null, stringTypeMapping)); - } - - if (patternValue.Length == 0) - { - return _sqlExpressionFactory.Constant(true); - } - - return patternValue.Any(IsLikeWildChar) - ? _sqlExpressionFactory.Like( - instance, - _sqlExpressionFactory.Constant($"%{EscapeLikePattern(patternValue)}%"), - _sqlExpressionFactory.Constant(LikeEscapeString)) - : _sqlExpressionFactory.Like(instance, _sqlExpressionFactory.Constant($"%{patternValue}%")); - } - - return _sqlExpressionFactory.OrElse( - _sqlExpressionFactory.Like( - pattern, - _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)), - _sqlExpressionFactory.GreaterThan( - _sqlExpressionFactory.Function( - "CHARINDEX", - new[] { pattern, instance }, - nullable: true, - argumentsPropagateNullability: new[] { true, true }, - typeof(int)), - _sqlExpressionFactory.Constant(0))); - } - - if (StartsWithMethodInfo.Equals(method)) - { - return TranslateStartsEndsWith(instance, arguments[0], true); - } - - if (EndsWithMethodInfo.Equals(method)) - { - return TranslateStartsEndsWith(instance, arguments[0], false); - } } if (IsNullOrEmptyMethodInfo.Equals(method)) @@ -355,80 +290,6 @@ public SqlServerStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactor return null; } - private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpression pattern, bool startsWith) - { - var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern); - - instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping); - pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping); - - if (pattern is SqlConstantExpression constantExpression) - { - // The pattern is constant. Aside from null or empty, we escape all special characters (%, _, \) - // in C# and send a simple LIKE - if (!(constantExpression.Value is string patternValue)) - { - return _sqlExpressionFactory.Like( - instance, - _sqlExpressionFactory.Constant(null, stringTypeMapping)); - } - - return patternValue.Any(IsLikeWildChar) - ? _sqlExpressionFactory.Like( - instance, - _sqlExpressionFactory.Constant( - startsWith - ? EscapeLikePattern(patternValue) + '%' - : '%' + EscapeLikePattern(patternValue)), - _sqlExpressionFactory.Constant(LikeEscapeString)) - : _sqlExpressionFactory.Like( - instance, - _sqlExpressionFactory.Constant(startsWith ? patternValue + '%' : '%' + patternValue)); - } - - // The pattern is non-constant, we use LEFT or RIGHT to extract substring and compare. - if (startsWith) - { - return _sqlExpressionFactory.Equal( - _sqlExpressionFactory.Function( - "LEFT", - new[] - { - instance, - _sqlExpressionFactory.Function( - "LEN", - new[] { pattern }, - nullable: true, - argumentsPropagateNullability: new[] { true }, - typeof(int)) - }, - nullable: true, - argumentsPropagateNullability: new[] { true, true }, - typeof(string), - stringTypeMapping), - pattern); - } - - return _sqlExpressionFactory.Equal( - _sqlExpressionFactory.Function( - "RIGHT", - new[] - { - instance, - _sqlExpressionFactory.Function( - "LEN", - new[] { pattern }, - nullable: true, - argumentsPropagateNullability: new[] { true }, - typeof(int)) - }, - nullable: true, - argumentsPropagateNullability: new[] { true, true }, - typeof(string), - stringTypeMapping), - pattern); - } - private SqlExpression TranslateIndexOf( SqlExpression instance, MethodInfo method, @@ -497,26 +358,4 @@ private SqlExpression TranslateIndexOf( }, charIndexExpression); } - - // See https://docs.microsoft.com/en-us/sql/t-sql/language-elements/like-transact-sql - private static bool IsLikeWildChar(char c) - => c is '%' or '_' or '['; - - private static string EscapeLikePattern(string pattern) - { - var builder = new StringBuilder(); - for (var i = 0; i < pattern.Length; i++) - { - var c = pattern[i]; - if (IsLikeWildChar(c) - || c == LikeEscapeChar) - { - builder.Append(LikeEscapeChar); - } - - builder.Append(c); - } - - return builder.ToString(); - } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 813912eec71..29f1c421110 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Text; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; @@ -14,6 +16,21 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; /// public class SqliteSqlTranslatingExpressionVisitor : RelationalSqlTranslatingExpressionVisitor { + private readonly QueryCompilationContext _queryCompilationContext; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + private static readonly MethodInfo StringStartsWithMethodInfo + = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) })!; + + private static readonly MethodInfo StringEndsWithMethodInfo + = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) })!; + + private static readonly MethodInfo EscapeLikePatternParameterMethod = + typeof(SqliteSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ConstructLikePatternParameter))!; + + private const char LikeEscapeChar = '\\'; + private const string LikeEscapeString = "\\"; + private static readonly IReadOnlyDictionary> RestrictedBinaryExpressions = new Dictionary> { @@ -91,6 +108,8 @@ public SqliteSqlTranslatingExpressionVisitor( QueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor) : base(dependencies, queryCompilationContext, queryableMethodTranslatingExpressionVisitor) { + _queryCompilationContext = queryCompilationContext; + _sqlExpressionFactory = dependencies.SqlExpressionFactory; } /// @@ -226,6 +245,213 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return visitedExpression; } + /// + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + var method = methodCallExpression.Method; + + if (method == StringStartsWithMethodInfo + && TryTranslateStartsEndsWith( + methodCallExpression.Object!, methodCallExpression.Arguments[0], startsWith: true, out var translation1)) + { + return translation1; + } + + if (method == StringEndsWithMethodInfo + && TryTranslateStartsEndsWith( + methodCallExpression.Object!, methodCallExpression.Arguments[0], startsWith: false, out var translation2)) + { + return translation2; + } + + return base.VisitMethodCall(methodCallExpression); + + bool TryTranslateStartsEndsWith( + Expression instance, + Expression pattern, + bool startsWith, + [NotNullWhen(true)] out SqlExpression? translation) + { + if (Visit(instance) is not SqlExpression translatedInstance + || Visit(pattern) is not SqlExpression translatedPattern) + { + translation = null; + return false; + } + + var stringTypeMapping = ExpressionExtensions.InferTypeMapping(translatedInstance, translatedPattern); + + translatedInstance = _sqlExpressionFactory.ApplyTypeMapping(translatedInstance, stringTypeMapping); + translatedPattern = _sqlExpressionFactory.ApplyTypeMapping(translatedPattern, stringTypeMapping); + + switch (translatedPattern) + { + case SqlConstantExpression patternConstant: + { + // The pattern is constant. Aside from null and empty string, we escape all special characters (%, _, \) and send a + // simple LIKE + translation = patternConstant.Value switch + { + null => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant(null, stringTypeMapping)), + + // In .NET, all strings start with/end with/contain the empty string, but SQL LIKE return false for empty patterns. + // Return % which always matches instead. + // Note that we don't just return a true constant, since null strings shouldn't match even an empty string + // (but SqlNullabilityProcess will convert this to a true constant if the instance is non-nullable) + "" => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant("%")), + + string s => s.Any(IsLikeWildChar) + ? _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant(startsWith ? EscapeLikePattern(s) + '%' : '%' + EscapeLikePattern(s)), + _sqlExpressionFactory.Constant(LikeEscapeString)) + : _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant(startsWith ? s + '%' : '%' + s)), + + _ => throw new UnreachableException() + }; + + return true; + } + + case SqlParameterExpression patternParameter + when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal): + { + // The pattern is a parameter, register a runtime parameter that will contain the rewritten LIKE pattern, where + // all special characters have been escaped. + var lambda = Expression.Lambda( + Expression.Call( + EscapeLikePatternParameterMethod, + QueryCompilationContext.QueryContextParameter, + Expression.Constant(patternParameter.Name), + Expression.Constant(startsWith)), + QueryCompilationContext.QueryContextParameter); + + var escapedPatternParameter = + _queryCompilationContext.RegisterRuntimeParameter(patternParameter.Name + "_rewritten", lambda); + + translation = _sqlExpressionFactory.Like( + translatedInstance, + new SqlParameterExpression(escapedPatternParameter.Name!, escapedPatternParameter.Type, stringTypeMapping), + _sqlExpressionFactory.Constant(LikeEscapeString)); + + return true; + } + + default: + // The pattern is a column or a complex expression; the possible special characters in the pattern cannot be escaped, + // preventing us from translating to LIKE. + if (startsWith) + { + // Generate: WHERE instance IS NOT NULL AND pattern IS NOT NULL AND (substr(instance, 1, length(pattern)) = pattern OR pattern = '') + // Note that the empty string pattern needs special handling, since in .NET it returns true for all non-null + // instances, but substr(instance, 0) returns the entire string in SQLite. + // Note that we compensate for the case where both the instance and the pattern are null (null.StartsWith(null)); a + // simple equality would yield true in that case, but we want false. We technically + translation = _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(translatedInstance), + _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(translatedPattern), + _sqlExpressionFactory.OrElse( + _sqlExpressionFactory.Equal( + _sqlExpressionFactory.Function( + "substr", + new[] + { + translatedInstance, + _sqlExpressionFactory.Constant(1), + _sqlExpressionFactory.Function( + "length", + new[] { translatedPattern }, + nullable: true, + argumentsPropagateNullability: new[] { true }, + typeof(int)) + }, + nullable: true, + argumentsPropagateNullability: new[] { true, false, true }, + typeof(string), + stringTypeMapping), + translatedPattern), + _sqlExpressionFactory.Equal(translatedPattern, _sqlExpressionFactory.Constant(string.Empty))))); + } + else + { + // Generate: WHERE instance IS NOT NULL AND pattern IS NOT NULL AND (substr(instance, -length(pattern)) = pattern OR pattern = '') + // Note that the empty string pattern needs special handling, since in .NET it returns true for all non-null + // instances, but substr(instance, 0) returns the entire string in SQLite. + // Note that we compensate for the case where both the instance and the pattern are null (null.StartsWith(null)); a + // simple equality would yield true in that case, but we want false. We technically + translation = + _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(translatedInstance), + _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(translatedPattern), + _sqlExpressionFactory.OrElse( + _sqlExpressionFactory.Equal( + _sqlExpressionFactory.Function( + "substr", + new[] + { + translatedInstance, + _sqlExpressionFactory.Negate( + _sqlExpressionFactory.Function( + "length", + new[] { translatedPattern }, + nullable: true, + argumentsPropagateNullability: new[] { true }, + typeof(int))) + }, + nullable: true, + argumentsPropagateNullability: new[] { true, true }, + typeof(string), + stringTypeMapping), + translatedPattern), + _sqlExpressionFactory.Equal(translatedPattern, _sqlExpressionFactory.Constant(string.Empty))))); + } + + return true; + } + } + } + + private static string? ConstructLikePatternParameter( + QueryContext queryContext, string baseParameterName, bool startsWith) + => queryContext.ParameterValues[baseParameterName] switch + { + null => null, + + // In .NET, all strings start/end with the empty string, but SQL LIKE return false for empty patterns. + // Return % which always matches instead. + "" => "%", + + string s => startsWith ? EscapeLikePattern(s) + '%' : '%' + EscapeLikePattern(s), + + _ => throw new UnreachableException() + }; + + // See https://www.sqlite.org/lang_expr.html + private static bool IsLikeWildChar(char c) + => c is '%' or '_'; + + private static string EscapeLikePattern(string pattern) + { + var builder = new StringBuilder(); + for (var i = 0; i < pattern.Length; i++) + { + var c = pattern[i]; + if (IsLikeWildChar(c) + || c == LikeEscapeChar) + { + builder.Append(LikeEscapeChar); + } + + builder.Append(c); + } + + return builder.ToString(); + } + [return: NotNullIfNotNull(nameof(expression))] private static Type? GetProviderType(SqlExpression? expression) => expression == null diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringMethodTranslator.cs index 840cabb2d30..70819ab0583 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringMethodTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringMethodTranslator.cs @@ -65,15 +65,9 @@ private static readonly MethodInfo TrimEndMethodInfoWithCharArrayArg private static readonly MethodInfo TrimMethodInfoWithCharArrayArg = typeof(string).GetRuntimeMethod(nameof(string.Trim), new[] { typeof(char[]) })!; - private static readonly MethodInfo StartsWithMethodInfo - = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) })!; - private static readonly MethodInfo ContainsMethodInfo = typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) })!; - private static readonly MethodInfo EndsWithMethodInfo - = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) })!; - private static readonly MethodInfo FirstOrDefaultMethodInfoWithoutArgs = typeof(Enumerable).GetRuntimeMethods().Single( m => m.Name == nameof(Enumerable.FirstOrDefault) @@ -85,7 +79,6 @@ private static readonly MethodInfo LastOrDefaultMethodInfoWithoutArgs && m.GetParameters().Length == 1).MakeGenericMethod(typeof(char)); private readonly ISqlExpressionFactory _sqlExpressionFactory; - private const char LikeEscapeChar = '\\'; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -214,28 +207,20 @@ public SqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping); pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping); - return _sqlExpressionFactory.OrElse( - _sqlExpressionFactory.Equal( - pattern, - _sqlExpressionFactory.Constant(string.Empty, stringTypeMapping)), - _sqlExpressionFactory.GreaterThan( - _sqlExpressionFactory.Function( - "instr", - new[] { instance, pattern }, - nullable: true, - argumentsPropagateNullability: new[] { true, true }, - typeof(int)), - _sqlExpressionFactory.Constant(0))); - } - - if (StartsWithMethodInfo.Equals(method)) - { - return TranslateStartsEndsWith(instance, arguments[0], true); - } - - if (EndsWithMethodInfo.Equals(method)) - { - return TranslateStartsEndsWith(instance, arguments[0], false); + // Note: we add IS NOT NULL checks here since we don't do null semantics/compensation for comparison (greater-than) + return + _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(instance), + _sqlExpressionFactory.AndAlso( + _sqlExpressionFactory.IsNotNull(pattern), + _sqlExpressionFactory.GreaterThan( + _sqlExpressionFactory.Function( + "instr", + new[] { instance, pattern }, + nullable: true, + argumentsPropagateNullability: new[] { true, true }, + typeof(int)), + _sqlExpressionFactory.Constant(0)))); } } @@ -291,124 +276,6 @@ public SqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) return null; } - private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpression pattern, bool startsWith) - { - var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern); - - instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping); - pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping); - - if (pattern is SqlConstantExpression constantExpression) - { - // The pattern is constant. Aside from null or empty, we escape all special characters (%, _, \) - // in C# and send a simple LIKE - if (!(constantExpression.Value is string constantString)) - { - return _sqlExpressionFactory.Like(instance, _sqlExpressionFactory.Constant(null, stringTypeMapping)); - } - - if (constantString.Length == 0) - { - return _sqlExpressionFactory.Constant(true); - } - - return constantString.Any(c => IsLikeWildChar(c)) - ? _sqlExpressionFactory.Like( - instance, - _sqlExpressionFactory.Constant( - startsWith - ? EscapeLikePattern(constantString) + '%' - : '%' + EscapeLikePattern(constantString)), - _sqlExpressionFactory.Constant( - LikeEscapeChar.ToString())) // SQL Server has no char mapping, avoid value conversion warning) - : _sqlExpressionFactory.Like( - instance, - _sqlExpressionFactory.Constant(startsWith ? constantString + '%' : '%' + constantString)); - } - - // The pattern is non-constant, we use LEFT or RIGHT to extract substring and compare. - // For StartsWith we also first run a LIKE to quickly filter out most non-matching results (sargable, but imprecise - // because of wildcards). - if (startsWith) - { - return _sqlExpressionFactory.OrElse( - _sqlExpressionFactory.AndAlso( - _sqlExpressionFactory.Like( - instance, - _sqlExpressionFactory.Add( - pattern, - _sqlExpressionFactory.Constant("%"))), - _sqlExpressionFactory.Equal( - _sqlExpressionFactory.Function( - "substr", - new[] - { - instance, - _sqlExpressionFactory.Constant(1), - _sqlExpressionFactory.Function( - "length", - new[] { pattern }, - nullable: true, - argumentsPropagateNullability: new[] { true }, - typeof(int)) - }, - nullable: true, - argumentsPropagateNullability: new[] { true, false, true }, - typeof(string), - stringTypeMapping), - pattern)), - _sqlExpressionFactory.Equal( - pattern, - _sqlExpressionFactory.Constant(string.Empty))); - } - - return _sqlExpressionFactory.OrElse( - _sqlExpressionFactory.Equal( - _sqlExpressionFactory.Function( - "substr", - new[] - { - instance, - _sqlExpressionFactory.Negate( - _sqlExpressionFactory.Function( - "length", - new[] { pattern }, - nullable: true, - argumentsPropagateNullability: new[] { true }, - typeof(int))) - }, - nullable: true, - argumentsPropagateNullability: new[] { true, true }, - typeof(string), - stringTypeMapping), - pattern), - _sqlExpressionFactory.Equal( - pattern, - _sqlExpressionFactory.Constant(string.Empty))); - } - - // See https://www.sqlite.org/lang_expr.html - private static bool IsLikeWildChar(char c) - => c is '%' or '_'; - - private static string EscapeLikePattern(string pattern) - { - var builder = new StringBuilder(); - for (var i = 0; i < pattern.Length; i++) - { - var c = pattern[i]; - if (IsLikeWildChar(c) - || c == LikeEscapeChar) - { - builder.Append(LikeEscapeChar); - } - - builder.Append(c); - } - - return builder.ToString(); - } - private SqlExpression? ProcessTrimMethod(SqlExpression instance, IReadOnlyList arguments, string functionName) { var typeMapping = instance.TypeMapping; diff --git a/src/EFCore/DbFunctionsExtensions.cs b/src/EFCore/DbFunctionsExtensions.cs index 8a3b831a673..86b9d15f0f9 100644 --- a/src/EFCore/DbFunctionsExtensions.cs +++ b/src/EFCore/DbFunctionsExtensions.cs @@ -31,10 +31,7 @@ public static class DbFunctionsExtensions /// The string that is to be matched. /// The pattern which may involve wildcards %,_,[,],^. /// if there is a match. - public static bool Like( - this DbFunctions _, - string matchExpression, - string pattern) + public static bool Like(this DbFunctions _, string? matchExpression, string? pattern) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like))); /// @@ -59,11 +56,7 @@ public static bool Like( /// if they are not used as wildcards. /// /// if there is a match. - public static bool Like( - this DbFunctions _, - string matchExpression, - string pattern, - string escapeCharacter) + public static bool Like(this DbFunctions _, string? matchExpression, string? pattern, string? escapeCharacter) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like))); /// diff --git a/src/EFCore/Query/Internal/QueryOptimizingExpressionVisitor.cs b/src/EFCore/Query/Internal/QueryOptimizingExpressionVisitor.cs index 4e7deb8ab01..6ca20e19952 100644 --- a/src/EFCore/Query/Internal/QueryOptimizingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/QueryOptimizingExpressionVisitor.cs @@ -38,14 +38,6 @@ public class QueryOptimizingExpressionVisitor : ExpressionVisitor private static readonly MethodInfo StringCompareWithoutComparisonMethod = typeof(string).GetRuntimeMethod(nameof(string.Compare), new[] { typeof(string), typeof(string) })!; - private static readonly MethodInfo StartsWithMethodInfo = - typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) })!; - - private static readonly MethodInfo EndsWithMethodInfo = - typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) })!; - - private static readonly Expression ConstantNullString = Expression.Constant(null, typeof(string)); - /// /// 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 @@ -180,33 +172,6 @@ protected override MemberAssignment VisitMemberAssignment(MemberAssignment membe /// protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { - if (Equals(StartsWithMethodInfo, methodCallExpression.Method) - || Equals(EndsWithMethodInfo, methodCallExpression.Method)) - { - if (methodCallExpression.Arguments[0] is ConstantExpression { Value: "" }) - { - // every string starts/ends with empty string. - return Expression.Constant(true); - } - - var newObject = Visit(methodCallExpression.Object)!; - var newArgument = Visit(methodCallExpression.Arguments[0]); - - var result = Expression.AndAlso( - Expression.NotEqual(newObject, ConstantNullString), - Expression.AndAlso( - Expression.NotEqual(newArgument, ConstantNullString), - methodCallExpression.Update(newObject, new[] { newArgument }))); - - return newArgument is ConstantExpression - ? result - : Expression.OrElse( - Expression.Equal( - newArgument, - Expression.Constant(string.Empty)), - result); - } - // Normalize x.Any(i => i == foo) to x.Contains(foo) // And x.All(i => i != foo) to !x.Contains(foo) if (methodCallExpression.Method.IsGenericMethod @@ -335,38 +300,7 @@ protected override Expression VisitNewArray(NewArrayExpression newArrayExpressio /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override Expression VisitUnary(UnaryExpression unaryExpression) - { - if (unaryExpression is { NodeType: ExpressionType.Not, Operand: MethodCallExpression innerMethodCall } - && (Equals(StartsWithMethodInfo, innerMethodCall.Method) - || Equals(EndsWithMethodInfo, innerMethodCall.Method))) - { - if (innerMethodCall.Arguments[0] is ConstantExpression { Value: "" }) - { - // every string starts/ends with empty string. - return Expression.Constant(false); - } - - var newObject = Visit(innerMethodCall.Object)!; - var newArgument = Visit(innerMethodCall.Arguments[0]); - - var result = Expression.AndAlso( - Expression.NotEqual(newObject, ConstantNullString), - Expression.AndAlso( - Expression.NotEqual(newArgument, ConstantNullString), - Expression.Not(innerMethodCall.Update(newObject, new[] { newArgument })))); - - return newArgument is ConstantExpression - ? result - : Expression.AndAlso( - Expression.NotEqual( - newArgument, - Expression.Constant(string.Empty)), - result); - } - - return unaryExpression.Update( - Visit(unaryExpression.Operand)); - } + => unaryExpression.Update(Visit(unaryExpression.Operand)); private static Expression MatchExpressionType(Expression expression, Type typeToMatch) => expression.Type != typeToMatch diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs index 6ff277f6f90..fd80af23244 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindFunctionsQueryCosmosTest.cs @@ -30,7 +30,21 @@ public override async Task String_StartsWith_Literal(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["ContactName"] != null) AND (("M" != null) AND STARTSWITH(c["ContactName"], "M")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["ContactName"], "M")) +"""); + } + + public override async Task String_StartsWith_Parameter(bool async) + { + await base.String_StartsWith_Parameter(async); + + AssertSql( +""" +@__pattern_0='M' + +SELECT c +FROM root c +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["ContactName"], @__pattern_0)) """); } @@ -42,8 +56,9 @@ public override async Task String_StartsWith_Identity(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["ContactName"] = "") OR ((c["ContactName"] != null) AND ((c["ContactName"] != null) AND STARTSWITH(c["ContactName"], c["ContactName"]))))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["ContactName"], c["ContactName"])) """); + } public override async Task String_StartsWith_Column(bool async) @@ -54,7 +69,7 @@ public override async Task String_StartsWith_Column(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["ContactName"] = "") OR ((c["ContactName"] != null) AND ((c["ContactName"] != null) AND STARTSWITH(c["ContactName"], c["ContactName"]))))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["ContactName"], c["ContactName"])) """); } @@ -66,7 +81,7 @@ public override async Task String_StartsWith_MethodCall(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["ContactName"] != null) AND (("M" != null) AND STARTSWITH(c["ContactName"], "M")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["ContactName"], "M")) """); } @@ -78,7 +93,21 @@ public override async Task String_EndsWith_Literal(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["ContactName"] != null) AND (("b" != null) AND ENDSWITH(c["ContactName"], "b")))) +WHERE ((c["Discriminator"] = "Customer") AND ENDSWITH(c["ContactName"], "b")) +"""); + } + + public override async Task String_EndsWith_Parameter(bool async) + { + await base.String_EndsWith_Parameter(async); + + AssertSql( +""" +@__pattern_0='b' + +SELECT c +FROM root c +WHERE ((c["Discriminator"] = "Customer") AND ENDSWITH(c["ContactName"], @__pattern_0)) """); } @@ -90,7 +119,7 @@ public override async Task String_EndsWith_Identity(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["ContactName"] = "") OR ((c["ContactName"] != null) AND ((c["ContactName"] != null) AND ENDSWITH(c["ContactName"], c["ContactName"]))))) +WHERE ((c["Discriminator"] = "Customer") AND ENDSWITH(c["ContactName"], c["ContactName"])) """); } @@ -102,7 +131,7 @@ public override async Task String_EndsWith_Column(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["ContactName"] = "") OR ((c["ContactName"] != null) AND ((c["ContactName"] != null) AND ENDSWITH(c["ContactName"], c["ContactName"]))))) +WHERE ((c["Discriminator"] = "Customer") AND ENDSWITH(c["ContactName"], c["ContactName"])) """); } @@ -114,7 +143,7 @@ public override async Task String_EndsWith_MethodCall(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["ContactName"] != null) AND (("m" != null) AND ENDSWITH(c["ContactName"], "m")))) +WHERE ((c["Discriminator"] = "Customer") AND ENDSWITH(c["ContactName"], "m")) """); } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs index ed5ae1017ef..86988aa34a1 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs @@ -1429,7 +1429,7 @@ await Assert.ThrowsAsync( """ SELECT c["City"] FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) ORDER BY c["Country"], c["City"] """); } @@ -2966,7 +2966,7 @@ public override async Task Comparing_to_fixed_string_parameter(bool async) SELECT c["CustomerID"] FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((@__prefix_0 = "") OR ((c["CustomerID"] != null) AND ((@__prefix_0 != null) AND STARTSWITH(c["CustomerID"], @__prefix_0))))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], @__prefix_0)) """); } @@ -2994,7 +2994,7 @@ public override async Task Comparing_entity_to_null_using_Equals(bool async) """ SELECT c["CustomerID"] FROM root c -WHERE (((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) AND NOT((c["CustomerID"] = null))) +WHERE (((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) AND NOT((c["CustomerID"] = null))) ORDER BY c["CustomerID"] """); } @@ -3059,7 +3059,7 @@ public override async Task Compare_collection_navigation_with_itself(bool async) """ SELECT c["CustomerID"] FROM root c -WHERE (((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) AND (c["CustomerID"] = c["CustomerID"])) +WHERE (((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) AND (c["CustomerID"] = c["CustomerID"])) """); } @@ -3095,7 +3095,7 @@ public override async Task OrderBy_ThenBy_same_column_different_direction(bool a """ SELECT c["CustomerID"] FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) ORDER BY c["CustomerID"] """); } @@ -3108,7 +3108,7 @@ public override async Task OrderBy_OrderBy_same_column_different_direction(bool """ SELECT c["CustomerID"] FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) ORDER BY c["CustomerID"] DESC """); } @@ -4238,7 +4238,7 @@ public override async Task MemberInitExpression_NewExpression_is_funcletized_eve """ SELECT c["CustomerID"] FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) """); } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs index bfc4e24ba47..c7187c13161 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs @@ -119,7 +119,7 @@ public override async Task Projection_of_entity_type_into_object_array(bool asyn """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) ORDER BY c["CustomerID"] """); } @@ -426,7 +426,7 @@ public override async Task New_date_time_in_anonymous_type_works(bool async) """ SELECT 1 FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) """); } @@ -903,7 +903,7 @@ public override async Task Client_method_in_projection_requiring_materialization """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) """); } @@ -915,7 +915,7 @@ public override async Task Client_method_in_projection_requiring_materialization """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) """); } @@ -1292,7 +1292,7 @@ public override async Task Projection_take_predicate_projection(bool async) SELECT VALUE {"Aggregate" : ((c["CustomerID"] || " ") || c["City"])} FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "A")) ORDER BY c["CustomerID"] OFFSET 0 LIMIT @__p_0 """); @@ -1632,7 +1632,7 @@ public override async Task VisitLambda_should_not_be_visited_trivially(bool asyn """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Order") AND ((c["CustomerID"] != null) AND (("A" != null) AND STARTSWITH(c["CustomerID"], "A")))) +WHERE ((c["Discriminator"] = "Order") AND STARTSWITH(c["CustomerID"], "A")) """); } @@ -1824,7 +1824,7 @@ public override async Task Using_enumerable_parameter_in_projection(bool async) """ SELECT c["CustomerID"] FROM root c -WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] != null) AND (("F" != null) AND STARTSWITH(c["CustomerID"], "F")))) +WHERE ((c["Discriminator"] = "Customer") AND STARTSWITH(c["CustomerID"], "F")) """); } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index fae640c3f12..8525a6da14f 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -1667,7 +1667,7 @@ public override async Task Where_comparison_to_nullable_bool(bool async) """ SELECT c FROM root c -WHERE ((c["Discriminator"] = "Customer") AND (((c["CustomerID"] != null) AND (("KI" != null) AND ENDSWITH(c["CustomerID"], "KI"))) = true)) +WHERE ((c["Discriminator"] = "Customer") AND (ENDSWITH(c["CustomerID"], "KI") = true)) """); } diff --git a/test/EFCore.Relational.Specification.Tests/Query/NorthwindFunctionsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NorthwindFunctionsQueryRelationalTestBase.cs index 0666285093e..0c55bc50430 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NorthwindFunctionsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NorthwindFunctionsQueryRelationalTestBase.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.TestModels.Northwind; + namespace Microsoft.EntityFrameworkCore.Query; public abstract class NorthwindFunctionsQueryRelationalTestBase : NorthwindFunctionsQueryTestBase diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index cac161b3732..7f6c4f6357f 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -2044,6 +2044,75 @@ join e2 in ss.Set() on e1.Id equals e2.Id select (e1.NullableIntA ?? (e1.NullableIntB ?? (e2.NullableIntC ?? e2.NullableIntB))) ?? e1.NullableIntC ?? (e2.NullableIntA ?? e2.NullableIntC ?? e1.NullableIntA)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Like(bool async) + { + await AssertQueryScalar(async, + ss => ss.Set().Where(e => EF.Functions.Like(e.StringA, e.StringB)).Select(e => e.Id), + ss => ss.Set().Where(e => LikeLite(e.StringA, e.StringB)).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => EF.Functions.Like(e.StringA, e.NullableStringB)).Select(e => e.Id), + ss => ss.Set().Where(e => LikeLite(e.StringA, e.NullableStringB)).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => EF.Functions.Like(e.NullableStringA, e.StringB)).Select(e => e.Id), + ss => ss.Set().Where(e => LikeLite(e.NullableStringA, e.StringB)).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => EF.Functions.Like(e.NullableStringA, e.NullableStringB)).Select(e => e.Id), + ss => ss.Set().Where(e => LikeLite(e.NullableStringA, e.NullableStringB)).Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Like_negated(bool async) + { + await AssertQueryScalar(async, + ss => ss.Set().Where(e => !EF.Functions.Like(e.StringA, e.StringB)).Select(e => e.Id), + ss => ss.Set().Where(e => !LikeLite(e.StringA, e.StringB)).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => !EF.Functions.Like(e.StringA, e.NullableStringB)).Select(e => e.Id), + ss => ss.Set().Where(e => !LikeLite(e.StringA, e.NullableStringB)).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => !EF.Functions.Like(e.NullableStringA, e.StringB)).Select(e => e.Id), + ss => ss.Set().Where(e => !LikeLite(e.NullableStringA, e.StringB)).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => !EF.Functions.Like(e.NullableStringA, e.NullableStringB)).Select(e => e.Id), + ss => ss.Set().Where(e => !LikeLite(e.NullableStringA, e.NullableStringB)).Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Like_with_escape_char(bool async) + { + await AssertQueryScalar(async, + ss => ss.Set().Where(e => EF.Functions.Like(e.StringA, e.StringB, "\\")).Select(e => e.Id), + ss => ss.Set().Where(e => LikeLite(e.StringA, e.StringB)).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => !EF.Functions.Like(e.StringA, e.StringB, "\\")).Select(e => e.Id), + ss => ss.Set().Where(e => !LikeLite(e.StringA, e.StringB)).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => EF.Functions.Like(e.StringA, e.StringB, null)).Select(e => e.Id), + ss => ss.Set().Where(e => false).Select(e => e.Id)); + + await AssertQueryScalar(async, + ss => ss.Set().Where(e => !EF.Functions.Like(e.StringA, e.StringB, null)).Select(e => e.Id), + ss => ss.Set().Where(e => true).Select(e => e.Id)); + } + + // We can't client-evaluate Like (for the expected results). + // However, since the test data has no LIKE wildcards, it effectively functions like equality - except that 'null like null' returns + // false instead of true. So we have this "lite" implementation which doesn't support wildcards. + private bool LikeLite(string s, string pattern) + => s == pattern && s is not null && pattern is not null; + private string NormalizeDelimitersInRawString(string sql) => Fixture.TestStore.NormalizeDelimitersInRawString(sql); diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index 529d3213ae0..35afecee25a 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -310,6 +310,9 @@ public virtual Task Method_call_on_optional_navigation_translates_to_null_condit async, ss => from e1 in ss.Set() where e1.OneToOne_Optional_FK1.Name.StartsWith(e1.OneToOne_Optional_FK1.Name) + select e1, + ss => from e1 in ss.Set() + where e1.OneToOne_Optional_FK1.Name.MaybeScalar(x => x.StartsWith(e1.OneToOne_Optional_FK1.Name)) == true select e1); [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/FunkyDataQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/FunkyDataQueryTestBase.cs index 6596ad8a00a..2d8d4b9bd9e 100644 --- a/test/EFCore.Specification.Tests/Query/FunkyDataQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/FunkyDataQueryTestBase.cs @@ -23,11 +23,13 @@ public virtual async Task String_contains_on_argument_with_wildcard_constant(boo { await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.Contains("%B")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.Contains("%B")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.Contains("%B")) == true).Select(c => c.FirstName)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.Contains("a_")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.Contains("a_")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.Contains("a_")) == true).Select(c => c.FirstName)); await AssertQuery( async, @@ -37,28 +39,28 @@ await AssertQuery( await AssertQuery( async, ss => ss.Set().Where(c => c.FirstName.Contains("")).Select(c => c.FirstName), - ss => ss.Set().Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName != null).Select(c => c.FirstName)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.Contains("_Ba_")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.Contains("_Ba_")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.Contains("_Ba_")) == true).Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.Contains("%B%a%r")).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.Contains("%B%a%r")) == true) + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.Contains("%B%a%r")) != true) .Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.Contains("")).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.Contains("")) == true) - .Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName == null).Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.Contains(null)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => true).Select(c => c.FirstName)); } [ConditionalTheory] @@ -68,12 +70,14 @@ public virtual async Task String_contains_on_argument_with_wildcard_parameter(bo var prm1 = "%B"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.Contains(prm1)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.Contains(prm1)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.Contains(prm1)) == true).Select(c => c.FirstName)); var prm2 = "a_"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.Contains(prm2)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.Contains(prm2)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.Contains(prm2)) == true).Select(c => c.FirstName)); var prm3 = (string)null; await AssertQuery( @@ -85,31 +89,32 @@ await AssertQuery( await AssertQuery( async, ss => ss.Set().Where(c => c.FirstName.Contains(prm4)).Select(c => c.FirstName), - ss => ss.Set().Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName != null).Select(c => c.FirstName)); var prm5 = "_Ba_"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.Contains(prm5)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.Contains(prm5)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.Contains(prm5)) == true).Select(c => c.FirstName)); var prm6 = "%B%a%r"; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.Contains(prm6)).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.Contains(prm6)) == true) + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.Contains(prm6)) != true) .Select(c => c.FirstName)); var prm7 = ""; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.Contains(prm7)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName == null).Select(c => c.FirstName)); var prm8 = (string)null; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.Contains(prm8)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => true).Select(c => c.FirstName)); } [ConditionalTheory] @@ -122,7 +127,7 @@ public virtual Task String_contains_on_argument_with_wildcard_column(bool async) .Where(r => r.fn.Contains(r.ln)), ss => ss.Set().Select(c => c.FirstName) .SelectMany(c => ss.Set().Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) - .Where(r => r.ln == "" || r.fn.Contains(r.ln)), + .Where(r => r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.Contains(xx))) == true), elementSorter: e => (e.fn, e.ln), elementAsserter: (e, a) => { @@ -140,7 +145,8 @@ public virtual Task String_contains_on_argument_with_wildcard_column_negated(boo .Where(r => !r.fn.Contains(r.ln)), ss => ss.Set().Select(c => c.FirstName) .SelectMany(c => ss.Set().Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) - .Where(r => r.ln != "" && !r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.Contains(xx))) == true)); + .Where(r => r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.Contains(xx))) != true)); + // .Where(r => r.ln != "" && !r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.Contains(xx))) == true)); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -148,11 +154,13 @@ public virtual async Task String_starts_with_on_argument_with_wildcard_constant( { await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith("%B")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.StartsWith("%B")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith("%B")) == true).Select(c => c.FirstName)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith("a_")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.StartsWith("a_")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith("a_")) == true).Select(c => c.FirstName)); await AssertQuery( async, @@ -162,28 +170,29 @@ await AssertQuery( await AssertQuery( async, ss => ss.Set().Where(c => c.FirstName.StartsWith("")).Select(c => c.FirstName), - ss => ss.Set().Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName != null).Select(c => c.FirstName)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith("_Ba_")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.StartsWith("_Ba_")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith("_Ba_")) == true).Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.StartsWith("%B%a%r")).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.StartsWith("%B%a%r")) == true) + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith("%B%a%r")) != true) .Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.StartsWith("")).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.StartsWith("")) == true) + ss => ss.Set().Where(c => c.FirstName == null) .Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.StartsWith(null)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => true).Select(c => c.FirstName)); } [ConditionalTheory] @@ -193,12 +202,14 @@ public virtual async Task String_starts_with_on_argument_with_wildcard_parameter var prm1 = "%B"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith(prm1)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.StartsWith(prm1)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith(prm1)) == true).Select(c => c.FirstName)); var prm2 = "a_"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith(prm2)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.StartsWith(prm2)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith(prm2)) == true).Select(c => c.FirstName)); var prm3 = (string)null; await AssertQuery( @@ -210,31 +221,32 @@ await AssertQuery( await AssertQuery( async, ss => ss.Set().Where(c => c.FirstName.StartsWith(prm4)).Select(c => c.FirstName), - ss => ss.Set().Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName != null).Select(c => c.FirstName)); var prm5 = "_Ba_"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith(prm5)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.StartsWith(prm5)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith(prm5)) == true).Select(c => c.FirstName)); var prm6 = "%B%a%r"; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.StartsWith(prm6)).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.StartsWith(prm6)) == true) + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith(prm6)) != true) .Select(c => c.FirstName)); var prm7 = ""; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.StartsWith(prm7)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName == null).Select(c => c.FirstName)); var prm8 = (string)null; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.StartsWith(prm8)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => true).Select(c => c.FirstName)); } [ConditionalTheory] @@ -243,34 +255,41 @@ public virtual async Task String_starts_with_on_argument_with_bracket(bool async { await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith("["))); + ss => ss.Set().Where(c => c.FirstName.StartsWith("[")), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith("[")) == true)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith("B["))); + ss => ss.Set().Where(c => c.FirstName.StartsWith("B[")), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith("B[")) == true)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith("B[[a^"))); + ss => ss.Set().Where(c => c.FirstName.StartsWith("B[[a^")), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith("B[[a^")) == true)); var prm1 = "["; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith(prm1))); + ss => ss.Set().Where(c => c.FirstName.StartsWith(prm1)), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith(prm1)) == true)); var prm2 = "B["; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith(prm2))); + ss => ss.Set().Where(c => c.FirstName.StartsWith(prm2)), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith(prm2)) == true)); var prm3 = "B[[a^"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith(prm3))); + ss => ss.Set().Where(c => c.FirstName.StartsWith(prm3)), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.StartsWith(prm3)) == true)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.StartsWith(c.LastName))); + ss => ss.Set().Where(c => c.FirstName.StartsWith(c.LastName)), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => c.LastName.MaybeScalar(xx => x.StartsWith(xx))) == true)); } [ConditionalTheory] @@ -283,7 +302,7 @@ public virtual Task String_starts_with_on_argument_with_wildcard_column(bool asy .Where(r => r.fn.StartsWith(r.ln)), ss => ss.Set().Select(c => c.FirstName) .SelectMany(c => ss.Set().Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) - .Where(r => r.ln == "" || r.fn.StartsWith(r.ln)), + .Where(r => r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.StartsWith(xx))) == true), elementSorter: e => (e.fn, e.ln), elementAsserter: (e, a) => { @@ -301,7 +320,7 @@ public virtual Task String_starts_with_on_argument_with_wildcard_column_negated( .Where(r => !r.fn.StartsWith(r.ln)), ss => ss.Set().Select(c => c.FirstName) .SelectMany(c => ss.Set().Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) - .Where(r => r.ln != "" && !r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.StartsWith(xx))) == true)); + .Where(r => !(r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.StartsWith(xx))) == true))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -309,11 +328,13 @@ public virtual async Task String_ends_with_on_argument_with_wildcard_constant(bo { await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.EndsWith("%B")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.EndsWith("%B")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith("%B")) == true).Select(c => c.FirstName)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.EndsWith("a_")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.EndsWith("a_")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith("a_")) == true).Select(c => c.FirstName)); await AssertQuery( async, @@ -323,27 +344,27 @@ await AssertQuery( await AssertQuery( async, ss => ss.Set().Where(c => c.FirstName.EndsWith("")).Select(c => c.FirstName), - ss => ss.Set().Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName != null).Select(c => c.FirstName)); await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.EndsWith("_Ba_")).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.EndsWith("_Ba_")).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith("_Ba_")) == true).Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.EndsWith("%B%a%r")).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.EndsWith("%B%a%r")) == true) - .Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith("%B%a%r")) != true).Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.EndsWith("")).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.EndsWith("")) == true).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith("")) != true).Select(c => c.FirstName)); await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.EndsWith(null)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => true).Select(c => c.FirstName)); } [ConditionalTheory] @@ -353,12 +374,14 @@ public virtual async Task String_ends_with_on_argument_with_wildcard_parameter(b var prm1 = "%B"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.EndsWith(prm1)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.EndsWith(prm1)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith(prm1)) == true).Select(c => c.FirstName)); var prm2 = "a_"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.EndsWith(prm2)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.EndsWith(prm2)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith(prm2)) == true).Select(c => c.FirstName)); var prm3 = (string)null; await AssertQuery( @@ -370,30 +393,31 @@ await AssertQuery( await AssertQuery( async, ss => ss.Set().Where(c => c.FirstName.EndsWith(prm4)).Select(c => c.FirstName), - ss => ss.Set().Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith(prm4)) == true).Select(c => c.FirstName)); var prm5 = "_Ba_"; await AssertQuery( async, - ss => ss.Set().Where(c => c.FirstName.EndsWith(prm5)).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.EndsWith(prm5)).Select(c => c.FirstName), + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith(prm5)) == true).Select(c => c.FirstName)); var prm6 = "%B%a%r"; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.EndsWith(prm6)).Select(c => c.FirstName), - ss => ss.Set().Where(c => !c.FirstName.MaybeScalar(x => x.EndsWith(prm6)) == true).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName.MaybeScalar(x => x.EndsWith(prm6)) != true).Select(c => c.FirstName)); var prm7 = ""; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.EndsWith(prm7)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => c.FirstName == null).Select(c => c.FirstName)); var prm8 = (string)null; await AssertQuery( async, ss => ss.Set().Where(c => !c.FirstName.EndsWith(prm8)).Select(c => c.FirstName), - ss => ss.Set().Where(c => false).Select(c => c.FirstName)); + ss => ss.Set().Where(c => true).Select(c => c.FirstName)); } [ConditionalTheory] @@ -406,7 +430,7 @@ public virtual Task String_ends_with_on_argument_with_wildcard_column(bool async .Where(r => r.fn.EndsWith(r.ln)), ss => ss.Set().Select(c => c.FirstName) .SelectMany(c => ss.Set().Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) - .Where(r => r.ln == "" || r.fn.EndsWith(r.ln)), + .Where(r => r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.EndsWith(xx))) == true), elementSorter: e => (e.fn, e.ln), elementAsserter: (e, a) => { @@ -424,7 +448,7 @@ public virtual Task String_ends_with_on_argument_with_wildcard_column_negated(bo .Where(r => !r.fn.EndsWith(r.ln)), ss => ss.Set().Select(c => c.FirstName) .SelectMany(c => ss.Set().Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) - .Where(r => r.ln != "" && !r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.EndsWith(xx))) == true)); + .Where(r => !(r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.EndsWith(xx))) == true))); [ConditionalTheory] [MemberData(nameof(IsAsyncData))] @@ -436,7 +460,7 @@ public virtual Task String_ends_with_inside_conditional(bool async) .Where(r => r.fn.EndsWith(r.ln) ? true : false), ss => ss.Set().Select(c => c.FirstName) .SelectMany(c => ss.Set().Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) - .Where(r => r.ln == "" || r.fn.EndsWith(r.ln) ? true : false), + .Where(r => r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.EndsWith(xx))) == true), elementSorter: e => (e.fn, e.ln), elementAsserter: (e, a) => { @@ -455,7 +479,7 @@ public virtual Task String_ends_with_inside_conditional_negated(bool async) ss => ss.Set().Select(c => c.FirstName) .SelectMany(c => ss.Set().Select(c2 => c2.LastName), (fn, ln) => new { fn, ln }) .Where( - r => r.ln != "" && !r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.EndsWith(xx))) == true + r => !(r.fn.MaybeScalar(x => r.ln.MaybeScalar(xx => x.EndsWith(xx))) == true) ? true : false)); @@ -466,6 +490,8 @@ public virtual Task String_ends_with_equals_nullable_column(bool async) async, ss => ss.Set().SelectMany(c => ss.Set(), (c1, c2) => new { c1, c2 }) .Where(r => r.c1.FirstName.EndsWith(r.c2.LastName) == r.c1.NullableBool.Value), + ss => ss.Set().SelectMany(c => ss.Set(), (c1, c2) => new { c1, c2 }) + .Where(r => (r.c1.FirstName != null && r.c2.LastName != null && r.c1.FirstName.EndsWith(r.c2.LastName)) == r.c1.NullableBool), elementSorter: e => (e.c1.Id, e.c2.Id), elementAsserter: (e, a) => { @@ -480,6 +506,8 @@ public virtual Task String_ends_with_not_equals_nullable_column(bool async) async, ss => ss.Set().SelectMany(c => ss.Set(), (c1, c2) => new { c1, c2 }) .Where(r => r.c1.FirstName.EndsWith(r.c2.LastName) != r.c1.NullableBool.Value), + ss => ss.Set().SelectMany(c => ss.Set(), (c1, c2) => new { c1, c2 }) + .Where(r => (r.c1.FirstName != null && r.c2.LastName != null && r.c1.FirstName.EndsWith(r.c2.LastName)) != r.c1.NullableBool), elementSorter: e => (e.c1.Id, e.c2.Id), elementAsserter: (e, a) => { diff --git a/test/EFCore.Specification.Tests/Query/NorthwindFunctionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindFunctionsQueryTestBase.cs index 4a514b97812..ad7957ec1fd 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindFunctionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindFunctionsQueryTestBase.cs @@ -40,6 +40,18 @@ public virtual Task String_StartsWith_Literal(bool async) ss => ss.Set().Where(c => c.ContactName.StartsWith("M")), entryCount: 12); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_StartsWith_Parameter(bool async) + { + var pattern = "M"; + + return AssertQuery( + async, + ss => ss.Set().Where(c => c.ContactName.StartsWith(pattern)), + entryCount: 12); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task String_StartsWith_Identity(bool async) @@ -72,6 +84,18 @@ public virtual Task String_EndsWith_Literal(bool async) ss => ss.Set().Where(c => c.ContactName.EndsWith("b")), entryCount: 1); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task String_EndsWith_Parameter(bool async) + { + var pattern = "b"; + + return AssertQuery( + async, + ss => ss.Set().Where(c => c.ContactName.EndsWith(pattern)), + entryCount: 1); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task String_EndsWith_Identity(bool async) diff --git a/test/EFCore.Specification.Tests/TestUtilities/ExpectedQueryRewritingVisitor.cs b/test/EFCore.Specification.Tests/TestUtilities/ExpectedQueryRewritingVisitor.cs index 29b9aa43db5..b091a5a1d63 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/ExpectedQueryRewritingVisitor.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/ExpectedQueryRewritingVisitor.cs @@ -11,15 +11,6 @@ private static readonly MethodInfo _maybeDefaultIfEmpty private static readonly MethodInfo _maybeMethod = typeof(TestExtensions).GetMethod(nameof(TestExtensions.Maybe)); - private static readonly MethodInfo _containsMethodInfo - = typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) }); - - private static readonly MethodInfo _startsWithMethodInfo - = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) }); - - private static readonly MethodInfo _endsWithMethodInfo - = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) }); - private static readonly MethodInfo _getShadowPropertyValueMethodInfo = typeof(ExpectedQueryRewritingVisitor).GetMethod(nameof(GetShadowPropertyValue)); @@ -95,14 +86,6 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp return Visit(rewritten); } - if (!_negated - && (methodCallExpression.Method == _containsMethodInfo - || methodCallExpression.Method == _startsWithMethodInfo - || methodCallExpression.Method == _endsWithMethodInfo)) - { - return RewriteStartsWithEndsWithContains(methodCallExpression); - } - if (methodCallExpression.Method.IsGenericMethod && methodCallExpression.Method.GetGenericMethodDefinition() == EnumerableMethods.DefaultIfEmptyWithoutArgument) { @@ -187,41 +170,6 @@ private Expression RewriteJoinGroupJoin(MethodCallExpression methodCallExpressio resultSelector); } - private Expression RewriteStartsWithEndsWithContains(MethodCallExpression methodCallExpression) - { - // c.FirstName.StartsWith(c.Nickname) - // gets converted to: - // c.Maybe(x => x.FirstName).MaybeScalar(x => c.Maybe(xx => xx.Nickname).MaybeScalar(xx => x.StartsWith(xx))) - var caller = Visit(methodCallExpression.Object); - var argument = Visit(methodCallExpression.Arguments[0]); - var outerMaybeScalarMethod = _maybeScalarNullableMethod.MakeGenericMethod(typeof(string), typeof(bool)); - var innerMaybeScalarMethod = _maybeScalarNonNullableMethod.MakeGenericMethod(typeof(string), typeof(bool)); - - var outerMaybeScalarLambdaParameter = Expression.Parameter(typeof(string), "x"); - var innerMaybeScalarLambdaParameter = Expression.Parameter(typeof(string), "xx"); - var innerMaybeScalarLambda = Expression.Lambda( - methodCallExpression.Update( - outerMaybeScalarLambdaParameter, - new[] { innerMaybeScalarLambdaParameter }), - innerMaybeScalarLambdaParameter); - - var innerMaybeScalar = Expression.Call( - innerMaybeScalarMethod, - argument, - innerMaybeScalarLambda); - - var outerMaybeScalarLambda = Expression.Lambda( - innerMaybeScalar, - outerMaybeScalarLambdaParameter); - - var outerMaybeScalar = Expression.Call( - outerMaybeScalarMethod, - caller, - outerMaybeScalarLambda); - - return Expression.Equal(outerMaybeScalar, Expression.Constant(true, typeof(bool?))); - } - public static TResult GetShadowPropertyValue(TEntity entity, Func shadowPropertyAccessor) => (TResult)shadowPropertyAccessor(entity); diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs index 60687254374..7d7dbc1865d 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs @@ -182,6 +182,7 @@ private static int AssertResults(IList expected, IList actual) { Assert.Equal(expected.Count, actual.Count); + var x = actual.Where(a => !expected.Contains(a)).ToList(); foreach (var expectedItem in expected) { Assert.True( diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs index ec171520b59..d1cc09f2deb 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqlServerTest.cs @@ -116,7 +116,7 @@ public override async Task Delete_predicate_based_on_optional_navigation(bool as DELETE FROM [p] FROM [Posts] AS [p] LEFT JOIN [Blogs] AS [b] ON [p].[BlogId] = [b].[Id] -WHERE [b].[Title] IS NOT NULL AND [b].[Title] LIKE N'Arthur%' +WHERE [b].[Title] LIKE N'Arthur%' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs index 49430899ea3..a0718c8442f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs @@ -357,7 +357,7 @@ DELETE FROM [o] 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] -WHERE [c].[CustomerID] IS NOT NULL AND [c].[CustomerID] LIKE N'F%' +WHERE [c].[CustomerID] LIKE N'F%' """); } @@ -503,7 +503,7 @@ DELETE FROM [o] 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] -WHERE [c].[City] IS NOT NULL AND [c].[City] LIKE N'Se%' +WHERE [c].[City] LIKE N'Se%' """); } @@ -1324,7 +1324,7 @@ FROM [Customers] AS [c] CROSS JOIN ( SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] FROM [Customers] AS [c0] - WHERE [c0].[City] IS NOT NULL AND [c0].[City] LIKE N'S%' + WHERE [c0].[City] LIKE N'S%' ) AS [t] LEFT JOIN ( SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] @@ -1347,7 +1347,7 @@ FROM [Customers] AS [c] CROSS JOIN ( SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] FROM [Customers] AS [c0] - WHERE [c0].[City] IS NOT NULL AND [c0].[City] LIKE N'S%' + WHERE [c0].[City] LIKE N'S%' ) AS [t] CROSS APPLY ( SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] @@ -1370,7 +1370,7 @@ FROM [Customers] AS [c] CROSS JOIN ( SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] FROM [Customers] AS [c0] - WHERE [c0].[City] IS NOT NULL AND [c0].[City] LIKE N'S%' + WHERE [c0].[City] LIKE N'S%' ) AS [t] OUTER APPLY ( SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySqlServerTest.cs index 23df3b68b53..5a9185e5a47 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySqlServerTest.cs @@ -153,7 +153,7 @@ public override async Task Include_collection_with_conditional_order_by(bool asy FROM [LevelOne] AS [l] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] ORDER BY CASE - WHEN [l].[Name] IS NOT NULL AND [l].[Name] LIKE N'%03' THEN 1 + WHEN [l].[Name] LIKE N'%03' THEN 1 ELSE 2 END, [l].[Id] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSharedTypeQuerySqlServerTest.cs index 0a0688e125e..35a36800138 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSharedTypeQuerySqlServerTest.cs @@ -479,7 +479,7 @@ FROM [Level1] AS [l0] WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[OneToMany_Optional_Inverse2Id] ORDER BY CASE - WHEN [l].[Name] IS NOT NULL AND [l].[Name] LIKE N'%03' THEN 1 + WHEN [l].[Name] LIKE N'%03' THEN 1 ELSE 2 END, [l].[Id] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs index 250d0b88295..93c5c4db025 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs @@ -1900,7 +1900,7 @@ public override async Task Include_collection_with_conditional_order_by(bool asy 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] ORDER BY CASE - WHEN [l].[Name] IS NOT NULL AND [l].[Name] LIKE N'%03' THEN 1 + WHEN [l].[Name] LIKE N'%03' THEN 1 ELSE 2 END, [l].[Id] """, @@ -1910,7 +1910,7 @@ ELSE 2 FROM [LevelOne] AS [l] INNER JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] ORDER BY CASE - WHEN [l].[Name] IS NOT NULL AND [l].[Name] LIKE N'%03' THEN 1 + WHEN [l].[Name] LIKE N'%03' THEN 1 ELSE 2 END, [l].[Id] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index a5aae6bf1cb..0008919873a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -270,7 +270,7 @@ public override async Task Navigation_inside_method_call_translated_to_join(bool 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].[Name] IS NOT NULL AND [l0].[Name] LIKE N'L%' +WHERE [l0].[Name] LIKE N'L%' """); } @@ -283,7 +283,7 @@ public override async Task Navigation_inside_method_call_translated_to_join2(boo SELECT [l].[Id], [l].[Level2_Optional_Id], [l].[Level2_Required_Id], [l].[Name], [l].[OneToMany_Optional_Inverse3Id], [l].[OneToMany_Optional_Self_Inverse3Id], [l].[OneToMany_Required_Inverse3Id], [l].[OneToMany_Required_Self_Inverse3Id], [l].[OneToOne_Optional_PK_Inverse3Id], [l].[OneToOne_Optional_Self3Id] FROM [LevelThree] AS [l] INNER JOIN [LevelTwo] AS [l0] ON [l].[Level2_Required_Id] = [l0].[Id] -WHERE [l0].[Name] IS NOT NULL AND [l0].[Name] LIKE N'L%' +WHERE [l0].[Name] LIKE N'L%' """); } @@ -296,7 +296,7 @@ public override async Task Optional_navigation_inside_method_call_translated_to_ 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_Optional_Id] -WHERE [l0].[Name] IS NOT NULL AND [l0].[Name] LIKE N'L%' +WHERE [l0].[Name] LIKE N'L%' """); } @@ -322,7 +322,7 @@ public override async Task Optional_navigation_inside_nested_method_call_transla 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_Optional_Id] -WHERE [l0].[Name] IS NOT NULL AND UPPER([l0].[Name]) LIKE N'L%' +WHERE UPPER([l0].[Name]) LIKE N'L%' """); } @@ -335,7 +335,7 @@ public override async Task Method_call_on_optional_navigation_translates_to_null 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_Optional_Id] -WHERE [l0].[Name] = N'' OR ([l0].[Name] IS NOT NULL AND LEFT([l0].[Name], LEN([l0].[Name])) = [l0].[Name]) +WHERE [l0].[Name] IS NOT NULL AND LEFT([l0].[Name], LEN([l0].[Name])) = [l0].[Name] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index a6e86f40965..3cbd6c9b531 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -3127,7 +3127,7 @@ LEFT JOIN ( FROM [Level1] AS [l0] WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] -WHERE [t].[Level2_Name] IS NOT NULL AND [t].[Level2_Name] LIKE N'L%' +WHERE [t].[Level2_Name] LIKE N'L%' """); } @@ -4103,7 +4103,7 @@ LEFT JOIN ( FROM [Level1] AS [l0] WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] -WHERE [t].[Level2_Name] = N'' OR ([t].[Level2_Name] IS NOT NULL AND LEFT([t].[Level2_Name], LEN([t].[Level2_Name])) = [t].[Level2_Name]) +WHERE [t].[Level2_Name] IS NOT NULL AND LEFT([t].[Level2_Name], LEN([t].[Level2_Name])) = [t].[Level2_Name] """); } @@ -4417,7 +4417,7 @@ LEFT JOIN ( FROM [Level1] AS [l0] WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] -WHERE [t].[Level2_Name] IS NOT NULL AND UPPER([t].[Level2_Name]) LIKE N'L%' +WHERE UPPER([t].[Level2_Name]) LIKE N'L%' """); } @@ -4457,7 +4457,7 @@ LEFT JOIN ( FROM [Level1] AS [l0] WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[Level1_Required_Id] -WHERE [t].[Level2_Name] IS NOT NULL AND [t].[Level2_Name] LIKE N'L%' +WHERE [t].[Level2_Name] LIKE N'L%' """); } @@ -6531,7 +6531,7 @@ WHERE [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] ) AS [t1] ON [t0].[Level2_Required_Id] = CASE WHEN [t1].[OneToOne_Required_PK_Date] IS NOT NULL AND [t1].[Level1_Required_Id] IS NOT NULL AND [t1].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [t1].[Id] END -WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [t0].[Level2_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [t1].[Level2_Name] IS NOT NULL AND [t1].[Level2_Name] LIKE N'L%' +WHERE [t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL AND [t0].[Level2_Required_Id] IS NOT NULL AND [t0].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [t1].[Level2_Name] LIKE N'L%' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs index 481d440880a..49f7418e96b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs @@ -39,12 +39,13 @@ WHERE [f].[FirstName] LIKE N'%a\_%' ESCAPE N'\' """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] LIKE NULL +WHERE 0 = 1 """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] +WHERE [f].[FirstName] IS NOT NULL """, // """ @@ -56,19 +57,18 @@ WHERE [f].[FirstName] LIKE N'%\_Ba\_%' ESCAPE N'\' """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] NOT LIKE N'%\%B\%a\%r%' ESCAPE N'\' +WHERE [f].[FirstName] NOT LIKE N'%\%B\%a\%r%' ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE 0 = 1 +WHERE [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] NOT LIKE NULL """); } @@ -78,63 +78,62 @@ public override async Task String_contains_on_argument_with_wildcard_parameter(b AssertSql( """ -@__prm1_0='%B' (Size = 4000) +@__prm1_0_rewritten='%\%B%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm1_0 LIKE N'' OR CHARINDEX(@__prm1_0, [f].[FirstName]) > 0 +WHERE [f].[FirstName] LIKE @__prm1_0_rewritten ESCAPE N'\' """, // """ -@__prm2_0='a_' (Size = 4000) +@__prm2_0_rewritten='%a\_%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm2_0 LIKE N'' OR CHARINDEX(@__prm2_0, [f].[FirstName]) > 0 +WHERE [f].[FirstName] LIKE @__prm2_0_rewritten ESCAPE N'\' """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE NULL LIKE N'' OR CHARINDEX(NULL, [f].[FirstName]) > 0 +WHERE 0 = 1 """, // """ -@__prm4_0='' (Size = 4000) +@__prm4_0_rewritten='%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm4_0 LIKE N'' OR CHARINDEX(@__prm4_0, [f].[FirstName]) > 0 +WHERE [f].[FirstName] LIKE @__prm4_0_rewritten ESCAPE N'\' """, // """ -@__prm5_0='_Ba_' (Size = 4000) +@__prm5_0_rewritten='%\_Ba\_%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm5_0 LIKE N'' OR CHARINDEX(@__prm5_0, [f].[FirstName]) > 0 +WHERE [f].[FirstName] LIKE @__prm5_0_rewritten ESCAPE N'\' """, // """ -@__prm6_0='%B%a%r' (Size = 4000) +@__prm6_0_rewritten='%\%B\%a\%r%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE NOT (@__prm6_0 LIKE N'' OR CHARINDEX(@__prm6_0, [f].[FirstName]) > 0) +WHERE [f].[FirstName] NOT LIKE @__prm6_0_rewritten ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ -@__prm7_0='' (Size = 4000) +@__prm7_0_rewritten='%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE NOT (@__prm7_0 LIKE N'' OR CHARINDEX(@__prm7_0, [f].[FirstName]) > 0) +WHERE [f].[FirstName] NOT LIKE @__prm7_0_rewritten ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE NOT (NULL LIKE N'' OR CHARINDEX(NULL, [f].[FirstName]) > 0) """); } @@ -143,11 +142,11 @@ public override async Task String_contains_on_argument_with_wildcard_column(bool await base.String_contains_on_argument_with_wildcard_column(async); AssertSql( -""" + """ SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE [f0].[LastName] LIKE N'' OR CHARINDEX([f0].[LastName], [f].[FirstName]) > 0 +WHERE [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND (CHARINDEX([f0].[LastName], [f].[FirstName]) > 0 OR [f0].[LastName] LIKE N'') """); } @@ -160,7 +159,7 @@ public override async Task String_contains_on_argument_with_wildcard_column_nega SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE NOT ([f0].[LastName] LIKE N'' OR CHARINDEX([f0].[LastName], [f].[FirstName]) > 0) +WHERE NOT ([f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND (CHARINDEX([f0].[LastName], [f].[FirstName]) > 0 OR [f0].[LastName] LIKE N'')) """); } @@ -172,13 +171,13 @@ public override async Task String_starts_with_on_argument_with_wildcard_constant """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'\%B%' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'\%B%' ESCAPE N'\' """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'a\_%' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'a\_%' ESCAPE N'\' """, // """ @@ -190,30 +189,30 @@ FROM [FunkyCustomers] AS [f] """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] +WHERE [f].[FirstName] IS NOT NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'\_Ba\_%' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'\_Ba\_%' ESCAPE N'\' """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] NOT LIKE N'\%B\%a\%r%' ESCAPE N'\' +WHERE [f].[FirstName] NOT LIKE N'\%B\%a\%r%' ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE 0 = 1 +WHERE [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE 0 = 1 """); } @@ -223,19 +222,19 @@ public override async Task String_starts_with_on_argument_with_wildcard_paramete AssertSql( """ -@__prm1_0='%B' (Size = 4000) +@__prm1_0_rewritten='\%B%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm1_0 = N'' OR ([f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0) +WHERE [f].[FirstName] LIKE @__prm1_0_rewritten ESCAPE N'\' """, // """ -@__prm2_0='a_' (Size = 4000) +@__prm2_0_rewritten='a\_%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm2_0 = N'' OR ([f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0) +WHERE [f].[FirstName] LIKE @__prm2_0_rewritten ESCAPE N'\' """, // """ @@ -245,41 +244,40 @@ FROM [FunkyCustomers] AS [f] """, // """ -@__prm4_0='' (Size = 4000) +@__prm4_0_rewritten='%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm4_0 = N'' OR ([f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm4_0)) = @__prm4_0) +WHERE [f].[FirstName] LIKE @__prm4_0_rewritten ESCAPE N'\' """, // """ -@__prm5_0='_Ba_' (Size = 4000) +@__prm5_0_rewritten='\_Ba\_%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm5_0 = N'' OR ([f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm5_0)) = @__prm5_0) +WHERE [f].[FirstName] LIKE @__prm5_0_rewritten ESCAPE N'\' """, // """ -@__prm6_0='%B%a%r' (Size = 4000) +@__prm6_0_rewritten='\%B\%a\%r%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm6_0 <> N'' AND [f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0 +WHERE [f].[FirstName] NOT LIKE @__prm6_0_rewritten ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ -@__prm7_0='' (Size = 4000) +@__prm7_0_rewritten='%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm7_0 <> N'' AND [f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0 +WHERE [f].[FirstName] NOT LIKE @__prm7_0_rewritten ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE 0 = 1 """); } @@ -291,49 +289,49 @@ public override async Task String_starts_with_on_argument_with_bracket(bool asyn """ SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'\[%' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'\[%' ESCAPE N'\' """, // """ SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'B\[%' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'B\[%' ESCAPE N'\' """, // """ SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'B\[\[a^%' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'B\[\[a^%' ESCAPE N'\' """, // """ -@__prm1_0='[' (Size = 4000) +@__prm1_0_rewritten='\[%' (Size = 4000) SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE @__prm1_0 = N'' OR ([f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0) +WHERE [f].[FirstName] LIKE @__prm1_0_rewritten ESCAPE N'\' """, // """ -@__prm2_0='B[' (Size = 4000) +@__prm2_0_rewritten='B\[%' (Size = 4000) SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE @__prm2_0 = N'' OR ([f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0) +WHERE [f].[FirstName] LIKE @__prm2_0_rewritten ESCAPE N'\' """, // """ -@__prm3_0='B[[a^' (Size = 4000) +@__prm3_0_rewritten='B\[\[a^%' (Size = 4000) SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE @__prm3_0 = N'' OR ([f].[FirstName] IS NOT NULL AND LEFT([f].[FirstName], LEN(@__prm3_0)) = @__prm3_0) +WHERE [f].[FirstName] LIKE @__prm3_0_rewritten ESCAPE N'\' """, // """ SELECT [f].[Id], [f].[FirstName], [f].[LastName], [f].[NullableBool] FROM [FunkyCustomers] AS [f] -WHERE [f].[LastName] = N'' OR ([f].[FirstName] IS NOT NULL AND [f].[LastName] IS NOT NULL AND LEFT([f].[FirstName], LEN([f].[LastName])) = [f].[LastName]) +WHERE [f].[FirstName] IS NOT NULL AND [f].[LastName] IS NOT NULL AND LEFT([f].[FirstName], LEN([f].[LastName])) = [f].[LastName] """); } @@ -346,7 +344,7 @@ public override async Task String_starts_with_on_argument_with_wildcard_column(b SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE [f0].[LastName] = N'' OR ([f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND LEFT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]) +WHERE [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND LEFT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName] """); } @@ -359,7 +357,7 @@ public override async Task String_starts_with_on_argument_with_wildcard_column_n SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE ([f0].[LastName] <> N'' OR [f0].[LastName] IS NULL) AND [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND LEFT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName] +WHERE [f].[FirstName] IS NULL OR [f0].[LastName] IS NULL OR LEFT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName] """); } @@ -371,13 +369,13 @@ public override async Task String_ends_with_on_argument_with_wildcard_constant(b """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'%\%B' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'%\%B' ESCAPE N'\' """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'%a\_' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'%a\_' ESCAPE N'\' """, // """ @@ -389,30 +387,30 @@ FROM [FunkyCustomers] AS [f] """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] +WHERE [f].[FirstName] IS NOT NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] LIKE N'%\_Ba\_' ESCAPE N'\' +WHERE [f].[FirstName] LIKE N'%\_Ba\_' ESCAPE N'\' """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE [f].[FirstName] IS NOT NULL AND [f].[FirstName] NOT LIKE N'%\%B\%a\%r' ESCAPE N'\' +WHERE [f].[FirstName] NOT LIKE N'%\%B\%a\%r' ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE 0 = 1 +WHERE [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE 0 = 1 """); } @@ -422,19 +420,19 @@ public override async Task String_ends_with_on_argument_with_wildcard_parameter( AssertSql( """ -@__prm1_0='%B' (Size = 4000) +@__prm1_0_rewritten='%\%B' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm1_0 = N'' OR ([f].[FirstName] IS NOT NULL AND RIGHT([f].[FirstName], LEN(@__prm1_0)) = @__prm1_0) +WHERE [f].[FirstName] LIKE @__prm1_0_rewritten ESCAPE N'\' """, // """ -@__prm2_0='a_' (Size = 4000) +@__prm2_0_rewritten='%a\_' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm2_0 = N'' OR ([f].[FirstName] IS NOT NULL AND RIGHT([f].[FirstName], LEN(@__prm2_0)) = @__prm2_0) +WHERE [f].[FirstName] LIKE @__prm2_0_rewritten ESCAPE N'\' """, // """ @@ -444,41 +442,40 @@ FROM [FunkyCustomers] AS [f] """, // """ -@__prm4_0='' (Size = 4000) +@__prm4_0_rewritten='%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm4_0 = N'' OR ([f].[FirstName] IS NOT NULL AND RIGHT([f].[FirstName], LEN(@__prm4_0)) = @__prm4_0) +WHERE [f].[FirstName] LIKE @__prm4_0_rewritten ESCAPE N'\' """, // """ -@__prm5_0='_Ba_' (Size = 4000) +@__prm5_0_rewritten='%\_Ba\_' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm5_0 = N'' OR ([f].[FirstName] IS NOT NULL AND RIGHT([f].[FirstName], LEN(@__prm5_0)) = @__prm5_0) +WHERE [f].[FirstName] LIKE @__prm5_0_rewritten ESCAPE N'\' """, // """ -@__prm6_0='%B%a%r' (Size = 4000) +@__prm6_0_rewritten='%\%B\%a\%r' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm6_0 <> N'' AND [f].[FirstName] IS NOT NULL AND RIGHT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0 +WHERE [f].[FirstName] NOT LIKE @__prm6_0_rewritten ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ -@__prm7_0='' (Size = 4000) +@__prm7_0_rewritten='%' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE @__prm7_0 <> N'' AND [f].[FirstName] IS NOT NULL AND RIGHT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0 +WHERE [f].[FirstName] NOT LIKE @__prm7_0_rewritten ESCAPE N'\' OR [f].[FirstName] IS NULL """, // """ SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE 0 = 1 """); } @@ -491,7 +488,7 @@ public override async Task String_ends_with_on_argument_with_wildcard_column(boo SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE [f0].[LastName] = N'' OR ([f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]) +WHERE [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName] """); } @@ -504,7 +501,7 @@ public override async Task String_ends_with_on_argument_with_wildcard_column_neg SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE ([f0].[LastName] <> N'' OR [f0].[LastName] IS NULL) AND [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName] +WHERE [f].[FirstName] IS NULL OR [f0].[LastName] IS NULL OR RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName] """); } @@ -518,7 +515,7 @@ public override async Task String_ends_with_inside_conditional(bool async) FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE CASE - WHEN [f0].[LastName] = N'' OR ([f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]) THEN CAST(1 AS bit) + WHEN [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName] THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); @@ -534,7 +531,7 @@ public override async Task String_ends_with_inside_conditional_negated(bool asyn FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE CASE - WHEN ([f0].[LastName] <> N'' OR [f0].[LastName] IS NULL) AND [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName] THEN CAST(1 AS bit) + WHEN [f].[FirstName] IS NULL OR [f0].[LastName] IS NULL OR RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName] THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit) """); @@ -550,7 +547,7 @@ public override async Task String_ends_with_equals_nullable_column(bool async) FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE CASE - WHEN ([f0].[LastName] = N'' AND [f0].[LastName] IS NOT NULL) OR ([f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]) THEN CAST(1 AS bit) + WHEN [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName] THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = [f].[NullableBool] """); @@ -566,7 +563,7 @@ public override async Task String_ends_with_not_equals_nullable_column(bool asyn FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE CASE - WHEN ([f0].[LastName] = N'' AND [f0].[LastName] IS NOT NULL) OR ([f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]) THEN CAST(1 AS bit) + WHEN [f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName] THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END <> [f].[NullableBool] OR [f].[NullableBool] IS NULL """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index c2d67045a07..e66a5169093 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -962,7 +962,7 @@ FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL ELSE CASE - WHEN [g].[LeaderNickname] IS NOT NULL AND [g].[LeaderNickname] LIKE N'%us' THEN CAST(1 AS bit) + WHEN [g].[LeaderNickname] LIKE N'%us' AND [g].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END END = CAST(1 AS bit) @@ -2431,7 +2431,7 @@ public override async Task Optional_navigation_type_compensation_works_with_bina AssertSql( """ SELECT CASE - WHEN [g].[HasSoulPatch] = CAST(1 AS bit) AND [t].[Note] LIKE N'%Cole%' THEN CAST(1 AS bit) + WHEN [g].[HasSoulPatch] = CAST(1 AS bit) AND [t].[Note] LIKE N'%Cole%' AND [t].[Note] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END FROM [Tags] AS [t] @@ -5952,7 +5952,7 @@ public override async Task Double_order_by_on_Like(bool async) FROM [Weapons] AS [w] LEFT JOIN [Weapons] AS [w0] ON [w].[SynergyWithId] = [w0].[Id] ORDER BY CASE - WHEN [w0].[Name] LIKE N'%Lancer' THEN CAST(1 AS bit) + WHEN [w0].[Name] LIKE N'%Lancer' AND [w0].[Name] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyNoTrackingQuerySqlServerTest.cs index 4a9bf7debdc..45da7fb88a7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyNoTrackingQuerySqlServerTest.cs @@ -28,7 +28,7 @@ WHERE NOT EXISTS ( SELECT 1 FROM [JoinOneToTwo] AS [j] INNER JOIN [EntityTwos] AS [e0] ON [j].[TwoId] = [e0].[Id] - WHERE [e].[Id] = [j].[OneId] AND [e0].[Name] NOT LIKE N'%B%') + WHERE [e].[Id] = [j].[OneId] AND ([e0].[Name] NOT LIKE N'%B%' OR [e0].[Name] IS NULL)) """); } @@ -112,7 +112,7 @@ INNER JOIN ( FROM [EntityRoots] AS [e0] WHERE [e0].[Discriminator] IN (N'EntityBranch', N'EntityLeaf') ) AS [t] ON [j].[EntityBranchId] = [t].[Id] - WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [e].[Id] + WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] LIKE N'L%'), [e].[Id] """); } @@ -144,7 +144,7 @@ ORDER BY ( SELECT COUNT_BIG(*) FROM [EntityTwoEntityTwo] AS [e0] INNER JOIN [EntityTwos] AS [e1] ON [e0].[SelfSkipSharedLeftId] = [e1].[Id] - WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] IS NOT NULL AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] + WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs index 5733cffca5e..18cf793bb43 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ManyToManyQuerySqlServerTest.cs @@ -27,7 +27,7 @@ WHERE NOT EXISTS ( SELECT 1 FROM [JoinOneToTwo] AS [j] INNER JOIN [EntityTwos] AS [e0] ON [j].[TwoId] = [e0].[Id] - WHERE [e].[Id] = [j].[OneId] AND [e0].[Name] NOT LIKE N'%B%') + WHERE [e].[Id] = [j].[OneId] AND ([e0].[Name] NOT LIKE N'%B%' OR [e0].[Name] IS NULL)) """); } @@ -111,7 +111,7 @@ INNER JOIN ( FROM [EntityRoots] AS [e0] WHERE [e0].[Discriminator] IN (N'EntityBranch', N'EntityLeaf') ) AS [t] ON [j].[EntityBranchId] = [t].[Id] - WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [e].[Id] + WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] LIKE N'L%'), [e].[Id] """); } @@ -143,7 +143,7 @@ ORDER BY ( SELECT COUNT_BIG(*) FROM [EntityTwoEntityTwo] AS [e0] INNER JOIN [EntityTwos] AS [e1] ON [e0].[SelfSkipSharedLeftId] = [e1].[Id] - WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] IS NOT NULL AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] + WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs index 863b77077ce..d6ea290245a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs @@ -1973,7 +1973,7 @@ public override async Task Average_with_non_matching_types_in_projection_doesnt_ """ SELECT AVG(CAST(CAST([o].[OrderID] AS bigint) AS float)) FROM [Orders] AS [o] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' +WHERE [o].[CustomerID] LIKE N'A%' """); } @@ -1985,7 +1985,7 @@ public override async Task Max_with_non_matching_types_in_projection_introduces_ """ SELECT MAX(CAST([o].[OrderID] AS bigint)) FROM [Orders] AS [o] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' +WHERE [o].[CustomerID] LIKE N'A%' """); } @@ -1997,7 +1997,7 @@ public override async Task Min_with_non_matching_types_in_projection_introduces_ """ SELECT MIN(CAST([o].[OrderID] AS bigint)) FROM [Orders] AS [o] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' +WHERE [o].[CustomerID] LIKE N'A%' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySqlServerTest.cs index 01c2b65a117..33f2330c27a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindEFPropertyIncludeQuerySqlServerTest.cs @@ -135,7 +135,7 @@ public override async Task Include_collection_alias_generation(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice] FROM [Orders] AS [o] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [o0].[OrderID] """); } @@ -234,7 +234,7 @@ public override async Task Include_references_then_include_collection(bool async FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } @@ -1136,7 +1136,7 @@ public override async Task Include_reference_when_entity_in_projection(bool asyn SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -1520,7 +1520,7 @@ public override async Task Include_reference(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -1750,7 +1750,7 @@ public override async Task Include_collection_and_reference(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID], [o0].[OrderID] """); } @@ -1891,7 +1891,7 @@ public override async Task Include_reference_and_collection(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID], [o0].[OrderID] """); } @@ -1962,7 +1962,7 @@ public override async Task Include_reference_and_collection_order_by(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs index 7bd4e81f5d4..900ad38e315 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs @@ -86,7 +86,21 @@ public override async Task String_StartsWith_Literal(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] IS NOT NULL AND [c].[ContactName] LIKE N'M%' +WHERE [c].[ContactName] LIKE N'M%' +"""); + } + + public override async Task String_StartsWith_Parameter(bool async) + { + await base.String_StartsWith_Parameter(async); + + AssertSql( +""" +@__pattern_0_rewritten='M%' (Size = 30) + +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].[ContactName] LIKE @__pattern_0_rewritten ESCAPE N'\' """); } @@ -98,7 +112,7 @@ public override async Task String_StartsWith_Identity(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] = N'' OR ([c].[ContactName] IS NOT NULL AND LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]) +WHERE [c].[ContactName] IS NOT NULL AND LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName] """); } @@ -110,7 +124,7 @@ public override async Task String_StartsWith_Column(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] = N'' OR ([c].[ContactName] IS NOT NULL AND LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]) +WHERE [c].[ContactName] IS NOT NULL AND LEFT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName] """); } @@ -122,7 +136,7 @@ public override async Task String_StartsWith_MethodCall(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] IS NOT NULL AND [c].[ContactName] LIKE N'M%' +WHERE [c].[ContactName] LIKE N'M%' """); } @@ -134,7 +148,21 @@ public override async Task String_EndsWith_Literal(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] IS NOT NULL AND [c].[ContactName] LIKE N'%b' +WHERE [c].[ContactName] LIKE N'%b' +"""); + } + + public override async Task String_EndsWith_Parameter(bool async) + { + await base.String_EndsWith_Parameter(async); + + AssertSql( +""" +@__pattern_0_rewritten='%b' (Size = 30) + +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].[ContactName] LIKE @__pattern_0_rewritten ESCAPE N'\' """); } @@ -146,7 +174,7 @@ public override async Task String_EndsWith_Identity(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] = N'' OR ([c].[ContactName] IS NOT NULL AND RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]) +WHERE [c].[ContactName] IS NOT NULL AND RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName] """); } @@ -158,7 +186,7 @@ public override async Task String_EndsWith_Column(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] = N'' OR ([c].[ContactName] IS NOT NULL AND RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName]) +WHERE [c].[ContactName] IS NOT NULL AND RIGHT([c].[ContactName], LEN([c].[ContactName])) = [c].[ContactName] """); } @@ -170,7 +198,7 @@ public override async Task String_EndsWith_MethodCall(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] IS NOT NULL AND [c].[ContactName] LIKE N'%m' +WHERE [c].[ContactName] LIKE N'%m' """); } @@ -198,7 +226,7 @@ public override async Task String_Contains_Identity(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] LIKE N'' OR CHARINDEX([c].[ContactName], [c].[ContactName]) > 0 +WHERE [c].[ContactName] IS NOT NULL AND (CHARINDEX([c].[ContactName], [c].[ContactName]) > 0 OR [c].[ContactName] LIKE N'') """); } @@ -210,7 +238,7 @@ public override async Task String_Contains_Column(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] FROM [Customers] AS [c] -WHERE [c].[ContactName] LIKE N'' OR CHARINDEX([c].[ContactName], [c].[ContactName]) > 0 +WHERE [c].[ContactName] IS NOT NULL AND (CHARINDEX([c].[ContactName], [c].[ContactName]) > 0 OR [c].[ContactName] LIKE N'') """); } @@ -232,11 +260,11 @@ public override async Task String_Contains_parameter_with_whitespace(bool async) AssertSql( """ -@__pattern_0=' ' (Size = 30) +@__pattern_0_rewritten='% %' (Size = 30) 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 @__pattern_0 LIKE N'' OR CHARINDEX(@__pattern_0, [c].[ContactName]) > 0 +WHERE [c].[ContactName] LIKE @__pattern_0_rewritten ESCAPE N'\' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs index e78b59cca9f..e3275d1dc6e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs @@ -2035,7 +2035,7 @@ SELECT COUNT(*) AS [c], [p0].[ProductID] FROM [Products] AS [p0] GROUP BY [p0].[ProductID] ) AS [t0] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' +WHERE [o].[CustomerID] LIKE N'A%' ORDER BY [o].[OrderID], [t].[ProductID] """); } @@ -3179,7 +3179,7 @@ public override async Task GroupBy_with_grouping_key_using_Like(bool async) SELECT [t].[Key], COUNT(*) AS [Count] FROM ( SELECT CASE - WHEN [o].[CustomerID] LIKE N'A%' THEN CAST(1 AS bit) + WHEN [o].[CustomerID] LIKE N'A%' AND [o].[CustomerID] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS [Key] FROM [Orders] AS [o] @@ -3294,7 +3294,7 @@ ELSE 0 ELSE 0 END), 0) AS [Sum2] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' +WHERE [o].[CustomerID] LIKE N'A%' GROUP BY [o].[CustomerID] """); } @@ -3478,7 +3478,7 @@ public override async Task Select_uncorrelated_collection_with_groupby_when_oute SELECT DISTINCT [c].[City] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' + WHERE [o].[CustomerID] LIKE N'A%' ) AS [t] OUTER APPLY ( SELECT [p].[ProductID] @@ -3523,7 +3523,7 @@ public override async Task Select_correlated_collection_after_GroupBy_aggregate_ SELECT [o].[CustomerID] FROM [Orders] AS [o] GROUP BY [o].[CustomerID] - HAVING [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' + HAVING [o].[CustomerID] LIKE N'F%' ) AS [t] LEFT JOIN [Orders] AS [o0] ON [t].[CustomerID] = [o0].[CustomerID] ORDER BY [t].[CustomerID] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySqlServerTest.cs index 65b2dbb52e9..a5199cb8e17 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeNoTrackingQuerySqlServerTest.cs @@ -139,7 +139,7 @@ public override async Task Include_reference_and_collection(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID], [o0].[OrderID] """); } @@ -206,7 +206,7 @@ public override async Task Include_collection_alias_generation(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice] FROM [Orders] AS [o] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [o0].[OrderID] """); } @@ -309,7 +309,7 @@ public override async Task Include_reference_when_entity_in_projection(bool asyn SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -1499,7 +1499,7 @@ public override async Task Include_collection_and_reference(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID], [o0].[OrderID] """); } @@ -1566,7 +1566,7 @@ public override async Task Include_reference(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs index 85becbcde38..026d4b3a4f1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs @@ -51,7 +51,7 @@ public override async Task Include_reference(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -170,7 +170,7 @@ public override async Task Include_reference_and_collection(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID], [o0].[OrderID] """); } @@ -301,7 +301,7 @@ public override async Task Include_collection_alias_generation(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice] FROM [Orders] AS [o] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [o0].[OrderID] """); } @@ -2022,7 +2022,7 @@ public override async Task Include_reference_and_collection_order_by(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } @@ -2075,7 +2075,7 @@ public override async Task Include_references_then_include_collection(bool async FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } @@ -2090,7 +2090,7 @@ public override async Task Include_collection_and_reference(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID], [o0].[OrderID] """); } @@ -2186,7 +2186,7 @@ public override async Task Include_reference_when_entity_in_projection(bool asyn SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindJoinQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindJoinQuerySqlServerTest.cs index 6fae0e31757..199b7471193 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindJoinQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindJoinQuerySqlServerTest.cs @@ -232,7 +232,7 @@ public override async Task Join_same_collection_force_alias_uniquefication(bool SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] FROM [Orders] AS [o] INNER JOIN [Orders] AS [o0] ON [o].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -402,7 +402,7 @@ FROM [Employees] AS [e] LEFT JOIN ( SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' + WHERE [o].[CustomerID] LIKE N'F%' ) AS [t] ON [e].[EmployeeID] = [t].[EmployeeID] """); } @@ -575,7 +575,7 @@ SELECT TOP(@__p_0) [o].[OrderID], [o].[CustomerID] FROM [Orders] AS [o] ORDER BY [o].[OrderID] ) AS [t] - WHERE [t].[CustomerID] IS NOT NULL AND [t].[CustomerID] LIKE N'A%' + WHERE [t].[CustomerID] LIKE N'A%' ) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 398e6167183..5da6226defc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -1488,7 +1488,7 @@ SELECT CASE WHEN EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE [c].[ContactName] IS NOT NULL AND [c].[ContactName] LIKE N'A%') THEN CAST(1 AS bit) + WHERE [c].[ContactName] LIKE N'A%') THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -1505,7 +1505,7 @@ FROM [Customers] AS [c] WHERE NOT EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%') + WHERE [o].[CustomerID] LIKE N'A%') """); } @@ -1520,7 +1520,7 @@ FROM [Customers] AS [c] WHERE ([c].[City] <> N'London' OR [c].[City] IS NULL) AND NOT EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%') + WHERE [o].[CustomerID] LIKE N'A%') """); } @@ -1535,7 +1535,7 @@ FROM [Customers] AS [c] WHERE NOT EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%') AND ([c].[City] <> N'London' OR [c].[City] IS NULL) + WHERE [o].[CustomerID] LIKE N'A%') AND ([c].[City] <> N'London' OR [c].[City] IS NULL) """); } @@ -1550,7 +1550,7 @@ FROM [Customers] AS [c] WHERE EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%') + WHERE [o].[CustomerID] LIKE N'A%') """); } @@ -1565,7 +1565,7 @@ FROM [Customers] AS [c] WHERE ([c].[City] <> N'London' OR [c].[City] IS NULL) AND EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%') + WHERE [o].[CustomerID] LIKE N'A%') """); } @@ -1580,7 +1580,7 @@ FROM [Customers] AS [c] WHERE EXISTS ( SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%') AND ([c].[City] <> N'London' OR [c].[City] IS NULL) + WHERE [o].[CustomerID] LIKE N'A%') AND ([c].[City] <> N'London' OR [c].[City] IS NULL) """); } @@ -1609,7 +1609,7 @@ SELECT CASE WHEN NOT EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE [c].[ContactName] IS NULL OR [c].[ContactName] NOT LIKE N'A%') THEN CAST(1 AS bit) + WHERE [c].[ContactName] NOT LIKE N'A%' OR [c].[ContactName] IS NULL) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -1625,7 +1625,7 @@ SELECT CASE WHEN NOT EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE ([c].[ContactName] <> N'' OR [c].[ContactName] IS NULL) AND ([c].[ContactName] IS NULL OR LEFT([c].[ContactName], LEN([c].[ContactName])) <> [c].[ContactName])) THEN CAST(1 AS bit) + WHERE [c].[ContactName] IS NULL OR LEFT([c].[ContactName], LEN([c].[ContactName])) <> [c].[ContactName]) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -3041,12 +3041,12 @@ public override async Task Environment_newline_is_funcletized(bool async) AssertSql( """ -@__NewLine_0=' -' (Size = 5) +@__NewLine_0_rewritten='% +%' (Size = 5) 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 @__NewLine_0 LIKE N'' OR CHARINDEX(@__NewLine_0, [c].[CustomerID]) > 0 +WHERE [c].[CustomerID] LIKE @__NewLine_0_rewritten ESCAPE N'\' """); } @@ -4786,12 +4786,11 @@ public override async Task Comparing_to_fixed_string_parameter(bool async) AssertSql( """ -@__prefix_0='A' (Size = 4000) -@__prefix_0_1='A' (Size = 5) +@__prefix_0_rewritten='A%' (Size = 5) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE @__prefix_0 = N'' OR LEFT([c].[CustomerID], LEN(@__prefix_0_1)) = @__prefix_0_1 +WHERE [c].[CustomerID] LIKE @__prefix_0_rewritten ESCAPE N'\' """); } @@ -4846,7 +4845,7 @@ FROM [Orders] AS [o] CROSS JOIN [Orders] AS [o0] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Customers] AS [c0] ON [o0].[CustomerID] = [c0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' AND ([c].[CustomerID] = [c0].[CustomerID] OR ([c].[CustomerID] IS NULL AND [c0].[CustomerID] IS NULL)) +WHERE [o].[CustomerID] LIKE N'A%' AND ([c].[CustomerID] = [c0].[CustomerID] OR ([c].[CustomerID] IS NULL AND [c0].[CustomerID] IS NULL)) ORDER BY [o].[OrderID], [o0].[OrderID] """); } @@ -4862,7 +4861,7 @@ FROM [Orders] AS [o] CROSS JOIN [Orders] AS [o0] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Customers] AS [c0] ON [o0].[CustomerID] = [c0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' AND ([c].[CustomerID] = [c0].[CustomerID] OR ([c].[CustomerID] IS NULL AND [c0].[CustomerID] IS NULL)) +WHERE [o].[CustomerID] LIKE N'A%' AND ([c].[CustomerID] = [c0].[CustomerID] OR ([c].[CustomerID] IS NULL AND [c0].[CustomerID] IS NULL)) ORDER BY [o].[OrderID], [o0].[OrderID] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs index bc446aa0398..f313a3f9398 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindNavigationsQuerySqlServerTest.cs @@ -384,7 +384,7 @@ FROM [Orders] AS [o] CROSS JOIN [Orders] AS [o0] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Customers] AS [c0] ON [o0].[CustomerID] = [c0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' AND [o0].[CustomerID] IS NOT NULL AND [o0].[CustomerID] LIKE N'A%' AND ([c].[CustomerID] = [c0].[CustomerID] OR ([c].[CustomerID] IS NULL AND [c0].[CustomerID] IS NULL)) +WHERE [o].[CustomerID] LIKE N'A%' AND [o0].[CustomerID] LIKE N'A%' AND ([c].[CustomerID] = [c0].[CustomerID] OR ([c].[CustomerID] IS NULL AND [c0].[CustomerID] IS NULL)) """); } @@ -701,7 +701,7 @@ SELECT COUNT_BIG(*) FROM [Order Details] AS [o3] WHERE [o].[OrderID] = [o3].[OrderID]) AS [collection2] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' +WHERE [o].[CustomerID] LIKE N'A%' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs index 2959a15f325..f4491f4cd95 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs @@ -27,12 +27,11 @@ public override async Task Count_query(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) SELECT COUNT(*) FROM [Customers] AS [c] -WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' """); } @@ -42,12 +41,11 @@ public override async Task Materialized_query(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) 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 @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' """); } @@ -57,13 +55,12 @@ public override async Task Find(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) @__p_0='ALFKI' (Size = 5) (DbType = StringFixedLength) SELECT TOP(1) [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 (@__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1) AND [c].[CustomerID] = @__p_0 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' AND [c].[CustomerID] = @__p_0 """); } @@ -73,12 +70,11 @@ public override async Task Materialized_query_parameter(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='F' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='F' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='F%' (Size = 40) 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 @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' """); } @@ -88,21 +84,19 @@ public override async Task Materialized_query_parameter_new_context(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) 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 @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' """, // """ -@__ef_filter__TenantPrefix_0='T' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='T' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='T%' (Size = 40) 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 @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' """); } @@ -112,12 +106,11 @@ public override async Task Projection_query_parameter(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='F' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='F' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='F%' (Size = 40) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' """); } @@ -127,12 +120,11 @@ public override async Task Projection_query(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' """); } @@ -142,8 +134,7 @@ public override async Task Include_query(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0] FROM [Customers] AS [c] @@ -153,11 +144,11 @@ FROM [Orders] AS [o] LEFT JOIN ( SELECT [c0].[CustomerID], [c0].[CompanyName] FROM [Customers] AS [c0] - WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c0].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 + WHERE [c0].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL ) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID] -WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' ORDER BY [c].[CustomerID], [t0].[OrderID] """); } @@ -181,15 +172,14 @@ public override async Task Included_many_to_one_query(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] FROM [Orders] AS [o] LEFT 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 @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 + WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL """); @@ -201,8 +191,7 @@ public override async Task Project_reference_that_itself_has_query_filter_with_a AssertSql( """ -@__ef_filter__TenantPrefix_1='B' (Size = 4000) -@__ef_filter__TenantPrefix_1_1='B' (Size = 40) +@__ef_filter__TenantPrefix_1_rewritten='B%' (Size = 40) @__ef_filter___quantity_0='50' SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate] @@ -213,7 +202,7 @@ FROM [Orders] AS [o0] LEFT JOIN ( SELECT [c].[CustomerID], [c].[CompanyName] FROM [Customers] AS [c] - WHERE @__ef_filter__TenantPrefix_1 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_1_1)) = @__ef_filter__TenantPrefix_1_1 + WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_1_rewritten ESCAPE N'\' ) AS [t] ON [o0].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL ) AS [t0] ON [o].[OrderID] = [t0].[OrderID] @@ -227,8 +216,7 @@ public override async Task Navs_query(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) @__ef_filter___quantity_1='50' SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] @@ -239,7 +227,7 @@ FROM [Orders] AS [o] LEFT JOIN ( SELECT [c0].[CustomerID], [c0].[CompanyName] FROM [Customers] AS [c0] - WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c0].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 + WHERE [c0].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL ) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID] @@ -252,13 +240,13 @@ FROM [Orders] AS [o1] LEFT JOIN ( SELECT [c1].[CustomerID], [c1].[CompanyName] FROM [Customers] AS [c1] - WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c1].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 + WHERE [c1].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' ) AS [t3] ON [o1].[CustomerID] = [t3].[CustomerID] WHERE [t3].[CustomerID] IS NOT NULL AND [t3].[CompanyName] IS NOT NULL ) AS [t2] ON [o0].[OrderID] = [t2].[OrderID] WHERE [o0].[Quantity] > @__ef_filter___quantity_1 ) AS [t1] ON [t0].[OrderID] = [t1].[OrderID] -WHERE (@__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1) AND [t1].[Discount] < CAST(10 AS real) +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' AND [t1].[Discount] < CAST(10 AS real) """); } @@ -274,14 +262,13 @@ public void FromSql_is_composed() AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) SELECT [m].[CustomerID], [m].[Address], [m].[City], [m].[CompanyName], [m].[ContactName], [m].[ContactTitle], [m].[Country], [m].[Fax], [m].[Phone], [m].[PostalCode], [m].[Region] FROM ( select * from Customers ) AS [m] -WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([m].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 +WHERE [m].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' """); } @@ -297,8 +284,7 @@ public void FromSql_is_composed_when_filter_has_navigation() AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) SELECT [m].[OrderID], [m].[CustomerID], [m].[EmployeeID], [m].[OrderDate] FROM ( @@ -307,7 +293,7 @@ public void FromSql_is_composed_when_filter_has_navigation() LEFT JOIN ( SELECT [c].[CustomerID], [c].[CompanyName] FROM [Customers] AS [c] - WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 + WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' ) AS [t] ON [m].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL """); @@ -319,23 +305,21 @@ public override void Compiled_query() AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) @__customerID='BERGS' (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 (@__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1) AND [c].[CustomerID] = @__customerID +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' AND [c].[CustomerID] = @__customerID """, // """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) @__customerID='BLAUS' (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 (@__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1) AND [c].[CustomerID] = @__customerID +WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' AND [c].[CustomerID] = @__customerID """); } @@ -345,15 +329,14 @@ public override async Task Entity_Equality(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] LEFT JOIN ( SELECT [c].[CustomerID], [c].[CompanyName] FROM [Customers] AS [c] - WHERE @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 + WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL """); @@ -372,15 +355,14 @@ public override async Task Included_many_to_one_query2(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 4000) -@__ef_filter__TenantPrefix_0_1='B' (Size = 40) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 40) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] FROM [Orders] AS [o] LEFT 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 @__ef_filter__TenantPrefix_0 = N'' OR LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0_1)) = @__ef_filter__TenantPrefix_0_1 + WHERE [c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE N'\' ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] WHERE [t].[CustomerID] IS NOT NULL AND [t].[CompanyName] IS NOT NULL """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index 17ea7263d61..d942cfe0bd2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -2307,7 +2307,7 @@ public override async Task Take_on_top_level_and_on_collection_projection_with_o FROM ( SELECT TOP(1) [o].[OrderID], [o].[OrderDate] FROM [Orders] AS [o] - WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' + WHERE [o].[CustomerID] LIKE N'F%' ) AS [t] OUTER APPLY ( SELECT [t1].[OrderID], [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock], [t1].[UnitPrice] AS [UnitPrice0], [t1].[ProductID] AS [ProductID0] @@ -2411,7 +2411,7 @@ public override async Task VisitLambda_should_not_be_visited_trivially(bool asyn """ SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'A%' +WHERE [o].[CustomerID] LIKE N'A%' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQuerySqlServerTest.cs index ea115ad74d1..a3ef7c7d3ad 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeNoTrackingQuerySqlServerTest.cs @@ -473,7 +473,7 @@ public override async Task Include_collection_and_reference(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """, // @@ -482,7 +482,7 @@ WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } @@ -1186,7 +1186,7 @@ public override async Task Include_collection_alias_generation(bool async) """ SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID] """, // @@ -1194,7 +1194,7 @@ ORDER BY [o].[OrderID] SELECT [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice], [o].[OrderID] FROM [Orders] AS [o] INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID] """); } @@ -1923,7 +1923,7 @@ public override async Task Include_reference(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -2010,7 +2010,7 @@ public override async Task Include_reference_when_entity_in_projection(bool asyn SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -2725,7 +2725,7 @@ public override async Task Include_reference_and_collection(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """, // @@ -2734,7 +2734,7 @@ WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs index b2690c273a7..b49c71f728c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs @@ -56,7 +56,7 @@ public override async Task Include_reference(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -164,7 +164,7 @@ public override async Task Include_reference_and_collection(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """, // @@ -173,7 +173,7 @@ WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } @@ -329,7 +329,7 @@ public override async Task Include_collection_alias_generation(bool async) """ SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID] """, // @@ -337,7 +337,7 @@ ORDER BY [o].[OrderID] SELECT [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice], [o].[OrderID] FROM [Orders] AS [o] INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID] """); } @@ -2589,7 +2589,7 @@ public override async Task Include_reference_when_entity_in_projection(bool asyn SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -2786,7 +2786,7 @@ public override async Task Include_reference_and_collection_order_by(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """, // @@ -2795,7 +2795,7 @@ WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] INNER JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } @@ -2861,7 +2861,7 @@ public override async Task Include_collection_and_reference(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """, // @@ -2870,7 +2870,7 @@ WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } @@ -2957,7 +2957,7 @@ public override async Task Include_references_then_include_collection(bool async SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """, // @@ -2966,7 +2966,7 @@ WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] INNER JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindStringIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindStringIncludeQuerySqlServerTest.cs index 36f3b2269f7..df7939a7d04 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindStringIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindStringIncludeQuerySqlServerTest.cs @@ -135,7 +135,7 @@ public override async Task Include_collection_alias_generation(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice] FROM [Orders] AS [o] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [o0].[OrderID] """); } @@ -234,7 +234,7 @@ public override async Task Include_references_then_include_collection(bool async FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } @@ -1136,7 +1136,7 @@ public override async Task Include_reference_when_entity_in_projection(bool asyn SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -1520,7 +1520,7 @@ public override async Task Include_reference(bool async) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' """); } @@ -1750,7 +1750,7 @@ public override async Task Include_collection_and_reference(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID], [o0].[OrderID] """); } @@ -1891,7 +1891,7 @@ public override async Task Include_reference_and_collection(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID], [o0].[OrderID] """); } @@ -1962,7 +1962,7 @@ public override async Task Include_reference_and_collection_order_by(bool async) FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] LEFT JOIN [Orders] AS [o0] ON [c].[CustomerID] = [o0].[CustomerID] -WHERE [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] LIKE N'F%' +WHERE [o].[CustomerID] LIKE N'F%' ORDER BY [o].[OrderID], [c].[CustomerID] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 9c16a61c754..e9050dc3653 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -1182,7 +1182,7 @@ public override async Task Where_equal_with_and_and_contains(bool async) """ SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE ([e].[NullableStringB] LIKE N'' OR CHARINDEX([e].[NullableStringB], [e].[NullableStringA]) > 0) AND [e].[BoolA] = CAST(1 AS bit) +WHERE [e].[NullableStringA] IS NOT NULL AND [e].[NullableStringB] IS NOT NULL AND (CHARINDEX([e].[NullableStringB], [e].[NullableStringA]) > 0 OR [e].[NullableStringB] LIKE N'') AND [e].[BoolA] = CAST(1 AS bit) """); } @@ -3251,6 +3251,95 @@ FROM [Entities1] AS [e] """); } + public override async Task Like(bool async) + { + await base.Like(async); + + AssertSql( +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] LIKE [e].[StringB] +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] LIKE [e].[NullableStringB] +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[NullableStringA] LIKE [e].[StringB] +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[NullableStringA] LIKE [e].[NullableStringB] +"""); + } + + public override async Task Like_negated(bool async) + { + await base.Like_negated(async); + + AssertSql( +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] NOT LIKE [e].[StringB] +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] NOT LIKE [e].[NullableStringB] OR [e].[NullableStringB] IS NULL +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[NullableStringA] NOT LIKE [e].[StringB] OR [e].[NullableStringA] IS NULL +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[NullableStringA] NOT LIKE [e].[NullableStringB] OR [e].[NullableStringA] IS NULL OR [e].[NullableStringB] IS NULL +"""); + } + + public override async Task Like_with_escape_char(bool async) + { + await base.Like_with_escape_char(async); + + AssertSql( +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] LIKE [e].[StringB] ESCAPE N'\' +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE [e].[StringA] NOT LIKE [e].[StringB] ESCAPE N'\' +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +WHERE 0 = 1 +""", + // +""" +SELECT [e].[Id] +FROM [Entities1] AS [e] +"""); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsQuerySqlServerTest.cs index dcbbe0f7f0c..83245c2736b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OperatorsQuerySqlServerTest.cs @@ -116,7 +116,7 @@ public override async Task Negate_on_like_expression(bool async) """ SELECT [o].[Id] FROM [OperatorEntityString] AS [o] -WHERE [o].[Value] IS NOT NULL AND [o].[Value] NOT LIKE N'A%' +WHERE [o].[Value] NOT LIKE N'A%' OR [o].[Value] IS NULL """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index c0a02e157e1..107cebb36a8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -3139,7 +3139,7 @@ FROM [Factions] AS [f] WHERE EXISTS ( SELECT 1 FROM [Leaders] AS [l] - WHERE [l].[Name] IS NOT NULL AND [l].[Name] LIKE N'Bran%' AND [l].[Name] = N'Crach an Craite') + WHERE [l].[Name] LIKE N'Bran%' AND [l].[Name] = N'Crach an Craite') """); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs index aedd2ba1fc0..83f62985caf 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs @@ -497,7 +497,7 @@ public override async Task Hierarchy_query_with_abstract_type_sibling(bool async """ SELECT [a].[Id], [a].[Discriminator], [a].[Species], [a].[Name], [a].[EdcuationLevel], [a].[FavoriteToy] FROM [Animals] AS [a] -WHERE [a].[Discriminator] IN (N'Cat', N'Dog') AND [a].[Species] IS NOT NULL AND [a].[Species] LIKE N'F%' +WHERE [a].[Discriminator] IN (N'Cat', N'Dog') AND [a].[Species] LIKE N'F%' """); } @@ -515,7 +515,7 @@ FROM [Animals] AS [a] LEFT JOIN [Pets] AS [p] ON [a].[Id] = [p].[Id] LEFT JOIN [Cats] AS [c] ON [a].[Id] = [c].[Id] LEFT JOIN [Dogs] AS [d] ON [a].[Id] = [d].[Id] -WHERE ([d].[Id] IS NOT NULL OR [c].[Id] IS NOT NULL) AND [a].[Species] IS NOT NULL AND [a].[Species] LIKE N'F%' +WHERE ([d].[Id] IS NOT NULL OR [c].[Id] IS NOT NULL) AND [a].[Species] LIKE N'F%' """); } @@ -533,7 +533,7 @@ UNION ALL SELECT [d].[Id], [d].[Species], [d].[Name], NULL AS [EdcuationLevel], [d].[FavoriteToy], N'Dog' AS [Discriminator] FROM [Dogs] AS [d] ) AS [t] -WHERE [t].[Species] IS NOT NULL AND [t].[Species] LIKE N'F%' +WHERE [t].[Species] LIKE N'F%' """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index d36b57e5a2f..c6cf78a9010 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -1331,7 +1331,7 @@ FROM [Officers] AS [o] WHERE CASE WHEN [t].[LeaderNickname] IS NULL THEN NULL ELSE CASE - WHEN [t].[LeaderNickname] IS NOT NULL AND [t].[LeaderNickname] LIKE N'%us' THEN CAST(1 AS bit) + WHEN [t].[LeaderNickname] LIKE N'%us' AND [t].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END END = CAST(1 AS bit) @@ -3384,7 +3384,7 @@ public override async Task Optional_navigation_type_compensation_works_with_bina AssertSql( """ SELECT CASE - WHEN [t0].[HasSoulPatch] = CAST(1 AS bit) AND [t].[Note] LIKE N'%Cole%' THEN CAST(1 AS bit) + WHEN [t0].[HasSoulPatch] = CAST(1 AS bit) AND [t].[Note] LIKE N'%Cole%' AND [t].[Note] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END FROM [Tags] AS [t] @@ -8111,7 +8111,7 @@ public override async Task Double_order_by_on_Like(bool async) FROM [Weapons] AS [w] LEFT JOIN [Weapons] AS [w0] ON [w].[SynergyWithId] = [w0].[Id] ORDER BY CASE - WHEN [w0].[Name] LIKE N'%Lancer' THEN CAST(1 AS bit) + WHEN [w0].[Name] LIKE N'%Lancer' AND [w0].[Name] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs index 6189420edaf..e1658bc3454 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs @@ -24,14 +24,14 @@ public override async Task Skip_navigation_all(bool async) await base.Skip_navigation_all(async); AssertSql( -""" + """ SELECT [e].[Id], [e].[Name] FROM [EntityOnes] AS [e] WHERE NOT EXISTS ( SELECT 1 FROM [JoinOneToTwo] AS [j] INNER JOIN [EntityTwos] AS [e0] ON [j].[TwoId] = [e0].[Id] - WHERE [e].[Id] = [j].[OneId] AND [e0].[Name] NOT LIKE N'%B%') + WHERE [e].[Id] = [j].[OneId] AND ([e0].[Name] NOT LIKE N'%B%' OR [e0].[Name] IS NULL)) """); } @@ -117,7 +117,7 @@ UNION ALL SELECT [l].[Id], [l].[Name], [l].[Number], [l].[IsGreen], N'EntityLeaf' AS [Discriminator] FROM [Leaves] AS [l] ) AS [t] ON [j].[EntityBranchId] = [t].[Id] - WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [e].[Id] + WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] LIKE N'L%'), [e].[Id] """); } @@ -149,7 +149,7 @@ ORDER BY ( SELECT COUNT_BIG(*) FROM [EntityTwoEntityTwo] AS [e0] INNER JOIN [EntityTwos] AS [e1] ON [e0].[SelfSkipSharedLeftId] = [e1].[Id] - WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] IS NOT NULL AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] + WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] """); } @@ -2135,7 +2135,7 @@ WHERE NOT EXISTS ( SELECT 1 FROM [UnidirectionalJoinOneToTwo] AS [u0] INNER JOIN [UnidirectionalEntityTwos] AS [u1] ON [u0].[TwoId] = [u1].[Id] - WHERE [u].[Id] = [u0].[OneId] AND [u1].[Name] NOT LIKE N'%B%') + WHERE [u].[Id] = [u0].[OneId] AND ([u1].[Name] NOT LIKE N'%B%' OR [u1].[Name] IS NULL)) """); } @@ -2205,7 +2205,7 @@ UNION ALL SELECT [u2].[Id], [u2].[Name], [u2].[Number], [u2].[IsGreen], N'UnidirectionalEntityLeaf' AS [Discriminator] FROM [UnidirectionalLeaves] AS [u2] ) AS [t] ON [u0].[UnidirectionalEntityBranchId] = [t].[Id] - WHERE [u].[Id] = [u0].[UnidirectionalEntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [u].[Id] + WHERE [u].[Id] = [u0].[UnidirectionalEntityOneId] AND [t].[Name] LIKE N'L%'), [u].[Id] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs index 63d8602a604..09718b1167c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs @@ -24,14 +24,14 @@ public override async Task Skip_navigation_all(bool async) await base.Skip_navigation_all(async); AssertSql( -""" + """ SELECT [e].[Id], [e].[Name] FROM [EntityOnes] AS [e] WHERE NOT EXISTS ( SELECT 1 FROM [JoinOneToTwo] AS [j] INNER JOIN [EntityTwos] AS [e0] ON [j].[TwoId] = [e0].[Id] - WHERE [e].[Id] = [j].[OneId] AND [e0].[Name] NOT LIKE N'%B%') + WHERE [e].[Id] = [j].[OneId] AND ([e0].[Name] NOT LIKE N'%B%' OR [e0].[Name] IS NULL)) """); } @@ -117,7 +117,7 @@ UNION ALL SELECT [l].[Id], [l].[Name], [l].[Number], [l].[IsGreen], N'EntityLeaf' AS [Discriminator] FROM [Leaves] AS [l] ) AS [t] ON [j].[EntityBranchId] = [t].[Id] - WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [e].[Id] + WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] LIKE N'L%'), [e].[Id] """); } @@ -149,7 +149,7 @@ ORDER BY ( SELECT COUNT_BIG(*) FROM [EntityTwoEntityTwo] AS [e0] INNER JOIN [EntityTwos] AS [e1] ON [e0].[SelfSkipSharedLeftId] = [e1].[Id] - WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] IS NOT NULL AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] + WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] """); } @@ -2129,14 +2129,14 @@ public override async Task Skip_navigation_all_unidirectional(bool async) await base.Skip_navigation_all_unidirectional(async); AssertSql( -""" + """ SELECT [u].[Id], [u].[Name] FROM [UnidirectionalEntityOnes] AS [u] WHERE NOT EXISTS ( SELECT 1 FROM [UnidirectionalJoinOneToTwo] AS [u0] INNER JOIN [UnidirectionalEntityTwos] AS [u1] ON [u0].[TwoId] = [u1].[Id] - WHERE [u].[Id] = [u0].[OneId] AND [u1].[Name] NOT LIKE N'%B%') + WHERE [u].[Id] = [u0].[OneId] AND ([u1].[Name] NOT LIKE N'%B%' OR [u1].[Name] IS NULL)) """); } @@ -2206,7 +2206,7 @@ UNION ALL SELECT [u2].[Id], [u2].[Name], [u2].[Number], [u2].[IsGreen], N'UnidirectionalEntityLeaf' AS [Discriminator] FROM [UnidirectionalLeaves] AS [u2] ) AS [t] ON [u0].[UnidirectionalEntityBranchId] = [t].[Id] - WHERE [u].[Id] = [u0].[UnidirectionalEntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [u].[Id] + WHERE [u].[Id] = [u0].[UnidirectionalEntityOneId] AND [t].[Name] LIKE N'L%'), [u].[Id] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 1259eee5860..cce40d12f42 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -1182,7 +1182,7 @@ FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL ELSE CASE - WHEN [g].[LeaderNickname] IS NOT NULL AND [g].[LeaderNickname] LIKE N'%us' THEN CAST(1 AS bit) + WHEN [g].[LeaderNickname] LIKE N'%us' AND [g].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END END = CAST(1 AS bit) @@ -2896,7 +2896,7 @@ public override async Task Optional_navigation_type_compensation_works_with_bina AssertSql( """ SELECT CASE - WHEN [t0].[HasSoulPatch] = CAST(1 AS bit) AND [t].[Note] LIKE N'%Cole%' THEN CAST(1 AS bit) + WHEN [t0].[HasSoulPatch] = CAST(1 AS bit) AND [t].[Note] LIKE N'%Cole%' AND [t].[Note] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END FROM [Tags] AS [t] @@ -6860,7 +6860,7 @@ public override async Task Double_order_by_on_Like(bool async) FROM [Weapons] AS [w] LEFT JOIN [Weapons] AS [w0] ON [w].[SynergyWithId] = [w0].[Id] ORDER BY CASE - WHEN [w0].[Name] LIKE N'%Lancer' THEN CAST(1 AS bit) + WHEN [w0].[Name] LIKE N'%Lancer' AND [w0].[Name] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs index 47151e29bbe..d4617c69dd8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs @@ -32,7 +32,7 @@ WHERE NOT EXISTS ( SELECT 1 FROM [JoinOneToTwo] AS [j] INNER JOIN [EntityTwos] AS [e0] ON [j].[TwoId] = [e0].[Id] - WHERE [e].[Id] = [j].[OneId] AND [e0].[Name] NOT LIKE N'%B%') + WHERE [e].[Id] = [j].[OneId] AND ([e0].[Name] NOT LIKE N'%B%' OR [e0].[Name] IS NULL)) """); } @@ -119,7 +119,7 @@ FROM [Roots] AS [r] INNER JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] ) AS [t] ON [j].[EntityBranchId] = [t].[Id] - WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [e].[Id] + WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] LIKE N'L%'), [e].[Id] """); } @@ -151,7 +151,7 @@ ORDER BY ( SELECT COUNT_BIG(*) FROM [EntityTwoEntityTwo] AS [e0] INNER JOIN [EntityTwos] AS [e1] ON [e0].[SelfSkipSharedLeftId] = [e1].[Id] - WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] IS NOT NULL AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] + WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] """); } @@ -2087,7 +2087,7 @@ WHERE NOT EXISTS ( SELECT 1 FROM [UnidirectionalJoinOneToTwo] AS [u0] INNER JOIN [UnidirectionalEntityTwos] AS [u1] ON [u0].[TwoId] = [u1].[Id] - WHERE [u].[Id] = [u0].[OneId] AND [u1].[Name] NOT LIKE N'%B%') + WHERE [u].[Id] = [u0].[OneId] AND ([u1].[Name] NOT LIKE N'%B%' OR [u1].[Name] IS NULL)) """); } @@ -2158,7 +2158,7 @@ FROM [UnidirectionalRoots] AS [u1] INNER JOIN [UnidirectionalBranches] AS [u2] ON [u1].[Id] = [u2].[Id] LEFT JOIN [UnidirectionalLeaves] AS [u3] ON [u1].[Id] = [u3].[Id] ) AS [t] ON [u0].[UnidirectionalEntityBranchId] = [t].[Id] - WHERE [u].[Id] = [u0].[UnidirectionalEntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [u].[Id] + WHERE [u].[Id] = [u0].[UnidirectionalEntityOneId] AND [t].[Name] LIKE N'L%'), [u].[Id] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs index c91f7b02b8c..8408cf7572e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs @@ -31,7 +31,7 @@ WHERE NOT EXISTS ( SELECT 1 FROM [JoinOneToTwo] AS [j] INNER JOIN [EntityTwos] AS [e0] ON [j].[TwoId] = [e0].[Id] - WHERE [e].[Id] = [j].[OneId] AND [e0].[Name] NOT LIKE N'%B%') + WHERE [e].[Id] = [j].[OneId] AND ([e0].[Name] NOT LIKE N'%B%' OR [e0].[Name] IS NULL)) """); } @@ -118,7 +118,7 @@ FROM [Roots] AS [r] INNER JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] ) AS [t] ON [j].[EntityBranchId] = [t].[Id] - WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [e].[Id] + WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] LIKE N'L%'), [e].[Id] """); } @@ -150,7 +150,7 @@ ORDER BY ( SELECT COUNT_BIG(*) FROM [EntityTwoEntityTwo] AS [e0] INNER JOIN [EntityTwos] AS [e1] ON [e0].[SelfSkipSharedLeftId] = [e1].[Id] - WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] IS NOT NULL AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] + WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] """); } @@ -2087,7 +2087,7 @@ WHERE NOT EXISTS ( SELECT 1 FROM [UnidirectionalJoinOneToTwo] AS [u0] INNER JOIN [UnidirectionalEntityTwos] AS [u1] ON [u0].[TwoId] = [u1].[Id] - WHERE [u].[Id] = [u0].[OneId] AND [u1].[Name] NOT LIKE N'%B%') + WHERE [u].[Id] = [u0].[OneId] AND ([u1].[Name] NOT LIKE N'%B%' OR [u1].[Name] IS NULL)) """); } @@ -2158,7 +2158,7 @@ FROM [UnidirectionalRoots] AS [u1] INNER JOIN [UnidirectionalBranches] AS [u2] ON [u1].[Id] = [u2].[Id] LEFT JOIN [UnidirectionalLeaves] AS [u3] ON [u1].[Id] = [u3].[Id] ) AS [t] ON [u0].[UnidirectionalEntityBranchId] = [t].[Id] - WHERE [u].[Id] = [u0].[UnidirectionalEntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [u].[Id] + WHERE [u].[Id] = [u0].[UnidirectionalEntityOneId] AND [t].[Name] LIKE N'L%'), [u].[Id] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalComplexNavigationsCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalComplexNavigationsCollectionsQuerySqlServerTest.cs index 1da8b8d6b74..7e7691a68ca 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalComplexNavigationsCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalComplexNavigationsCollectionsQuerySqlServerTest.cs @@ -373,7 +373,7 @@ public override async Task Include_collection_with_conditional_order_by(bool asy FROM [LevelOne] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [l] LEFT JOIN [LevelTwo] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [l0] ON [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] ORDER BY CASE - WHEN [l].[Name] IS NOT NULL AND [l].[Name] LIKE N'%03' THEN 1 + WHEN [l].[Name] LIKE N'%03' THEN 1 ELSE 2 END, [l].[Id] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalComplexNavigationsCollectionsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalComplexNavigationsCollectionsSharedTypeQuerySqlServerTest.cs index c1968494f8f..e330998af7d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalComplexNavigationsCollectionsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalComplexNavigationsCollectionsSharedTypeQuerySqlServerTest.cs @@ -1453,7 +1453,7 @@ LEFT JOIN ( WHERE [l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [t] ON [l].[Id] = [t].[OneToMany_Optional_Inverse2Id] ORDER BY CASE - WHEN [l].[Name] IS NOT NULL AND [l].[Name] LIKE N'%03' THEN 1 + WHEN [l].[Name] LIKE N'%03' THEN 1 ELSE 2 END, [l].[Id] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 073e8897506..c3b46d07c87 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -966,7 +966,7 @@ public override async Task Double_order_by_on_Like(bool async) FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w] LEFT JOIN [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w0] ON [w].[SynergyWithId] = [w0].[Id] ORDER BY CASE - WHEN [w0].[Name] LIKE N'%Lancer' THEN CAST(1 AS bit) + WHEN [w0].[Name] LIKE N'%Lancer' AND [w0].[Name] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); @@ -7673,7 +7673,7 @@ public override async Task Null_propagation_optimization2(bool async) WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL ELSE CASE - WHEN [g].[LeaderNickname] IS NOT NULL AND [g].[LeaderNickname] LIKE N'%us' THEN CAST(1 AS bit) + WHEN [g].[LeaderNickname] LIKE N'%us' AND [g].[LeaderNickname] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END END = CAST(1 AS bit) @@ -8975,7 +8975,7 @@ public override async Task Optional_navigation_type_compensation_works_with_bina AssertSql( """ SELECT CASE - WHEN [g].[HasSoulPatch] = CAST(1 AS bit) AND [t].[Note] LIKE N'%Cole%' THEN CAST(1 AS bit) + WHEN [g].[HasSoulPatch] = CAST(1 AS bit) AND [t].[Note] LIKE N'%Cole%' AND [t].[Note] IS NOT NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END FROM [Tags] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [t] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs index 3bb7425611a..3cc407d0eff 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs @@ -58,7 +58,7 @@ WHERE NOT EXISTS ( SELECT 1 FROM [JoinOneToTwo] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [j] INNER JOIN [EntityTwos] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e0] ON [j].[TwoId] = [e0].[Id] - WHERE [e].[Id] = [j].[OneId] AND [e0].[Name] NOT LIKE N'%B%') + WHERE [e].[Id] = [j].[OneId] AND ([e0].[Name] NOT LIKE N'%B%' OR [e0].[Name] IS NULL)) """); } @@ -142,7 +142,7 @@ INNER JOIN ( FROM [EntityRoots] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e0] WHERE [e0].[Discriminator] IN (N'EntityBranch', N'EntityLeaf') ) AS [t] ON [j].[EntityBranchId] = [t].[Id] - WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] IS NOT NULL AND [t].[Name] LIKE N'L%'), [e].[Id] + WHERE [e].[Id] = [j].[EntityOneId] AND [t].[Name] LIKE N'L%'), [e].[Id] """); } @@ -174,7 +174,7 @@ ORDER BY ( SELECT COUNT_BIG(*) FROM [EntityTwoEntityTwo] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e0] INNER JOIN [EntityTwos] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [e1] ON [e0].[SelfSkipSharedLeftId] = [e1].[Id] - WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] IS NOT NULL AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] + WHERE [e].[Id] = [e0].[SelfSkipSharedRightId] AND [e1].[Name] LIKE N'L%') DESC, [e].[Id] """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs index 86a4199b3dc..ea262a8d3b6 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesSqliteTest.cs @@ -110,7 +110,7 @@ DELETE FROM "Posts" AS "p" SELECT "p0"."Id" FROM "Posts" AS "p0" LEFT JOIN "Blogs" AS "b" ON "p0"."BlogId" = "b"."Id" - WHERE "b"."Title" IS NOT NULL AND "b"."Title" LIKE 'Arthur%' + WHERE "b"."Title" LIKE 'Arthur%' ) """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs index 73e572a8f1c..878103f33fe 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs @@ -360,7 +360,7 @@ SELECT 1 FROM "Order Details" AS "o0" INNER JOIN "Orders" AS "o1" ON "o0"."OrderID" = "o1"."OrderID" LEFT JOIN "Customers" AS "c" ON "o1"."CustomerID" = "c"."CustomerID" - WHERE "c"."CustomerID" IS NOT NULL AND "c"."CustomerID" LIKE 'F%' AND "o0"."OrderID" = "o"."OrderID" AND "o0"."ProductID" = "o"."ProductID") + WHERE "c"."CustomerID" LIKE 'F%' AND "o0"."OrderID" = "o"."OrderID" AND "o0"."ProductID" = "o"."ProductID") """); } @@ -503,7 +503,7 @@ SELECT 1 FROM "Order Details" AS "o0" INNER JOIN "Orders" AS "o1" ON "o0"."OrderID" = "o1"."OrderID" LEFT JOIN "Customers" AS "c" ON "o1"."CustomerID" = "c"."CustomerID" - WHERE "c"."City" IS NOT NULL AND "c"."City" LIKE 'Se%' AND "o0"."OrderID" = "o"."OrderID" AND "o0"."ProductID" = "o"."ProductID") + WHERE "c"."City" LIKE 'Se%' AND "o0"."OrderID" = "o"."OrderID" AND "o0"."ProductID" = "o"."ProductID") """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/FromSqlQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/FromSqlQuerySqliteTest.cs index d4b0ed269c6..169fcc8bca0 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/FromSqlQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/FromSqlQuerySqliteTest.cs @@ -23,7 +23,7 @@ public override async Task FromSqlRaw_queryable_composed(bool async) FROM ( SELECT * FROM "Customers" ) AS "m" -WHERE 'z' = '' OR instr("m"."ContactName", 'z') > 0 +WHERE "m"."ContactName" IS NOT NULL AND instr("m"."ContactName", 'z') > 0 """); } @@ -73,7 +73,7 @@ public override async Task FromSqlRaw_composed_with_common_table_expression(bool ) SELECT * FROM "Customers2" ) AS "m" -WHERE 'z' = '' OR instr("m"."ContactName", 'z') > 0 +WHERE "m"."ContactName" IS NOT NULL AND instr("m"."ContactName", 'z') > 0 """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 6518a3b9b20..fc616568611 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -1095,7 +1095,7 @@ SELECT COALESCE(( FROM ( SELECT DISTINCT "w"."Id", "w"."AmmunitionType", "w"."IsAutomatic", "w"."Name", "w"."OwnerFullName", "w"."SynergyWithId" FROM "Weapons" AS "w" - WHERE "g"."FullName" = "w"."OwnerFullName" AND ('Lancer' = '' OR instr("w"."Name", 'Lancer') > 0) + WHERE "g"."FullName" = "w"."OwnerFullName" AND "w"."Name" IS NOT NULL AND instr("w"."Name", 'Lancer') > 0 ) AS "t" LIMIT 1), 0) FROM "Gears" AS "g" @@ -2259,7 +2259,7 @@ public override async Task Where_subquery_distinct_singleordefault_boolean2(bool WHERE "g"."HasSoulPatch" AND COALESCE(( SELECT DISTINCT "w"."IsAutomatic" FROM "Weapons" AS "w" - WHERE "g"."FullName" = "w"."OwnerFullName" AND ('Lancer' = '' OR instr("w"."Name", 'Lancer') > 0) + WHERE "g"."FullName" = "w"."OwnerFullName" AND "w"."Name" IS NOT NULL AND instr("w"."Name", 'Lancer') > 0 LIMIT 1), 0) ORDER BY "g"."Nickname" """); @@ -2527,7 +2527,7 @@ public override async Task Cast_to_derived_followed_by_multiple_includes(bool as FROM "LocustLeaders" AS "l" LEFT JOIN "Gears" AS "g" ON "l"."DefeatedByNickname" = "g"."Nickname" AND "l"."DefeatedBySquadId" = "g"."SquadId" LEFT JOIN "Weapons" AS "w" ON "g"."FullName" = "w"."OwnerFullName" -WHERE 'Queen' = '' OR instr("l"."Name", 'Queen') > 0 +WHERE instr("l"."Name", 'Queen') > 0 ORDER BY "l"."Name", "g"."Nickname", "g"."SquadId" """); } @@ -2966,7 +2966,7 @@ public override async Task Where_subquery_distinct_singleordefault_boolean_with_ FROM ( SELECT DISTINCT "w"."Id", "w"."AmmunitionType", "w"."IsAutomatic", "w"."Name", "w"."OwnerFullName", "w"."SynergyWithId" FROM "Weapons" AS "w" - WHERE "g"."FullName" = "w"."OwnerFullName" AND ('Lancer' = '' OR instr("w"."Name", 'Lancer') > 0) + WHERE "g"."FullName" = "w"."OwnerFullName" AND "w"."Name" IS NOT NULL AND instr("w"."Name", 'Lancer') > 0 ) AS "t" LIMIT 1) ORDER BY "g"."Nickname" @@ -3169,7 +3169,7 @@ public override async Task Where_subquery_distinct_singleordefault_boolean1(bool FROM ( SELECT DISTINCT "w"."Id", "w"."AmmunitionType", "w"."IsAutomatic", "w"."Name", "w"."OwnerFullName", "w"."SynergyWithId" FROM "Weapons" AS "w" - WHERE "g"."FullName" = "w"."OwnerFullName" AND ('Lancer' = '' OR instr("w"."Name", 'Lancer') > 0) + WHERE "g"."FullName" = "w"."OwnerFullName" AND "w"."Name" IS NOT NULL AND instr("w"."Name", 'Lancer') > 0 ) AS "t" LIMIT 1), 0) ORDER BY "g"."Nickname" @@ -3283,7 +3283,7 @@ public override async Task Null_propagation_optimization2(bool async) FROM "Gears" AS "g" WHERE CASE WHEN "g"."LeaderNickname" IS NULL THEN NULL - ELSE "g"."LeaderNickname" IS NOT NULL AND "g"."LeaderNickname" LIKE '%us' + ELSE "g"."LeaderNickname" LIKE '%us' AND "g"."LeaderNickname" IS NOT NULL END = 1 """); } @@ -3697,7 +3697,7 @@ public override async Task Double_order_by_on_Like(bool async) SELECT "w0"."Id", "w0"."AmmunitionType", "w0"."IsAutomatic", "w0"."Name", "w0"."OwnerFullName", "w0"."SynergyWithId" FROM "Weapons" AS "w" LEFT JOIN "Weapons" AS "w0" ON "w"."SynergyWithId" = "w0"."Id" -ORDER BY "w0"."Name" LIKE '%Lancer' +ORDER BY "w0"."Name" LIKE '%Lancer' AND "w0"."Name" IS NOT NULL """); } @@ -3781,7 +3781,7 @@ public override async Task Cast_to_derived_followed_by_include_and_FirstOrDefaul SELECT "l"."Name", "l"."Discriminator", "l"."LocustHordeId", "l"."ThreatLevel", "l"."ThreatLevelByte", "l"."ThreatLevelNullableByte", "l"."DefeatedByNickname", "l"."DefeatedBySquadId", "l"."HighCommandId", "g"."Nickname", "g"."SquadId", "g"."AssignedCityName", "g"."CityOfBirthName", "g"."Discriminator", "g"."FullName", "g"."HasSoulPatch", "g"."LeaderNickname", "g"."LeaderSquadId", "g"."Rank" FROM "LocustLeaders" AS "l" LEFT JOIN "Gears" AS "g" ON "l"."DefeatedByNickname" = "g"."Nickname" AND "l"."DefeatedBySquadId" = "g"."SquadId" -WHERE 'Queen' = '' OR instr("l"."Name", 'Queen') > 0 +WHERE instr("l"."Name", 'Queen') > 0 LIMIT 1 """); } @@ -3823,7 +3823,7 @@ public override async Task Non_unicode_string_literals_is_used_for_non_unicode_c """ SELECT "c"."Name", "c"."Location", "c"."Nation" FROM "Cities" AS "c" -WHERE 'Jacinto' = '' OR instr("c"."Location", 'Jacinto') > 0 +WHERE "c"."Location" IS NOT NULL AND instr("c"."Location", 'Jacinto') > 0 """); } @@ -4738,7 +4738,7 @@ public override async Task Select_subquery_distinct_singleordefault_boolean2(boo SELECT COALESCE(( SELECT DISTINCT "w"."IsAutomatic" FROM "Weapons" AS "w" - WHERE "g"."FullName" = "w"."OwnerFullName" AND ('Lancer' = '' OR instr("w"."Name", 'Lancer') > 0) + WHERE "g"."FullName" = "w"."OwnerFullName" AND "w"."Name" IS NOT NULL AND instr("w"."Name", 'Lancer') > 0 LIMIT 1), 0) FROM "Gears" AS "g" WHERE "g"."HasSoulPatch" @@ -4964,7 +4964,7 @@ public override async Task Optional_navigation_type_compensation_works_with_bina AssertSql( """ -SELECT "g"."HasSoulPatch" AND ('Cole' = '' OR instr("t"."Note", 'Cole') > 0) +SELECT "g"."HasSoulPatch" AND "t"."Note" IS NOT NULL AND instr("t"."Note", 'Cole') > 0 FROM "Tags" AS "t" LEFT JOIN "Gears" AS "g" ON "t"."GearNickName" = "g"."Nickname" AND "t"."GearSquadId" = "g"."SquadId" """); @@ -6389,7 +6389,7 @@ public override async Task Non_unicode_string_literals_is_used_for_non_unicode_c """ SELECT "c"."Name", "c"."Location", "c"."Nation" FROM "Cities" AS "c" -WHERE 'Add' = '' OR instr(COALESCE("c"."Location", '') || 'Added', 'Add') > 0 +WHERE instr(COALESCE("c"."Location", '') || 'Added', 'Add') > 0 """); } @@ -7152,7 +7152,7 @@ public override async Task Optional_navigation_type_compensation_works_with_bina SELECT "t"."Id", "t"."GearNickName", "t"."GearSquadId", "t"."IssueDate", "t"."Note" FROM "Tags" AS "t" LEFT JOIN "Gears" AS "g" ON "t"."GearNickName" = "g"."Nickname" AND "t"."GearSquadId" = "g"."SquadId" -WHERE "g"."HasSoulPatch" OR 'Cole' = '' OR instr("t"."Note", 'Cole') > 0 +WHERE "g"."HasSoulPatch" OR ("t"."Note" IS NOT NULL AND instr("t"."Note", 'Cole') > 0) """); } @@ -7248,7 +7248,7 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_with FROM ( SELECT DISTINCT "w"."Id", "w"."AmmunitionType", "w"."IsAutomatic", "w"."Name", "w"."OwnerFullName", "w"."SynergyWithId" FROM "Weapons" AS "w" - WHERE "g"."FullName" = "w"."OwnerFullName" AND ('Lancer' = '' OR instr("w"."Name", 'Lancer') > 0) + WHERE "g"."FullName" = "w"."OwnerFullName" AND "w"."Name" IS NOT NULL AND instr("w"."Name", 'Lancer') > 0 ) AS "t" LIMIT 1) FROM "Gears" AS "g" @@ -8617,7 +8617,7 @@ public override async Task Include_with_complex_order_by(bool async) ORDER BY ( SELECT "w"."Name" FROM "Weapons" AS "w" - WHERE "g"."FullName" = "w"."OwnerFullName" AND ('Gnasher' = '' OR instr("w"."Name", 'Gnasher') > 0) + WHERE "g"."FullName" = "w"."OwnerFullName" AND "w"."Name" IS NOT NULL AND instr("w"."Name", 'Gnasher') > 0 LIMIT 1), "g"."Nickname", "g"."SquadId" """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs index 4bd97731f8b..1feb83d9ff2 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs @@ -709,7 +709,21 @@ public override async Task String_StartsWith_Literal(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" IS NOT NULL AND "c"."ContactName" LIKE 'M%' +WHERE "c"."ContactName" LIKE 'M%' +"""); + } + + public override async Task String_StartsWith_Parameter(bool async) + { + await base.String_StartsWith_Parameter(async); + + AssertSql( +""" +@__pattern_0_rewritten='M%' (Size = 2) + +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"."ContactName" LIKE @__pattern_0_rewritten ESCAPE '\' """); } @@ -721,7 +735,7 @@ public override async Task String_StartsWith_Identity(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" = '' OR ("c"."ContactName" IS NOT NULL AND (("c"."ContactName" LIKE "c"."ContactName" || '%' AND substr("c"."ContactName", 1, length("c"."ContactName")) = "c"."ContactName") OR "c"."ContactName" = '')) +WHERE "c"."ContactName" IS NOT NULL AND (substr("c"."ContactName", 1, length("c"."ContactName")) = "c"."ContactName" OR "c"."ContactName" = '') """); } @@ -733,7 +747,7 @@ public override async Task String_StartsWith_Column(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" = '' OR ("c"."ContactName" IS NOT NULL AND (("c"."ContactName" LIKE "c"."ContactName" || '%' AND substr("c"."ContactName", 1, length("c"."ContactName")) = "c"."ContactName") OR "c"."ContactName" = '')) +WHERE "c"."ContactName" IS NOT NULL AND (substr("c"."ContactName", 1, length("c"."ContactName")) = "c"."ContactName" OR "c"."ContactName" = '') """); } @@ -745,7 +759,7 @@ public override async Task String_StartsWith_MethodCall(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" IS NOT NULL AND "c"."ContactName" LIKE 'M%' +WHERE "c"."ContactName" LIKE 'M%' """); } @@ -757,7 +771,21 @@ public override async Task String_EndsWith_Literal(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" IS NOT NULL AND "c"."ContactName" LIKE '%b' +WHERE "c"."ContactName" LIKE '%b' +"""); + } + + public override async Task String_EndsWith_Parameter(bool async) + { + await base.String_EndsWith_Parameter(async); + + AssertSql( +""" +@__pattern_0_rewritten='%b' (Size = 2) + +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"."ContactName" LIKE @__pattern_0_rewritten ESCAPE '\' """); } @@ -769,7 +797,7 @@ public override async Task String_EndsWith_Identity(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" = '' OR ("c"."ContactName" IS NOT NULL AND (substr("c"."ContactName", -length("c"."ContactName")) = "c"."ContactName" OR "c"."ContactName" = '')) +WHERE "c"."ContactName" IS NOT NULL AND (substr("c"."ContactName", -length("c"."ContactName")) = "c"."ContactName" OR "c"."ContactName" = '') """); } @@ -781,7 +809,7 @@ public override async Task String_EndsWith_Column(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" = '' OR ("c"."ContactName" IS NOT NULL AND (substr("c"."ContactName", -length("c"."ContactName")) = "c"."ContactName" OR "c"."ContactName" = '')) +WHERE "c"."ContactName" IS NOT NULL AND (substr("c"."ContactName", -length("c"."ContactName")) = "c"."ContactName" OR "c"."ContactName" = '') """); } @@ -793,7 +821,7 @@ public override async Task String_EndsWith_MethodCall(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" IS NOT NULL AND "c"."ContactName" LIKE '%m' +WHERE "c"."ContactName" LIKE '%m' """); } @@ -805,7 +833,7 @@ public override async Task String_Contains_Literal(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" FROM "Customers" AS "c" -WHERE 'M' = '' OR instr("c"."ContactName", 'M') > 0 +WHERE "c"."ContactName" IS NOT NULL AND instr("c"."ContactName", 'M') > 0 """); } @@ -817,7 +845,7 @@ public override async Task String_Contains_Identity(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" = '' OR instr("c"."ContactName", "c"."ContactName") > 0 +WHERE "c"."ContactName" IS NOT NULL AND instr("c"."ContactName", "c"."ContactName") > 0 """); } @@ -829,7 +857,7 @@ public override async Task String_Contains_Column(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" FROM "Customers" AS "c" -WHERE "c"."ContactName" = '' OR instr("c"."ContactName", "c"."ContactName") > 0 +WHERE "c"."ContactName" IS NOT NULL AND instr("c"."ContactName", "c"."ContactName") > 0 """); } @@ -863,7 +891,7 @@ public override async Task String_Contains_MethodCall(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" FROM "Customers" AS "c" -WHERE 'M' = '' OR instr("c"."ContactName", 'M') > 0 +WHERE "c"."ContactName" IS NOT NULL AND instr("c"."ContactName", 'M') > 0 """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs index fda6bb4fd16..06007b2bd05 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindMiscellaneousQuerySqliteTest.cs @@ -26,7 +26,7 @@ public override async Task Query_expression_with_to_string_and_contains(bool asy """ SELECT "o"."CustomerID" FROM "Orders" AS "o" -WHERE "o"."OrderDate" IS NOT NULL AND ('10' = '' OR instr(CAST("o"."EmployeeID" AS TEXT), '10') > 0) +WHERE "o"."OrderDate" IS NOT NULL AND "o"."EmployeeID" IS NOT NULL AND instr(CAST("o"."EmployeeID" AS TEXT), '10') > 0 """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindQueryFiltersQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindQueryFiltersQuerySqliteTest.cs index d4931689fbe..9484b21aa83 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindQueryFiltersQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindQueryFiltersQuerySqliteTest.cs @@ -21,11 +21,11 @@ public override async Task Count_query(bool async) AssertSql( """ -@__ef_filter__TenantPrefix_0='B' (Size = 1) +@__ef_filter__TenantPrefix_0_rewritten='B%' (Size = 2) SELECT COUNT(*) FROM "Customers" AS "c" -WHERE @__ef_filter__TenantPrefix_0 = '' OR ("c"."CompanyName" LIKE @__ef_filter__TenantPrefix_0 || '%' AND substr("c"."CompanyName", 1, length(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) OR @__ef_filter__TenantPrefix_0 = '' +WHERE "c"."CompanyName" LIKE @__ef_filter__TenantPrefix_0_rewritten ESCAPE '\' """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsQuerySqliteTest.cs index 753671a6f84..6703593d2e7 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/OperatorsQuerySqliteTest.cs @@ -108,7 +108,7 @@ public override async Task Negate_on_like_expression(bool async) """ SELECT "o"."Id" FROM "OperatorEntityString" AS "o" -WHERE "o"."Value" IS NOT NULL AND "o"."Value" NOT LIKE 'A%' +WHERE "o"."Value" NOT LIKE 'A%' OR "o"."Value" IS NULL """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SqlQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SqlQuerySqliteTest.cs index 64ee9bc41b1..a01caaa5449 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/SqlQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SqlQuerySqliteTest.cs @@ -23,7 +23,7 @@ public override async Task SqlQueryRaw_queryable_composed(bool async) FROM ( SELECT * FROM "Customers" ) AS "m" -WHERE 'z' = '' OR instr("m"."ContactName", 'z') > 0 +WHERE instr("m"."ContactName", 'z') > 0 """); } @@ -73,7 +73,7 @@ public override async Task SqlQueryRaw_composed_with_common_table_expression(boo ) SELECT * FROM "Customers2" ) AS "m" -WHERE 'z' = '' OR instr("m"."ContactName", 'z') > 0 +WHERE instr("m"."ContactName", 'z') > 0 """); }