From baf9d531bac78005194ff84e85952b061812f7ee Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Tue, 22 Oct 2019 13:22:29 -0700 Subject: [PATCH] Fix to #16078 - Query/Null semantics: when checking if expression is null, just check it's constituents rather than entire expression Problem was that during null semantics rewrite we create IS NULL calls on the operands of the comparison. If the operands themselves are complicated, we were simply comparing the entire complex expression to null. In some cases, we only need to look at constituents, e.g. a + b == null <=> a == null || b == null. Also added other minor optimizations around null semantics: - non_nullable_column IS NULL resolves to false, - try to simplify expression after applying de Morgan transformations Also fixed a bug exposed by these changes, where column nullability would be incorrect for scenarios with owned types. --- ...NullSemanticsRewritingExpressionVisitor.cs | 52 +++++++- ...qlExpressionOptimizingExpressionVisitor.cs | 93 ++++++++----- ...rNullabilityOptimizingExpressionVisitor.cs | 30 +++-- .../Query/SqlExpressions/ColumnExpression.cs | 2 +- .../Query/GearsOfWarQueryTestBase.cs | 101 +++++++++++++++ .../Query/GearsOfWarQuerySqlServerTest.cs | 122 +++++++++++++++--- .../Query/NullSemanticsQuerySqlServerTest.cs | 12 +- ...impleQuerySqlServerTest.ResultOperators.cs | 2 +- .../Query/SimpleQuerySqlServerTest.Where.cs | 30 ++--- .../Query/SimpleQuerySqlServerTest.cs | 14 +- .../SpatialQuerySqlServerGeographyTest.cs | 2 +- .../Query/SimpleQuerySqliteTest.cs | 16 +-- 12 files changed, 374 insertions(+), 102 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs index e35a76c166f..69eb84b91f6 100644 --- a/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs @@ -242,11 +242,13 @@ private SqlBinaryExpression VisitSqlBinaryExpression(SqlBinaryExpression sqlBina newRight = rightUnary.Operand; } - // TODO: optimize this by looking at subcomponents, e.g. f(a, b) == null <=> a == null || b == null - var leftIsNull = _sqlExpressionFactory.IsNull(newLeft); - var rightIsNull = _sqlExpressionFactory.IsNull(newRight); + var isNullOptimizer = new IsNullOptimizingExpressionVisitor(_sqlExpressionFactory); + + var leftIsNull = (SqlExpression)isNullOptimizer.Visit(_sqlExpressionFactory.IsNull(newLeft)); + var rightIsNull = (SqlExpression)isNullOptimizer.Visit(_sqlExpressionFactory.IsNull(newRight)); // doing a full null semantics rewrite - removing all nulls from truth table + // this will NOT be correct once we introduce simplified null semantics _isNullable = false; if (sqlBinaryExpression.OperatorType == ExpressionType.Equal) @@ -335,6 +337,50 @@ private SqlBinaryExpression VisitSqlBinaryExpression(SqlBinaryExpression sqlBina return sqlBinaryExpression.Update(newLeft, newRight); } + private class IsNullOptimizingExpressionVisitor : ExpressionVisitor + { + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + public IsNullOptimizingExpressionVisitor(ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + protected override Expression VisitExtension(Expression extensionExpression) + { + if (extensionExpression is SqlUnaryExpression sqlUnaryExpression + && sqlUnaryExpression.OperatorType == ExpressionType.Equal) + { + if (sqlUnaryExpression.Operand is SqlUnaryExpression sqlUnaryOperand + && (sqlUnaryOperand.OperatorType == ExpressionType.Convert + || sqlUnaryOperand.OperatorType == ExpressionType.Not + || sqlUnaryOperand.OperatorType == ExpressionType.Negate)) + { + return (SqlExpression)Visit(_sqlExpressionFactory.IsNull(sqlUnaryOperand.Operand)); + } + + if (sqlUnaryExpression.Operand is SqlBinaryExpression sqlBinaryOperand) + { + var newLeft = (SqlExpression)Visit(_sqlExpressionFactory.IsNull(sqlBinaryOperand.Left)); + var newRight = (SqlExpression)Visit(_sqlExpressionFactory.IsNull(sqlBinaryOperand.Right)); + + // (a ?? b) == null <=> a == null && b == null + return sqlBinaryOperand.OperatorType == ExpressionType.Coalesce + ? _sqlExpressionFactory.AndAlso(newLeft, newRight) + : _sqlExpressionFactory.OrElse(newLeft, newRight); + } + + if (sqlUnaryExpression.Operand is ColumnExpression columnOperand + && !columnOperand.IsNullable) + { + return _sqlExpressionFactory.Constant(false, sqlUnaryExpression.TypeMapping); + } + } + + return base.VisitExtension(extensionExpression); + } + } + private List FindNonNullableColumns(SqlExpression sqlExpression) { var result = new List(); diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs index 4ff4fd7ee60..7e6a06d443f 100644 --- a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs @@ -3,6 +3,7 @@ using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage; namespace Microsoft.EntityFrameworkCore.Query.Internal { @@ -60,6 +61,15 @@ protected virtual Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnary return SqlExpressionFactory.Constant(innerConstantNull1.Value == null, sqlUnaryExpression.TypeMapping); } + // non_nullable_column IS NULL -> false + // non_nullable_column IS NOT NULL -> true + if ((sqlUnaryExpression.OperatorType == ExpressionType.Equal || sqlUnaryExpression.OperatorType == ExpressionType.NotEqual) + && sqlUnaryExpression.Operand is ColumnExpression innerColumn + && !innerColumn.IsNullable) + { + return SqlExpressionFactory.Constant(sqlUnaryExpression.OperatorType == ExpressionType.NotEqual, sqlUnaryExpression.TypeMapping); + } + // NULL IS NOT NULL -> false // non_nullable_constant IS NOT NULL -> true if (sqlUnaryExpression.OperatorType == ExpressionType.NotEqual @@ -135,9 +145,13 @@ private Expression VisitNot(SqlUnaryExpression sqlUnaryExpression) var newLeft = (SqlExpression)Visit(SqlExpressionFactory.Not(innerBinary.Left)); var newRight = (SqlExpression)Visit(SqlExpressionFactory.Not(innerBinary.Right)); - return innerBinary.OperatorType == ExpressionType.AndAlso - ? SqlExpressionFactory.OrElse(newLeft, newRight) - : SqlExpressionFactory.AndAlso(newLeft, newRight); + return CreateSqlBinaryEqualityExpression( + innerBinary.OperatorType == ExpressionType.AndAlso + ? ExpressionType.OrElse + : ExpressionType.AndAlso, + newLeft, + newRight, + innerBinary.TypeMapping); } // those optimizations are only valid in 2-value logic @@ -168,36 +182,11 @@ private Expression VisitSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpress if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso || sqlBinaryExpression.OperatorType == ExpressionType.OrElse) { - // true && a -> a - // true || a -> true - // false && a -> false - // false || a -> a - if (newLeft is SqlConstantExpression newLeftConstant) - { - return sqlBinaryExpression.OperatorType == ExpressionType.AndAlso - ? (bool)newLeftConstant.Value - ? newRight - : newLeftConstant - : (bool)newLeftConstant.Value - ? newLeftConstant - : newRight; - } - else if (newRight is SqlConstantExpression newRightConstant) - { - // a && true -> a - // a || true -> true - // a && false -> false - // a || false -> a - return sqlBinaryExpression.OperatorType == ExpressionType.AndAlso - ? (bool)newRightConstant.Value - ? newLeft - : newRightConstant - : (bool)newRightConstant.Value - ? newRightConstant - : newLeft; - } - - return sqlBinaryExpression.Update(newLeft, newRight); + return CreateSqlBinaryEqualityExpression( + sqlBinaryExpression.OperatorType, + newLeft, + newRight, + sqlBinaryExpression.TypeMapping); } // those optimizations are only valid in 2-value logic @@ -227,5 +216,43 @@ private Expression VisitSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpress return sqlBinaryExpression.Update(newLeft, newRight); } + + private SqlExpression CreateSqlBinaryEqualityExpression( + ExpressionType operatorType, + SqlExpression newLeft, + SqlExpression newRight, + RelationalTypeMapping typeMapping) + { + // true && a -> a + // true || a -> true + // false && a -> false + // false || a -> a + if (newLeft is SqlConstantExpression newLeftConstant) + { + return operatorType == ExpressionType.AndAlso + ? (bool)newLeftConstant.Value + ? newRight + : newLeftConstant + : (bool)newLeftConstant.Value + ? newLeftConstant + : newRight; + } + else if (newRight is SqlConstantExpression newRightConstant) + { + // a && true -> a + // a || true -> true + // a && false -> false + // a || false -> a + return operatorType == ExpressionType.AndAlso + ? (bool)newRightConstant.Value + ? newLeft + : newRightConstant + : (bool)newRightConstant.Value + ? newRightConstant + : newLeft; + } + + return SqlExpressionFactory.MakeBinary(operatorType, newLeft, newRight, typeMapping); + } } } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ParameterNullabilityOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ParameterNullabilityOptimizingExpressionVisitor.cs index 771aa6bfbd0..1164f8b51d7 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ParameterNullabilityOptimizingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ParameterNullabilityOptimizingExpressionVisitor.cs @@ -30,22 +30,36 @@ protected override Expression VisitExtension(Expression extensionExpression) { var newSelectExpression = (SelectExpression)base.VisitExtension(extensionExpression); - return newSelectExpression.Predicate is SqlConstantExpression newSelectPredicateConstant + // if predicate is optimized to true, we can simply remove it + var newPredicate = newSelectExpression.Predicate is SqlConstantExpression newSelectPredicateConstant && !(selectExpression.Predicate is SqlConstantExpression) - ? newSelectExpression.Update( + ? (bool)newSelectPredicateConstant.Value + ? null + : SqlExpressionFactory.Equal( + newSelectPredicateConstant, + SqlExpressionFactory.Constant(true, newSelectPredicateConstant.TypeMapping)) + : newSelectExpression.Predicate; + + var newHaving = newSelectExpression.Having is SqlConstantExpression newSelectHavingConstant + && !(selectExpression.Having is SqlConstantExpression) + ? (bool)newSelectHavingConstant.Value + ? null + : SqlExpressionFactory.Equal( + newSelectHavingConstant, + SqlExpressionFactory.Constant(true, newSelectHavingConstant.TypeMapping)) + : newSelectExpression.Having; + + return newSelectExpression.Update( newSelectExpression.Projection.ToList(), newSelectExpression.Tables.ToList(), - SqlExpressionFactory.Equal( - newSelectPredicateConstant, - SqlExpressionFactory.Constant(true, newSelectPredicateConstant.TypeMapping)), + newPredicate, newSelectExpression.GroupBy.ToList(), - newSelectExpression.Having, + newHaving, newSelectExpression.Orderings.ToList(), newSelectExpression.Limit, newSelectExpression.Offset, newSelectExpression.IsDistinct, - newSelectExpression.Alias) - : newSelectExpression; + newSelectExpression.Alias); } return base.VisitExtension(extensionExpression); diff --git a/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs index 7ef6d75044a..f89d2a088a2 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/ColumnExpression.cs @@ -16,7 +16,7 @@ public class ColumnExpression : SqlExpression internal ColumnExpression(IProperty property, TableExpressionBase table, bool nullable) : this( property.GetColumnName(), table, property.ClrType, property.GetRelationalTypeMapping(), - nullable || property.IsNullable || property.DeclaringEntityType.BaseType != null) + nullable || property.IsColumnNullable()) { } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index ec4fbc72b14..c137abca516 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -8006,6 +8006,107 @@ public virtual Task Group_by_with_aggregate_max_on_entity_type(bool isAsync) }))); } + [ConditionalTheory(Skip = "issue #18492")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Group_by_on_StartsWith_with_null_parameter_as_argument(bool isAsync) + { + var prm = (string)null; + + return AssertQueryScalar( + isAsync, + ss => ss.Set().GroupBy(g => g.FullName.StartsWith(prm)).Select(g => g.Key)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Group_by_with_having_StartsWith_with_null_parameter_as_argument(bool isAsync) + { + var prm = (string)null; + + return AssertQuery( + isAsync, + ss => ss.Set().GroupBy(g => g.FullName).Where(g => g.Key.StartsWith(prm)).Select(g => g.Key), + ss => ss.Set().GroupBy(g => g.FullName).Where(g => false).Select(g => g.Key)); + } + + [ConditionalTheory(Skip = "issue #18492")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_StartsWith_with_null_parameter_as_argument(bool isAsync) + { + var prm = (string)null; + + return AssertQueryScalar( + isAsync, + ss => ss.Set().Select(g => g.FullName.StartsWith(prm)), + ss => ss.Set().Select(g => false)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_null_parameter_is_not_null(bool isAsync) + { + var prm = (string)null; + + return AssertQueryScalar( + isAsync, + ss => ss.Set().Select(g => prm != null), + ss => ss.Set().Select(g => false)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_null_parameter_is_not_null(bool isAsync) + { + var prm = (string)null; + + return AssertQuery( + isAsync, + ss => ss.Set().Where(g => prm != null), + ss => ss.Set().Where(g => false)); + } + + [ConditionalTheory(Skip = "issue #18492")] + [MemberData(nameof(IsAsyncData))] + public virtual Task OrderBy_StartsWith_with_null_parameter_as_argument(bool isAsync) + { + var prm = (string)null; + + return AssertQuery( + isAsync, + ss => ss.Set().OrderBy(g => g.FullName.StartsWith(prm)).ThenBy(g => g.Nickname), + ss => ss.Set().OrderBy(g => false).ThenBy(g => g.Nickname), + assertOrder: true); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Where_with_enum_flags_parameter(bool isAsync) + { + MilitaryRank? rank = MilitaryRank.Private; + + await AssertQuery( + isAsync, + ss => ss.Set().Where(g => (g.Rank & rank) == rank)); + + rank = null; + + await AssertQuery( + isAsync, + ss => ss.Set().Where(g => (g.Rank & rank) == rank)); + + rank = MilitaryRank.Corporal; + + await AssertQuery( + isAsync, + ss => ss.Set().Where(g => (g.Rank | rank) != rank)); + + rank = null; + + await AssertQuery( + isAsync, + ss => ss.Set().Where(g => (g.Rank | rank) != rank)); + } + protected async Task AssertTranslationFailed(Func testCode) { Assert.Contains( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index aac491cb831..500d5ead532 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -564,7 +564,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) AND ([g].[Rank] & ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) AND (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -572,7 +572,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR ([g].[Rank] & ( + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -592,7 +592,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) AND (1 & ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) AND (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -600,7 +600,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR (1 & ( + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -626,7 +626,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) AND ([g].[Rank] & ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) AND (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -634,7 +634,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR ([g].[Rank] & ( + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -654,7 +654,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) AND (1 & ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) AND (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -662,7 +662,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR (1 & ( + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -688,7 +688,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId])) AND ([g].[Rank] & ( + ORDER BY [g0].[Nickname], [g0].[SquadId])) AND (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -696,7 +696,7 @@ WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') - ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR ([g].[Rank] & ( + ORDER BY [g0].[Nickname], [g0].[SquadId]) IS NOT NULL)) OR (( SELECT TOP(1) [g0].[Rank] FROM [Gears] AS [g0] WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') @@ -716,7 +716,7 @@ public override async Task Where_enum_has_flag_with_non_nullable_parameter(bool SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ((([g].[Rank] & @__parameter_0) = @__parameter_0) AND [g].[Rank] & @__parameter_0 IS NOT NULL)"); +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & @__parameter_0) = @__parameter_0)"); } public override async Task Where_has_flag_with_nullable_parameter(bool isAsync) @@ -728,7 +728,7 @@ public override async Task Where_has_flag_with_nullable_parameter(bool isAsync) SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ((([g].[Rank] & @__parameter_0) = @__parameter_0) AND [g].[Rank] & @__parameter_0 IS NOT NULL)"); +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & @__parameter_0) = @__parameter_0)"); } public override async Task Select_enum_has_flag(bool isAsync) @@ -1122,7 +1122,7 @@ public override async Task Select_null_propagation_negative6(bool isAsync) AssertSql( @"SELECT CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN ((CAST(LEN([g].[LeaderNickname]) AS int) <> CAST(LEN([g].[LeaderNickname]) AS int)) OR (CAST(LEN([g].[LeaderNickname]) AS int) IS NULL OR CAST(LEN([g].[LeaderNickname]) AS int) IS NULL)) AND (CAST(LEN([g].[LeaderNickname]) AS int) IS NOT NULL OR CAST(LEN([g].[LeaderNickname]) AS int) IS NOT NULL) THEN CAST(1 AS bit) + WHEN ((CAST(LEN([g].[LeaderNickname]) AS int) <> CAST(LEN([g].[LeaderNickname]) AS int)) OR (LEN([g].[LeaderNickname]) IS NULL OR LEN([g].[LeaderNickname]) IS NULL)) AND (LEN([g].[LeaderNickname]) IS NOT NULL OR LEN([g].[LeaderNickname]) IS NOT NULL) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END ELSE NULL @@ -6694,8 +6694,7 @@ FROM [Weapons] AS [w] ORDER BY [w].[Id]) IS NOT NULL)"); } - public override async Task Query_with_complex_let_containing_ordering_and_filter_projecting_firstOrDefault_element_of_let( - bool isAsync) + public override async Task Query_with_complex_let_containing_ordering_and_filter_projecting_firstOrDefault_element_of_let(bool isAsync) { await base.Query_with_complex_let_containing_ordering_and_filter_projecting_firstOrDefault_element_of_let(isAsync); @@ -6709,8 +6708,7 @@ FROM [Gears] AS [g] WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Nickname] <> N'Dom')"); } - public override async Task - Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation(bool isAsync) + public override async Task Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation(bool isAsync) { await base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation(isAsync); @@ -6721,8 +6719,7 @@ public override async Task public override async Task Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(bool isAsync) { - await base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex( - isAsync); + await base.Null_semantics_is_correctly_applied_for_function_comparisons_that_take_arguments_from_optional_navigation_complex(isAsync); AssertSql( @"SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[Note] @@ -7416,6 +7413,93 @@ WHERE [g].[Discriminator] IN (N'Gear', N'Officer') GROUP BY [c].[Name]"); } + public override async Task Group_by_on_StartsWith_with_null_parameter_as_argument(bool isAsync) + { + await base.Group_by_on_StartsWith_with_null_parameter_as_argument(isAsync); + + AssertSql( + @""); + } + + public override async Task Group_by_with_having_StartsWith_with_null_parameter_as_argument(bool isAsync) + { + await base.Group_by_with_having_StartsWith_with_null_parameter_as_argument(isAsync); + + AssertSql( + @"SELECT [g].[FullName] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') +GROUP BY [g].[FullName] +HAVING CAST(0 AS bit) = CAST(1 AS bit)"); + } + + public override async Task Select_StartsWith_with_null_parameter_as_argument(bool isAsync) + { + await base.Select_StartsWith_with_null_parameter_as_argument(isAsync); + + AssertSql( + @""); + } + + public override async Task Select_null_parameter_is_not_null(bool isAsync) + { + await base.Select_null_parameter_is_not_null(isAsync); + + AssertSql( + @"@__p_0='False' + +SELECT @__p_0 +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer')"); + } + + public override async Task Where_null_parameter_is_not_null(bool isAsync) + { + await base.Where_null_parameter_is_not_null(isAsync); + + AssertSql( + @"@__p_0='False' + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (@__p_0 = CAST(1 AS bit))"); + } + + public override async Task OrderBy_StartsWith_with_null_parameter_as_argument(bool isAsync) + { + await base.OrderBy_StartsWith_with_null_parameter_as_argument(isAsync); + + AssertSql( + @""); + } + + public override async Task Where_with_enum_flags_parameter(bool isAsync) + { + await base.Where_with_enum_flags_parameter(isAsync); + + AssertSql( + @"@__rank_0='0' (Nullable = true) + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] & @__rank_0) = @__rank_0)", + // + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer')", + // + @"@__rank_0='1' (Nullable = true) + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND (([g].[Rank] | @__rank_0) <> @__rank_0)", + // + @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE CAST(0 AS bit) = CAST(1 AS bit)"); + + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index acbc08a838a..e2e1f384df9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -834,7 +834,7 @@ public override void Where_equal_with_coalesce() AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE ((COALESCE([e].[NullableStringA], [e].[NullableStringB]) = [e].[NullableStringC]) AND (COALESCE([e].[NullableStringA], [e].[NullableStringB]) IS NOT NULL AND [e].[NullableStringC] IS NOT NULL)) OR (COALESCE([e].[NullableStringA], [e].[NullableStringB]) IS NULL AND [e].[NullableStringC] IS NULL)"); +WHERE ((COALESCE([e].[NullableStringA], [e].[NullableStringB]) = [e].[NullableStringC]) AND (([e].[NullableStringA] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL) AND [e].[NullableStringC] IS NOT NULL)) OR (([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) AND [e].[NullableStringC] IS NULL)"); } public override void Where_not_equal_with_coalesce() @@ -844,7 +844,7 @@ public override void Where_not_equal_with_coalesce() AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE ((COALESCE([e].[NullableStringA], [e].[NullableStringB]) <> [e].[NullableStringC]) OR (COALESCE([e].[NullableStringA], [e].[NullableStringB]) IS NULL OR [e].[NullableStringC] IS NULL)) AND (COALESCE([e].[NullableStringA], [e].[NullableStringB]) IS NOT NULL OR [e].[NullableStringC] IS NOT NULL)"); +WHERE ((COALESCE([e].[NullableStringA], [e].[NullableStringB]) <> [e].[NullableStringC]) OR (([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) OR [e].[NullableStringC] IS NULL)) AND (([e].[NullableStringA] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL) OR [e].[NullableStringC] IS NOT NULL)"); } public override void Where_equal_with_coalesce_both_sides() @@ -854,7 +854,7 @@ public override void Where_equal_with_coalesce_both_sides() AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE (COALESCE([e].[NullableStringA], [e].[NullableStringB]) = COALESCE([e].[StringA], [e].[StringB])) AND COALESCE([e].[NullableStringA], [e].[NullableStringB]) IS NOT NULL"); +WHERE (COALESCE([e].[NullableStringA], [e].[NullableStringB]) = COALESCE([e].[StringA], [e].[StringB])) AND ([e].[NullableStringA] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL)"); } public override void Where_not_equal_with_coalesce_both_sides() @@ -864,7 +864,7 @@ public override void Where_not_equal_with_coalesce_both_sides() AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE ((COALESCE([e].[NullableIntA], [e].[NullableIntB]) <> COALESCE([e].[NullableIntC], [e].[NullableIntB])) OR (COALESCE([e].[NullableIntA], [e].[NullableIntB]) IS NULL OR COALESCE([e].[NullableIntC], [e].[NullableIntB]) IS NULL)) AND (COALESCE([e].[NullableIntA], [e].[NullableIntB]) IS NOT NULL OR COALESCE([e].[NullableIntC], [e].[NullableIntB]) IS NOT NULL)"); +WHERE ((COALESCE([e].[NullableIntA], [e].[NullableIntB]) <> COALESCE([e].[NullableIntC], [e].[NullableIntB])) OR (([e].[NullableIntA] IS NULL AND [e].[NullableIntB] IS NULL) OR ([e].[NullableIntC] IS NULL AND [e].[NullableIntB] IS NULL))) AND (([e].[NullableIntA] IS NOT NULL OR [e].[NullableIntB] IS NOT NULL) OR ([e].[NullableIntC] IS NOT NULL OR [e].[NullableIntB] IS NOT NULL))"); } public override void Where_equal_with_conditional() @@ -1317,7 +1317,7 @@ FROM [Entities1] AS [e] // @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE (([e].[NullableBoolA] = COALESCE([e].[NullableBoolB], [e].[NullableBoolC])) AND ([e].[NullableBoolA] IS NOT NULL AND COALESCE([e].[NullableBoolB], [e].[NullableBoolC]) IS NOT NULL)) OR ([e].[NullableBoolA] IS NULL AND COALESCE([e].[NullableBoolB], [e].[NullableBoolC]) IS NULL)", +WHERE (([e].[NullableBoolA] = COALESCE([e].[NullableBoolB], [e].[NullableBoolC])) AND ([e].[NullableBoolA] IS NOT NULL AND ([e].[NullableBoolB] IS NOT NULL OR [e].[NullableBoolC] IS NOT NULL))) OR ([e].[NullableBoolA] IS NULL AND ([e].[NullableBoolB] IS NULL AND [e].[NullableBoolC] IS NULL))", // @"SELECT [e].[Id] FROM [Entities1] AS [e] @@ -1325,7 +1325,7 @@ FROM [Entities1] AS [e] // @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE ((COALESCE([e].[NullableBoolB], [e].[NullableBoolC]) <> [e].[NullableBoolA]) OR (COALESCE([e].[NullableBoolB], [e].[NullableBoolC]) IS NULL OR [e].[NullableBoolA] IS NULL)) AND (COALESCE([e].[NullableBoolB], [e].[NullableBoolC]) IS NOT NULL OR [e].[NullableBoolA] IS NOT NULL)"); +WHERE ((COALESCE([e].[NullableBoolB], [e].[NullableBoolC]) <> [e].[NullableBoolA]) OR (([e].[NullableBoolB] IS NULL AND [e].[NullableBoolC] IS NULL) OR [e].[NullableBoolA] IS NULL)) AND (([e].[NullableBoolB] IS NOT NULL OR [e].[NullableBoolC] IS NOT NULL) OR [e].[NullableBoolA] IS NOT NULL)"); } public override void Null_semantics_conditional() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs index f78d1e1d294..25fe8f84cc8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs @@ -820,7 +820,7 @@ public override async Task Contains_with_local_list_closure_all_null(bool isAsyn AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE [c].[CustomerID] IS NULL"); +WHERE CAST(0 AS bit) = CAST(1 AS bit)"); } public override async Task Contains_with_local_list_inline(bool isAsync) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs index 4017e3d88c6..7c092752f8d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Where.cs @@ -129,13 +129,13 @@ public override async Task Where_method_call_nullable_type_closure_via_query_cac SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND CAST([e].[ReportsTo] AS bigint) IS NOT NULL", +WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND [e].[ReportsTo] IS NOT NULL", // @"@__p_0='5' (Nullable = true) SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND CAST([e].[ReportsTo] AS bigint) IS NOT NULL"); +WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND [e].[ReportsTo] IS NOT NULL"); } public override async Task Where_method_call_nullable_type_reverse_closure_via_query_cache(bool isAsync) @@ -327,17 +327,17 @@ public override async Task Where_simple_closure_via_query_cache_nullable_type(bo SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND CAST([e].[ReportsTo] AS bigint) IS NOT NULL", +WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND [e].[ReportsTo] IS NOT NULL", // @"@__p_0='5' (Nullable = true) SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND CAST([e].[ReportsTo] AS bigint) IS NOT NULL", +WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND [e].[ReportsTo] IS NOT NULL", // @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE CAST([e].[ReportsTo] AS bigint) IS NULL"); +WHERE [e].[ReportsTo] IS NULL"); } public override async Task Where_simple_closure_via_query_cache_nullable_type_reverse(bool isAsync) @@ -347,19 +347,19 @@ public override async Task Where_simple_closure_via_query_cache_nullable_type_re AssertSql( @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE CAST([e].[ReportsTo] AS bigint) IS NULL", +WHERE [e].[ReportsTo] IS NULL", // @"@__p_0='5' (Nullable = true) SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND CAST([e].[ReportsTo] AS bigint) IS NOT NULL", +WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND [e].[ReportsTo] IS NOT NULL", // @"@__p_0='2' (Nullable = true) SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND CAST([e].[ReportsTo] AS bigint) IS NOT NULL"); +WHERE (CAST([e].[ReportsTo] AS bigint) = @__p_0) AND [e].[ReportsTo] IS NOT NULL"); } public override void Where_subquery_closure_via_query_cache() @@ -647,7 +647,7 @@ public override async Task Where_string_length(bool isAsync) AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE (CAST(LEN([c].[City]) AS int) = 6) AND CAST(LEN([c].[City]) AS int) IS NOT NULL"); +WHERE (CAST(LEN([c].[City]) AS int) = 6) AND LEN([c].[City]) IS NOT NULL"); } public override async Task Where_string_indexof(bool isAsync) @@ -1292,7 +1292,7 @@ public override async Task Where_concat_string_int_comparison1(bool isAsync) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE ((([c].[CustomerID] + CAST(@__i_0 AS nchar(5))) = [c].[CompanyName]) AND ([c].[CustomerID] + CAST(@__i_0 AS nchar(5)) IS NOT NULL AND [c].[CompanyName] IS NOT NULL)) OR ([c].[CustomerID] + CAST(@__i_0 AS nchar(5)) IS NULL AND [c].[CompanyName] IS NULL)"); +WHERE (([c].[CustomerID] + CAST(@__i_0 AS nchar(5))) = [c].[CompanyName]) AND [c].[CompanyName] IS NOT NULL"); } public override async Task Where_concat_string_int_comparison2(bool isAsync) @@ -1304,7 +1304,7 @@ public override async Task Where_concat_string_int_comparison2(bool isAsync) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE (((CAST(@__i_0 AS nchar(5)) + [c].[CustomerID]) = [c].[CompanyName]) AND (CAST(@__i_0 AS nchar(5)) + [c].[CustomerID] IS NOT NULL AND [c].[CompanyName] IS NOT NULL)) OR (CAST(@__i_0 AS nchar(5)) + [c].[CustomerID] IS NULL AND [c].[CompanyName] IS NULL)"); +WHERE ((CAST(@__i_0 AS nchar(5)) + [c].[CustomerID]) = [c].[CompanyName]) AND [c].[CompanyName] IS NOT NULL"); } public override async Task Where_concat_string_int_comparison3(bool isAsync) @@ -1317,7 +1317,7 @@ public override async Task Where_concat_string_int_comparison3(bool isAsync) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE (((((CAST(@__p_0 AS nchar(5)) + [c].[CustomerID]) + CAST(@__j_1 AS nchar(5))) + CAST(42 AS nchar(5))) = [c].[CompanyName]) AND (((CAST(@__p_0 AS nchar(5)) + [c].[CustomerID]) + CAST(@__j_1 AS nchar(5))) + CAST(42 AS nchar(5)) IS NOT NULL AND [c].[CompanyName] IS NOT NULL)) OR (((CAST(@__p_0 AS nchar(5)) + [c].[CustomerID]) + CAST(@__j_1 AS nchar(5))) + CAST(42 AS nchar(5)) IS NULL AND [c].[CompanyName] IS NULL)"); +WHERE ((((CAST(@__p_0 AS nchar(5)) + [c].[CustomerID]) + CAST(@__j_1 AS nchar(5))) + CAST(42 AS nchar(5))) = [c].[CompanyName]) AND [c].[CompanyName] IS NOT NULL"); } public override async Task Where_concat_string_int_comparison4(bool isAsync) @@ -1327,7 +1327,7 @@ public override async Task Where_concat_string_int_comparison4(bool isAsync) AssertSql( @"SELECT [o].[CustomerID] FROM [Orders] AS [o] -WHERE (((CAST([o].[OrderID] AS nchar(5)) + [o].[CustomerID]) = [o].[CustomerID]) AND (CAST([o].[OrderID] AS nchar(5)) + [o].[CustomerID] IS NOT NULL AND [o].[CustomerID] IS NOT NULL)) OR (CAST([o].[OrderID] AS nchar(5)) + [o].[CustomerID] IS NULL AND [o].[CustomerID] IS NULL)"); +WHERE (((CAST([o].[OrderID] AS nchar(5)) + [o].[CustomerID]) = [o].[CustomerID]) AND ([o].[CustomerID] IS NOT NULL AND [o].[CustomerID] IS NOT NULL)) OR ([o].[CustomerID] IS NULL AND [o].[CustomerID] IS NULL)"); } public override async Task Where_concat_string_string_comparison(bool isAsync) @@ -1339,7 +1339,7 @@ public override async Task Where_concat_string_string_comparison(bool isAsync) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE (((@__i_0 + [c].[CustomerID]) = [c].[CompanyName]) AND (@__i_0 + [c].[CustomerID] IS NOT NULL AND [c].[CompanyName] IS NOT NULL)) OR (@__i_0 + [c].[CustomerID] IS NULL AND [c].[CompanyName] IS NULL)"); +WHERE ((@__i_0 + [c].[CustomerID]) = [c].[CompanyName]) AND [c].[CompanyName] IS NOT NULL"); } public override async Task Where_string_concat_method_comparison(bool isAsync) @@ -1351,7 +1351,7 @@ public override async Task Where_string_concat_method_comparison(bool isAsync) SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE (((@__i_0 + [c].[CustomerID]) = [c].[CompanyName]) AND (@__i_0 + [c].[CustomerID] IS NOT NULL AND [c].[CompanyName] IS NOT NULL)) OR (@__i_0 + [c].[CustomerID] IS NULL AND [c].[CompanyName] IS NULL)"); +WHERE ((@__i_0 + [c].[CustomerID]) = [c].[CompanyName]) AND [c].[CompanyName] IS NOT NULL"); } public override async Task Where_ternary_boolean_condition_true(bool isAsync) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs index 4035d17a34d..84c8615ee35 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs @@ -1522,7 +1522,7 @@ public override async Task All_top_level_column(bool isAsync) WHEN NOT EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE (([c].[ContactName] <> N'') OR [c].[ContactName] IS NULL) AND ((([c].[ContactName] IS NULL AND ([c].[ContactName] IS NOT NULL AND (CAST(0 AS bit) = CAST(1 AS bit)))) OR ([c].[ContactName] IS NULL AND (CAST(1 AS bit) = CAST(1 AS bit)))) OR (([c].[ContactName] IS NULL AND (CAST(0 AS bit) = CAST(1 AS bit))) OR (LEFT([c].[ContactName], LEN([c].[ContactName])) <> [c].[ContactName])))) THEN CAST(1 AS bit) + 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) ELSE CAST(0 AS bit) END"); } @@ -2078,7 +2078,7 @@ FROM [Customers] AS [c] ORDER BY [c].[CustomerID] OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY ) AS [t] - WHERE ([t].[CustomerID] IS NULL AND (CAST(0 AS bit) = CAST(1 AS bit))) OR NOT ([t].[CustomerID] LIKE N'B%')) THEN CAST(1 AS bit) + WHERE NOT ([t].[CustomerID] LIKE N'B%')) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END"); } @@ -2099,7 +2099,7 @@ SELECT TOP(@__p_0) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName FROM [Customers] AS [c] ORDER BY [c].[CustomerID] ) AS [t] - WHERE ([t].[CustomerID] IS NULL AND (CAST(0 AS bit) = CAST(1 AS bit))) OR NOT ([t].[CustomerID] LIKE N'A%')) THEN CAST(1 AS bit) + WHERE NOT ([t].[CustomerID] LIKE N'A%')) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END"); } @@ -2641,7 +2641,7 @@ public override async Task Filter_coalesce_operator(bool isAsync) AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE (COALESCE([c].[CompanyName], [c].[ContactName]) = N'The Big Cheese') AND COALESCE([c].[CompanyName], [c].[ContactName]) IS NOT NULL"); +WHERE (COALESCE([c].[CompanyName], [c].[ContactName]) = N'The Big Cheese') AND ([c].[CompanyName] IS NOT NULL OR [c].[ContactName] IS NOT NULL)"); } [SqlServerCondition(SqlServerCondition.SupportsOffset)] @@ -3693,7 +3693,7 @@ public override async Task Anonymous_complex_distinct_where(bool isAsync) AssertSql( @"SELECT DISTINCT [c].[CustomerID] + [c].[City] AS [A] FROM [Customers] AS [c] -WHERE (([c].[CustomerID] + [c].[City]) = N'ALFKIBerlin') AND [c].[CustomerID] + [c].[City] IS NOT NULL"); +WHERE (([c].[CustomerID] + [c].[City]) = N'ALFKIBerlin') AND [c].[City] IS NOT NULL"); } public override async Task Anonymous_complex_distinct_orderby(bool isAsync) @@ -3797,7 +3797,7 @@ public override async Task DTO_complex_distinct_where(bool isAsync) AssertSql( @"SELECT DISTINCT [c].[CustomerID] + [c].[City] AS [Property] FROM [Customers] AS [c] -WHERE (([c].[CustomerID] + [c].[City]) = N'ALFKIBerlin') AND [c].[CustomerID] + [c].[City] IS NOT NULL"); +WHERE (([c].[CustomerID] + [c].[City]) = N'ALFKIBerlin') AND [c].[City] IS NOT NULL"); } public override async Task DTO_complex_distinct_orderby(bool isAsync) @@ -4277,7 +4277,7 @@ public override async Task Comparing_entity_to_null_using_Equals(bool isAsync) AssertSql( @"SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE ([c].[CustomerID] LIKE N'A%') AND ([c].[CustomerID] IS NOT NULL OR (CAST(1 AS bit) = CAST(1 AS bit))) +WHERE [c].[CustomerID] LIKE N'A%' ORDER BY [c].[CustomerID]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs index 83222dd103b..a252b5cd101 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs @@ -327,7 +327,7 @@ public override async Task GetInteriorRingN(bool isAsync) AssertSql( @"SELECT [p].[Id], CASE - WHEN [p].[Polygon] IS NULL OR ((([p].[Polygon].NumRings() - 1) = 0) AND [p].[Polygon].NumRings() - 1 IS NOT NULL) THEN NULL + WHEN [p].[Polygon] IS NULL OR ((([p].[Polygon].NumRings() - 1) = 0) AND [p].[Polygon].NumRings() IS NOT NULL) THEN NULL ELSE [p].[Polygon].RingN(0 + 2) END AS [InteriorRing0] FROM [PolygonEntity] AS [p]"); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SimpleQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SimpleQuerySqliteTest.cs index 64abd1c92c6..a90444f93db 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/SimpleQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SimpleQuerySqliteTest.cs @@ -494,7 +494,7 @@ public override async Task Where_datetime_year_component(bool isAsync) AssertSql( @"SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" FROM ""Orders"" AS ""o"" -WHERE (CAST(strftime('%Y', ""o"".""OrderDate"") AS INTEGER) = 1998) AND CAST(strftime('%Y', ""o"".""OrderDate"") AS INTEGER) IS NOT NULL"); +WHERE (CAST(strftime('%Y', ""o"".""OrderDate"") AS INTEGER) = 1998) AND strftime('%Y', ""o"".""OrderDate"") IS NOT NULL"); } public override async Task Where_datetime_month_component(bool isAsync) @@ -504,7 +504,7 @@ public override async Task Where_datetime_month_component(bool isAsync) AssertSql( @"SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" FROM ""Orders"" AS ""o"" -WHERE (CAST(strftime('%m', ""o"".""OrderDate"") AS INTEGER) = 4) AND CAST(strftime('%m', ""o"".""OrderDate"") AS INTEGER) IS NOT NULL"); +WHERE (CAST(strftime('%m', ""o"".""OrderDate"") AS INTEGER) = 4) AND strftime('%m', ""o"".""OrderDate"") IS NOT NULL"); } public override async Task Where_datetime_dayOfYear_component(bool isAsync) @@ -514,7 +514,7 @@ public override async Task Where_datetime_dayOfYear_component(bool isAsync) AssertSql( @"SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" FROM ""Orders"" AS ""o"" -WHERE (CAST(strftime('%j', ""o"".""OrderDate"") AS INTEGER) = 68) AND CAST(strftime('%j', ""o"".""OrderDate"") AS INTEGER) IS NOT NULL"); +WHERE (CAST(strftime('%j', ""o"".""OrderDate"") AS INTEGER) = 68) AND strftime('%j', ""o"".""OrderDate"") IS NOT NULL"); } public override async Task Where_datetime_day_component(bool isAsync) @@ -524,7 +524,7 @@ public override async Task Where_datetime_day_component(bool isAsync) AssertSql( @"SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" FROM ""Orders"" AS ""o"" -WHERE (CAST(strftime('%d', ""o"".""OrderDate"") AS INTEGER) = 4) AND CAST(strftime('%d', ""o"".""OrderDate"") AS INTEGER) IS NOT NULL"); +WHERE (CAST(strftime('%d', ""o"".""OrderDate"") AS INTEGER) = 4) AND strftime('%d', ""o"".""OrderDate"") IS NOT NULL"); } public override async Task Where_datetime_hour_component(bool isAsync) @@ -534,7 +534,7 @@ public override async Task Where_datetime_hour_component(bool isAsync) AssertSql( @"SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" FROM ""Orders"" AS ""o"" -WHERE (CAST(strftime('%H', ""o"".""OrderDate"") AS INTEGER) = 14) AND CAST(strftime('%H', ""o"".""OrderDate"") AS INTEGER) IS NOT NULL"); +WHERE (CAST(strftime('%H', ""o"".""OrderDate"") AS INTEGER) = 14) AND strftime('%H', ""o"".""OrderDate"") IS NOT NULL"); } public override async Task Where_datetime_minute_component(bool isAsync) @@ -544,7 +544,7 @@ public override async Task Where_datetime_minute_component(bool isAsync) AssertSql( @"SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" FROM ""Orders"" AS ""o"" -WHERE (CAST(strftime('%M', ""o"".""OrderDate"") AS INTEGER) = 23) AND CAST(strftime('%M', ""o"".""OrderDate"") AS INTEGER) IS NOT NULL"); +WHERE (CAST(strftime('%M', ""o"".""OrderDate"") AS INTEGER) = 23) AND strftime('%M', ""o"".""OrderDate"") IS NOT NULL"); } public override async Task Where_datetime_second_component(bool isAsync) @@ -554,7 +554,7 @@ public override async Task Where_datetime_second_component(bool isAsync) AssertSql( @"SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate"" FROM ""Orders"" AS ""o"" -WHERE (CAST(strftime('%S', ""o"".""OrderDate"") AS INTEGER) = 44) AND CAST(strftime('%S', ""o"".""OrderDate"") AS INTEGER) IS NOT NULL"); +WHERE (CAST(strftime('%S', ""o"".""OrderDate"") AS INTEGER) = 44) AND strftime('%S', ""o"".""OrderDate"") IS NOT NULL"); } [ConditionalTheory(Skip = "Issue#15586")] @@ -715,7 +715,7 @@ public override async Task Where_string_indexof(bool isAsync) AssertSql( @"SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" FROM ""Customers"" AS ""c"" -WHERE ((instr(""c"".""City"", 'Sea') - 1) <> -1) OR instr(""c"".""City"", 'Sea') - 1 IS NULL"); +WHERE ((instr(""c"".""City"", 'Sea') - 1) <> -1) OR instr(""c"".""City"", 'Sea') IS NULL"); } public override async Task Indexof_with_emptystring(bool isAsync)