diff --git a/src/EFCore.Relational/Query/Internal/IsNullOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/IsNullOptimizingExpressionVisitor.cs new file mode 100644 index 00000000000..dca264ed5fb --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/IsNullOptimizingExpressionVisitor.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + public class IsNullOptimizingExpressionVisitor : ExpressionVisitor + { + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + public IsNullOptimizingExpressionVisitor(ISqlExpressionFactory sqlExpressionFactory) + { + _sqlExpressionFactory = sqlExpressionFactory; + } + + protected override Expression VisitExtension(Expression extensionExpression) + { + if (extensionExpression is SqlUnaryExpression sqlUnaryExpression) + { + if (sqlUnaryExpression.OperatorType == ExpressionType.Equal) + { + switch (sqlUnaryExpression.Operand) + { + case SqlUnaryExpression sqlUnaryOperand + when sqlUnaryOperand.OperatorType == ExpressionType.Convert + || sqlUnaryOperand.OperatorType == ExpressionType.Not + || sqlUnaryOperand.OperatorType == ExpressionType.Negate: + return (SqlExpression)Visit(_sqlExpressionFactory.IsNull(sqlUnaryOperand.Operand)); + + case SqlBinaryExpression sqlBinaryOperand: + var newLeft = (SqlExpression)Visit(_sqlExpressionFactory.IsNull(sqlBinaryOperand.Left)); + var newRight = (SqlExpression)Visit(_sqlExpressionFactory.IsNull(sqlBinaryOperand.Right)); + + return sqlBinaryOperand.OperatorType == ExpressionType.Coalesce + ? _sqlExpressionFactory.AndAlso(newLeft, newRight) + : _sqlExpressionFactory.OrElse(newLeft, newRight); + + case ColumnExpression columnOperand + when !columnOperand.IsNullable: + return _sqlExpressionFactory.Constant(false, sqlUnaryExpression.TypeMapping); + } + } + + if (sqlUnaryExpression.OperatorType == ExpressionType.Equal) + { + switch (sqlUnaryExpression.Operand) + { + case SqlUnaryExpression sqlUnaryOperand + when sqlUnaryOperand.OperatorType == ExpressionType.Convert + || sqlUnaryOperand.OperatorType == ExpressionType.Not + || sqlUnaryOperand.OperatorType == ExpressionType.Negate: + return (SqlExpression)Visit(_sqlExpressionFactory.IsNotNull(sqlUnaryOperand.Operand)); + + case SqlBinaryExpression sqlBinaryOperand: + var newLeft = (SqlExpression)Visit(_sqlExpressionFactory.IsNotNull(sqlBinaryOperand.Left)); + var newRight = (SqlExpression)Visit(_sqlExpressionFactory.IsNotNull(sqlBinaryOperand.Right)); + + return sqlBinaryOperand.OperatorType == ExpressionType.Coalesce + ? _sqlExpressionFactory.OrElse(newLeft, newRight) + : _sqlExpressionFactory.AndAlso(newLeft, newRight); + + case ColumnExpression columnOperand + when !columnOperand.IsNullable: + return _sqlExpressionFactory.Constant(true, sqlUnaryExpression.TypeMapping); + } + } + } + + return base.VisitExtension(extensionExpression); + } + } +} diff --git a/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs index e35a76c166f..88adca66232 100644 --- a/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs @@ -242,11 +242,11 @@ 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); // 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) diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs index 4ff4fd7ee60..832552722ec 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,16 @@ 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 +146,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 SimplifyLogicalSqlBinaryExpression( + innerBinary.OperatorType == ExpressionType.AndAlso + ? ExpressionType.OrElse + : ExpressionType.AndAlso, + newLeft, + newRight, + innerBinary.TypeMapping); } // those optimizations are only valid in 2-value logic @@ -168,36 +183,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 SimplifyLogicalSqlBinaryExpression( + sqlBinaryExpression.OperatorType, + newLeft, + newRight, + sqlBinaryExpression.TypeMapping); } // those optimizations are only valid in 2-value logic @@ -227,5 +217,43 @@ private Expression VisitSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpress return sqlBinaryExpression.Update(newLeft, newRight); } + + private SqlExpression SimplifyLogicalSqlBinaryExpression( + 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/RelationalQueryTranslationPostprocessor.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs index 3fd24558073..824015b6c0f 100644 --- a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs @@ -42,6 +42,7 @@ public override Expression Process(Expression query) query = new NullSemanticsRewritingExpressionVisitor(SqlExpressionFactory).Visit(query); } + query = new IsNullOptimizingExpressionVisitor(SqlExpressionFactory).Visit(query); query = OptimizeSqlExpression(query); query = new NullComparisonTransformingExpressionVisitor().Visit(query); diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ParameterNullabilityOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ParameterNullabilityOptimizingExpressionVisitor.cs index 771aa6bfbd0..993dbe30b46 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ParameterNullabilityOptimizingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ParameterNullabilityOptimizingExpressionVisitor.cs @@ -30,16 +30,33 @@ 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) + ? (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 newPredicate != newSelectExpression.Predicate + || newHaving != newSelectExpression.Having ? 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, 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.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index 1e894588bb1..e5db7fb1c58 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -982,6 +982,25 @@ public virtual void Null_semantics_with_null_check_complex() } } + [ConditionalFact] + public virtual void IsNull_on_complex_expression() + { + using (var ctx = CreateContext()) + { + var query1 = ctx.Entities1.Where(e => -e.NullableIntA != null).ToList(); + Assert.Equal(18, query1.Count); + + var query2 = ctx.Entities1.Where(e => (e.NullableIntA + e.NullableIntB) == null).ToList(); + Assert.Equal(15, query2.Count); + + var query3 = ctx.Entities1.Where(e => (e.NullableIntA ?? e.NullableIntB) == null).ToList(); + Assert.Equal(3, query3.Count); + + var query4 = ctx.Entities1.Where(e => (e.NullableIntA ?? e.NullableIntB) != null).ToList(); + Assert.Equal(24, query4.Count); + } + } + protected static TResult Maybe(object caller, Func expression) where TResult : class { 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..53293cbdc49 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,92 @@ 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..3b84689bbbc 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() @@ -1438,6 +1438,28 @@ FROM [Entities1] AS [e] WHERE ([e].[NullableIntA] IS NOT NULL OR [e].[NullableIntB] IS NOT NULL) AND ((([e].[NullableIntA] = [e].[NullableIntC]) AND ([e].[NullableIntA] IS NOT NULL AND [e].[NullableIntC] IS NOT NULL)) OR ([e].[NullableIntA] IS NULL AND [e].[NullableIntC] IS NULL))"); } + public override void IsNull_on_complex_expression() + { + base.IsNull_on_complex_expression(); + + AssertSql( + @"SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] +FROM [Entities1] AS [e] +WHERE [e].[NullableIntA] IS NOT NULL", + // + @"SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] +FROM [Entities1] AS [e] +WHERE [e].[NullableIntA] IS NULL OR [e].[NullableIntB] IS NULL", + // + @"SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] +FROM [Entities1] AS [e] +WHERE [e].[NullableIntA] IS NULL AND [e].[NullableIntB] IS NULL", + // + @"SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] +FROM [Entities1] AS [e] +WHERE [e].[NullableIntA] IS NOT NULL OR [e].[NullableIntB] IS NOT NULL"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); 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)