From 9ca9ce7a57683fe10fd4ed1985798904568f9748 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Fri, 10 Jan 2020 14:39:49 -0800 Subject: [PATCH] Fix to #18555 - Query: when rewriting null semantics for comparisons with functions use function specific metadata to get better SQL When we need to compute whether a function is null, we often can just evaluate nullability of it's constituents (instance & arguments), e.g. SUBSTRING(stringProperty, 0, 5) == null -> stringProperty == null Adding metadata to SqlFunctionExpression: nullResultAllowed - indicates whether function can ever be null, instancePropagatesNullability - indicates whether function instance can be used to calculate nullability of the entire function argumentsPropagateNullability - array indicating which (if any) function arguments can be used to calculate nullability of the entire function If "canBeNull" is set to false we can instantly compute IsNull/IsNotNull of that function. Otherwise, we look at values of instancePropagatesNullability and argumentsPropagateNullability - if any of them are set to true, we use corresponding argument(s) to compute function nullability. If all of them are set to false we must fallback to the old method and evaluate nullability of the entire function. --- .../Query/ISqlExpressionFactory.cs | 48 ++ ...lityBasedSqlProcessingExpressionVisitor.cs | 116 +++-- .../RelationalMethodCallTranslatorProvider.cs | 2 + ...lationalSqlTranslatingExpressionVisitor.cs | 57 ++- .../Query/SqlExpressionFactory.cs | 96 +++- .../SqlExpressions/SqlFunctionExpression.cs | 186 ++++++- ...erverGeometryCollectionMemberTranslator.cs | 3 + ...erverGeometryCollectionMethodTranslator.cs | 3 + .../SqlServerGeometryMemberTranslator.cs | 8 + .../SqlServerGeometryMethodTranslator.cs | 23 +- .../SqlServerLineStringMemberTranslator.cs | 3 + .../SqlServerLineStringMethodTranslator.cs | 3 + ...qlServerMultiLineStringMemberTranslator.cs | 3 + .../SqlServerPointMemberTranslator.cs | 2 + .../SqlServerPolygonMemberTranslator.cs | 9 + .../SqlServerPolygonMethodTranslator.cs | 6 + .../SqlServerByteArrayMethodTranslator.cs | 7 +- .../Internal/SqlServerConvertTranslator.cs | 2 + .../SqlServerDateDiffFunctionsTranslator.cs | 2 + .../SqlServerDateTimeMemberTranslator.cs | 12 + .../SqlServerDateTimeMethodTranslator.cs | 2 + .../SqlServerFromPartsFunctionTranslator.cs | 12 + ...ServerFullTextSearchFunctionsTranslator.cs | 4 + .../SqlServerIsDateFunctionTranslator.cs | 2 + .../Query/Internal/SqlServerMathTranslator.cs | 6 + .../Internal/SqlServerNewGuidTranslator.cs | 2 + .../SqlServerObjectToStringTranslator.cs | 2 + .../Internal/SqlServerQuerySqlGenerator.cs | 2 + ...qlServerSqlTranslatingExpressionVisitor.cs | 13 +- .../SqlServerStringMemberTranslator.cs | 7 +- .../SqlServerStringMethodTranslator.cs | 52 +- .../SqliteByteArrayMethodTranslator.cs | 14 +- .../Internal/SqliteDateTimeAddTranslator.cs | 4 + .../SqliteDateTimeMemberTranslator.cs | 6 + .../Query/Internal/SqliteExpression.cs | 6 +- .../Query/Internal/SqliteMathTranslator.cs | 7 +- .../SqliteSqlTranslatingExpressionVisitor.cs | 7 +- .../Internal/SqliteStringLengthTranslator.cs | 7 +- .../Internal/SqliteStringMethodTranslator.cs | 38 +- ...qliteGeometryCollectionMemberTranslator.cs | 7 +- ...qliteGeometryCollectionMethodTranslator.cs | 2 + .../SqliteGeometryMemberTranslator.cs | 33 +- .../SqliteGeometryMethodTranslator.cs | 40 +- .../SqliteLineStringMemberTranslator.cs | 26 +- .../SqliteLineStringMethodTranslator.cs | 2 + .../SqliteMultiLineStringMemberTranslator.cs | 2 + .../Internal/SqlitePointMemberTranslator.cs | 7 +- .../Internal/SqlitePolygonMemberTranslator.cs | 7 +- .../Internal/SqlitePolygonMethodTranslator.cs | 2 + .../Query/SpatialQueryInMemoryTest.cs | 21 + .../Query/UdfDbFunctionTestBase.cs | 32 +- .../Query/SpatialQueryTestBase.cs | 285 +++++++++-- .../ComplexNavigationsQuerySqlServerTest.cs | 2 +- .../Query/FunkyDataQuerySqlServerTest.cs | 18 +- .../Query/GearsOfWarQuerySqlServerTest.cs | 18 +- .../NorthwindFunctionsQuerySqlServerTest.cs | 18 +- ...orthwindMiscellaneousQuerySqlServerTest.cs | 2 +- .../NorthwindSelectQuerySqlServerTest.cs | 2 +- .../Query/NorthwindWhereQuerySqlServerTest.cs | 10 +- .../Query/NullSemanticsQuerySqlServerTest.cs | 6 +- .../Query/QueryBugsTest.cs | 2 +- .../Query/SpatialQuerySqlServerFixture.cs | 7 +- .../SpatialQuerySqlServerGeographyTest.cs | 308 ++++++++---- .../SpatialQuerySqlServerGeometryTest.cs | 382 ++++++++------ .../Query/GearsOfWarQuerySqliteTest.cs | 2 +- .../Query/NorthwindWhereQuerySqliteTest.cs | 8 +- .../Query/SpatialQuerySqliteFixture.cs | 5 +- .../Query/SpatialQuerySqliteTest.cs | 476 +++++++++++------- 68 files changed, 1865 insertions(+), 651 deletions(-) diff --git a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs index 955e4857bbe..a1d98adacd4 100644 --- a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs @@ -131,6 +131,54 @@ SqlFunctionExpression Function( [NotNull] Type returnType, [CanBeNull] RelationalTypeMapping typeMapping = null); + SqlFunctionExpression Function( + [NotNull] string name, + [NotNull] IEnumerable arguments, + bool nullResultAllowed, + [NotNull] IEnumerable argumentsPropagateNullability, + [NotNull] Type returnType, + [CanBeNull] RelationalTypeMapping typeMapping = null); + + SqlFunctionExpression Function( + [CanBeNull] string schema, + [NotNull] string name, + [NotNull] IEnumerable arguments, + bool nullResultAllowed, + [NotNull] IEnumerable argumentsPropagateNullability, + [NotNull] Type returnType, + [CanBeNull] RelationalTypeMapping typeMapping = null); + + SqlFunctionExpression Function( + [CanBeNull] SqlExpression instance, + [NotNull] string name, + [NotNull] IEnumerable arguments, + bool nullResultAllowed, + bool instancePropagatesNullability, + [NotNull] IEnumerable argumentsPropagateNullability, + [NotNull] Type returnType, + [CanBeNull] RelationalTypeMapping typeMapping = null); + + SqlFunctionExpression Function( + [NotNull] string name, + bool nullResultAllowed, + [NotNull] Type returnType, + [CanBeNull] RelationalTypeMapping typeMapping = null); + + SqlFunctionExpression Function( + [NotNull] string schema, + [NotNull] string name, + bool nullResultAllowed, + [NotNull] Type returnType, + [CanBeNull] RelationalTypeMapping typeMapping = null); + + SqlFunctionExpression Function( + [CanBeNull] SqlExpression instance, + [NotNull] string name, + bool nullResultAllowed, + bool instancePropagatesNullability, + [NotNull] Type returnType, + [CanBeNull] RelationalTypeMapping typeMapping = null); + ExistsExpression Exists([NotNull] SelectExpression subquery, bool negated); InExpression In([NotNull] SqlExpression item, [NotNull] SqlExpression values, bool negated); InExpression In([NotNull] SqlExpression item, [NotNull] SelectExpression subquery, bool negated); diff --git a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs index 63802c16f36..ca7cec44a90 100644 --- a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs @@ -898,8 +898,7 @@ private SqlExpression RewriteNullSemantics( return sqlBinaryExpression.Update(left, right); } - private SqlExpression SimplifyLogicalSqlBinaryExpression( - SqlBinaryExpression sqlBinaryExpression) + private SqlExpression SimplifyLogicalSqlBinaryExpression(SqlBinaryExpression sqlBinaryExpression) { var leftUnary = sqlBinaryExpression.Left as SqlUnaryExpression; var rightUnary = sqlBinaryExpression.Right as SqlUnaryExpression; @@ -1253,37 +1252,96 @@ protected virtual SqlExpression ProcessNullNotNull( sqlUnaryExpression.TypeMapping)); } - case SqlFunctionExpression sqlFunctionExpression - when sqlFunctionExpression.IsBuiltIn && string.Equals("COALESCE", sqlFunctionExpression.Name, StringComparison.OrdinalIgnoreCase): + case SqlFunctionExpression sqlFunctionExpression: { - // for coalesce: - // (a ?? b) == null -> a == null && b == null - // (a ?? b) != null -> a != null || b != null - var left = ProcessNullNotNull( - SqlExpressionFactory.MakeUnary( - sqlUnaryExpression.OperatorType, - sqlFunctionExpression.Arguments[0], - typeof(bool), - sqlUnaryExpression.TypeMapping), - operandNullable: null); + if (sqlFunctionExpression.IsBuiltIn && string.Equals("COALESCE", sqlFunctionExpression.Name, StringComparison.OrdinalIgnoreCase)) + { + // for coalesce: + // (a ?? b) == null -> a == null && b == null + // (a ?? b) != null -> a != null || b != null + var left = ProcessNullNotNull( + SqlExpressionFactory.MakeUnary( + sqlUnaryExpression.OperatorType, + sqlFunctionExpression.Arguments[0], + typeof(bool), + sqlUnaryExpression.TypeMapping), + operandNullable: null); + + var right = ProcessNullNotNull( + SqlExpressionFactory.MakeUnary( + sqlUnaryExpression.OperatorType, + sqlFunctionExpression.Arguments[1], + typeof(bool), + sqlUnaryExpression.TypeMapping), + operandNullable: null); - var right = ProcessNullNotNull( - SqlExpressionFactory.MakeUnary( - sqlUnaryExpression.OperatorType, - sqlFunctionExpression.Arguments[1], - typeof(bool), - sqlUnaryExpression.TypeMapping), - operandNullable: null); + return SimplifyLogicalSqlBinaryExpression( + SqlExpressionFactory.MakeBinary( + sqlUnaryExpression.OperatorType == ExpressionType.Equal + ? ExpressionType.AndAlso + : ExpressionType.OrElse, + left, + right, + sqlUnaryExpression.TypeMapping)); + } - return SimplifyLogicalSqlBinaryExpression( - SqlExpressionFactory.MakeBinary( - sqlUnaryExpression.OperatorType == ExpressionType.Equal - ? ExpressionType.AndAlso - : ExpressionType.OrElse, - left, - right, - sqlUnaryExpression.TypeMapping)); + if (!sqlFunctionExpression.NullResultAllowed) + { + // when we know that function can't be nullable: + // non_nullable_function() is null-> false + // non_nullable_function() is not null -> true + return SqlExpressionFactory.Constant( + sqlUnaryExpression.OperatorType == ExpressionType.NotEqual, + sqlUnaryExpression.TypeMapping); + } + + // see if we can derive function nullability from it's instance and/or arguments + // rather than evaluating nullability of the entire function + var nullabilityPropagationElements = new List(); + if (sqlFunctionExpression.Instance != null + && sqlFunctionExpression.InstancPropagatesNullability == true) + { + nullabilityPropagationElements.Add(sqlFunctionExpression.Instance); + } + + for (var i = 0; i < sqlFunctionExpression.Arguments.Count; i++) + { + if (sqlFunctionExpression.ArgumentsPropagateNullability[i]) + { + nullabilityPropagationElements.Add(sqlFunctionExpression.Arguments[i]); + } + } + + if (nullabilityPropagationElements.Count > 0) + { + var result = ProcessNullNotNull( + SqlExpressionFactory.MakeUnary( + sqlUnaryExpression.OperatorType, + nullabilityPropagationElements[0], + sqlUnaryExpression.Type, + sqlUnaryExpression.TypeMapping), + operandNullable: null); + + foreach (var element in nullabilityPropagationElements.Skip(1)) + { + result = SimplifyLogicalSqlBinaryExpression( + sqlUnaryExpression.OperatorType == ExpressionType.Equal + ? SqlExpressionFactory.OrElse( + result, + ProcessNullNotNull( + SqlExpressionFactory.IsNull(element), + operandNullable: null)) + : SqlExpressionFactory.AndAlso( + result, + ProcessNullNotNull( + SqlExpressionFactory.IsNotNull(element), + operandNullable: null))); + } + + return result; + } } + break; } return sqlUnaryExpression; diff --git a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs index 20f3cddb026..83ef27398bf 100644 --- a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs +++ b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs @@ -56,6 +56,8 @@ public virtual SqlExpression Translate( dbFunction.Schema, dbFunction.Name, arguments, + nullResultAllowed: true, + argumentsPropagateNullability: arguments.Select(a => true).ToList(), method.ReturnType); } diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 1bc21038d94..ff57a2e8ea6 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -99,11 +99,20 @@ public virtual SqlExpression TranslateAverage([NotNull] Expression expression) return inputType == typeof(float) ? SqlExpressionFactory.Convert( SqlExpressionFactory.Function( - "AVG", new[] { sqlExpression }, typeof(double)), + "AVG", + new[] { sqlExpression }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false }, + typeof(double)), sqlExpression.Type, sqlExpression.TypeMapping) : (SqlExpression)SqlExpressionFactory.Function( - "AVG", new[] { sqlExpression }, sqlExpression.Type, sqlExpression.TypeMapping); + "AVG", + new[] { sqlExpression }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false }, + sqlExpression.Type, + sqlExpression.TypeMapping); } public virtual SqlExpression TranslateCount([CanBeNull] Expression expression = null) @@ -115,7 +124,12 @@ public virtual SqlExpression TranslateCount([CanBeNull] Expression expression = } return SqlExpressionFactory.ApplyDefaultTypeMapping( - SqlExpressionFactory.Function("COUNT", new[] { SqlExpressionFactory.Fragment("*") }, typeof(int))); + SqlExpressionFactory.Function( + "COUNT", + new[] { SqlExpressionFactory.Fragment("*") }, + nullResultAllowed: false, + argumentsPropagateNullability: new[] { false }, + typeof(int))); } public virtual SqlExpression TranslateLongCount([CanBeNull] Expression expression = null) @@ -127,7 +141,12 @@ public virtual SqlExpression TranslateLongCount([CanBeNull] Expression expressio } return SqlExpressionFactory.ApplyDefaultTypeMapping( - SqlExpressionFactory.Function("COUNT", new[] { SqlExpressionFactory.Fragment("*") }, typeof(long))); + SqlExpressionFactory.Function( + "COUNT", + new[] { SqlExpressionFactory.Fragment("*") }, + nullResultAllowed: false, + argumentsPropagateNullability: new[] { false }, + typeof(long))); } public virtual SqlExpression TranslateMax([NotNull] Expression expression) @@ -140,7 +159,13 @@ public virtual SqlExpression TranslateMax([NotNull] Expression expression) } return sqlExpression != null - ? SqlExpressionFactory.Function("MAX", new[] { sqlExpression }, sqlExpression.Type, sqlExpression.TypeMapping) + ? SqlExpressionFactory.Function( + "MAX", + new[] { sqlExpression }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false }, + sqlExpression.Type, + sqlExpression.TypeMapping) : null; } @@ -154,7 +179,13 @@ public virtual SqlExpression TranslateMin([NotNull] Expression expression) } return sqlExpression != null - ? SqlExpressionFactory.Function("MIN", new[] { sqlExpression }, sqlExpression.Type, sqlExpression.TypeMapping) + ? SqlExpressionFactory.Function( + "MIN", + new[] { sqlExpression }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false }, + sqlExpression.Type, + sqlExpression.TypeMapping) : null; } @@ -176,11 +207,21 @@ public virtual SqlExpression TranslateSum([NotNull] Expression expression) return inputType == typeof(float) ? SqlExpressionFactory.Convert( - SqlExpressionFactory.Function("SUM", new[] { sqlExpression }, typeof(double)), + SqlExpressionFactory.Function( + "SUM", + new[] { sqlExpression }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false }, + typeof(double)), inputType, sqlExpression.TypeMapping) : (SqlExpression)SqlExpressionFactory.Function( - "SUM", new[] { sqlExpression }, inputType, sqlExpression.TypeMapping); + "SUM", + new[] { sqlExpression }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false }, + inputType, + sqlExpression.TypeMapping); } private sealed class SqlTypeMappingVerifyingExpressionVisitor : ExpressionVisitor diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 9703701c969..bc7827ad86f 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -376,6 +376,9 @@ public virtual SqlFunctionExpression Coalesce(SqlExpression left, SqlExpression return SqlFunctionExpression.Create( "COALESCE", typeMappedArguments, + nullResultAllowed: true, + // COALESCE is handled separately since it's only nullable if *both* arguments are null + argumentsPropagateNullability: new[] { false, false }, resultType, inferredTypeMapping); } @@ -485,7 +488,52 @@ public virtual CaseExpression Case(IReadOnlyList whenClauses, Sq } public virtual SqlFunctionExpression Function( - string name, IEnumerable arguments, Type returnType, RelationalTypeMapping typeMapping = null) + string name, + IEnumerable arguments, + Type returnType, + RelationalTypeMapping typeMapping = null) + => Function(name, arguments, nullResultAllowed: true, argumentsPropagateNullability: arguments.Select(a => false), returnType, typeMapping); + + public virtual SqlFunctionExpression Function( + string schema, + string name, + IEnumerable arguments, + Type returnType, + RelationalTypeMapping typeMapping = null) + => Function(schema, name, arguments, nullResultAllowed: true, argumentsPropagateNullability: arguments.Select(a => false), returnType, typeMapping); + + public virtual SqlFunctionExpression Function( + SqlExpression instance, + string name, + IEnumerable arguments, + Type returnType, + RelationalTypeMapping typeMapping = null) + => Function( + instance, + name, + arguments, + nullResultAllowed: true, + instancePropagatesNullability: false, + argumentsPropagateNullability: arguments.Select(a => false), + returnType, + typeMapping); + + public virtual SqlFunctionExpression Function(string name, Type returnType, RelationalTypeMapping typeMapping = null) + => Function(name, nullResultAllowed: true, returnType, typeMapping); + + public virtual SqlFunctionExpression Function(string schema, string name, Type returnType, RelationalTypeMapping typeMapping = null) + => Function(schema, name, nullResultAllowed: true, returnType, typeMapping); + + public virtual SqlFunctionExpression Function(SqlExpression instance, string name, Type returnType, RelationalTypeMapping typeMapping = null) + => Function(instance, name, nullResultAllowed: true, instancePropagatesNullability: false, returnType, typeMapping); + + public virtual SqlFunctionExpression Function( + string name, + IEnumerable arguments, + bool nullResultAllowed, + IEnumerable argumentsPropagateNullability, + Type returnType, + RelationalTypeMapping typeMapping = null) { Check.NotEmpty(name, nameof(name)); Check.NotNull(arguments, nameof(arguments)); @@ -501,12 +549,20 @@ public virtual SqlFunctionExpression Function( return SqlFunctionExpression.Create( name, typeMappedArguments, + nullResultAllowed, + argumentsPropagateNullability, returnType, typeMapping); } public virtual SqlFunctionExpression Function( - string schema, string name, IEnumerable arguments, Type returnType, RelationalTypeMapping typeMapping = null) + string schema, + string name, + IEnumerable arguments, + bool nullResultAllowed, + IEnumerable argumentsPropagateNullability, + Type returnType, + RelationalTypeMapping typeMapping = null) { Check.NotEmpty(name, nameof(name)); Check.NotNull(arguments, nameof(arguments)); @@ -522,12 +578,20 @@ public virtual SqlFunctionExpression Function( schema, name, typeMappedArguments, + nullResultAllowed, + argumentsPropagateNullability, returnType, typeMapping); } public virtual SqlFunctionExpression Function( - SqlExpression instance, string name, IEnumerable arguments, Type returnType, + SqlExpression instance, + string name, + IEnumerable arguments, + bool nullResultAllowed, + bool instancePropagatesNullability, + IEnumerable argumentsPropagateNullability, + Type returnType, RelationalTypeMapping typeMapping = null) { Check.NotEmpty(name, nameof(name)); @@ -545,34 +609,48 @@ public virtual SqlFunctionExpression Function( instance, name, typeMappedArguments, + nullResultAllowed, + instancePropagatesNullability, + argumentsPropagateNullability, returnType, typeMapping); } - public virtual SqlFunctionExpression Function(string name, Type returnType, RelationalTypeMapping typeMapping = null) + public virtual SqlFunctionExpression Function(string name, bool nullResultAllowed, Type returnType, RelationalTypeMapping typeMapping = null) { Check.NotEmpty(name, nameof(name)); Check.NotNull(returnType, nameof(returnType)); - return SqlFunctionExpression.CreateNiladic(name, returnType, typeMapping); + return SqlFunctionExpression.CreateNiladic(name, nullResultAllowed, returnType, typeMapping); } - public virtual SqlFunctionExpression Function(string schema, string name, Type returnType, RelationalTypeMapping typeMapping = null) + public virtual SqlFunctionExpression Function(string schema, string name, bool nullResultAllowed, Type returnType, RelationalTypeMapping typeMapping = null) { Check.NotEmpty(schema, nameof(schema)); Check.NotEmpty(name, nameof(name)); Check.NotNull(returnType, nameof(returnType)); - return SqlFunctionExpression.CreateNiladic(schema, name, returnType, typeMapping); + return SqlFunctionExpression.CreateNiladic(schema, name, nullResultAllowed, returnType, typeMapping); } public virtual SqlFunctionExpression Function( - SqlExpression instance, string name, Type returnType, RelationalTypeMapping typeMapping = null) + SqlExpression instance, + string name, + bool nullResultAllowed, + bool instancePropagatesNullability, + Type returnType, + RelationalTypeMapping typeMapping = null) { Check.NotEmpty(name, nameof(name)); Check.NotNull(returnType, nameof(returnType)); - return SqlFunctionExpression.CreateNiladic(ApplyDefaultTypeMapping(instance), name, returnType, typeMapping); + return SqlFunctionExpression.CreateNiladic( + ApplyDefaultTypeMapping(instance), + name, + nullResultAllowed, + instancePropagatesNullability, + returnType, + typeMapping); } public virtual ExistsExpression Exists(SelectExpression subquery, bool negated) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs index e77848a02a9..a3467749330 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SqlFunctionExpression.cs @@ -17,17 +17,80 @@ public static SqlFunctionExpression CreateNiladic( [NotNull] string name, [NotNull] Type type, [CanBeNull] RelationalTypeMapping typeMapping) + => CreateNiladic(name, nullResultAllowed: true, type, typeMapping); + + public static SqlFunctionExpression CreateNiladic( + [NotNull] string schema, + [NotNull] string name, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + => CreateNiladic(schema, name, nullResultAllowed: true, type, typeMapping); + + public static SqlFunctionExpression CreateNiladic( + [NotNull] SqlExpression instance, + [NotNull] string name, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + => CreateNiladic(instance, name, nullResultAllowed: true, instancPropagatesNullability: false, type, typeMapping); + + public static SqlFunctionExpression Create( + [NotNull] SqlExpression instance, + [NotNull] string name, + [NotNull] IEnumerable arguments, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + => Create( + instance, + name, + arguments, + nullResultAllowed: true, + instancPropagatesNullability: false, + argumentsPropagateNullability: arguments.Select(a => false), + type, + typeMapping); + + public static SqlFunctionExpression Create( + [NotNull] string name, + [NotNull] IEnumerable arguments, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + => Create(name, arguments, nullResultAllowed: true, argumentsPropagateNullability: arguments.Select(a => false), type, typeMapping); + + public static SqlFunctionExpression Create( + [CanBeNull] string schema, + [NotNull] string name, + [NotNull] IEnumerable arguments, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) + => Create(schema, name, arguments, nullResultAllowed: true, argumentsPropagateNullability: arguments.Select(a => false), type, typeMapping); + + public static SqlFunctionExpression CreateNiladic( + [NotNull] string name, + bool nullResultAllowed, + [NotNull] Type type, + [CanBeNull] RelationalTypeMapping typeMapping) { Check.NotEmpty(name, nameof(name)); Check.NotNull(type, nameof(type)); return new SqlFunctionExpression( - instance: null, schema: null, name, niladic: true, arguments: null, builtIn: true, type, typeMapping); + instance: null, + schema: null, + name, + niladic: true, + arguments: null, + nullResultAllowed, + instancPropagatesNullability: null, + argumentsPropagateNullability: null, + builtIn: true, + type, + typeMapping); } public static SqlFunctionExpression CreateNiladic( [NotNull] string schema, [NotNull] string name, + bool nullResultAllowed, [NotNull] Type type, [CanBeNull] RelationalTypeMapping typeMapping) { @@ -36,12 +99,24 @@ public static SqlFunctionExpression CreateNiladic( Check.NotNull(type, nameof(type)); return new SqlFunctionExpression( - instance: null, schema, name, niladic: true, arguments: null, builtIn: true, type, typeMapping); + instance: null, + schema, + name, + niladic: true, + arguments: null, + nullResultAllowed, + instancPropagatesNullability: null, + argumentsPropagateNullability: null, + builtIn: true, + type, + typeMapping); } public static SqlFunctionExpression CreateNiladic( [NotNull] SqlExpression instance, [NotNull] string name, + bool nullResultAllowed, + bool instancPropagatesNullability, [NotNull] Type type, [CanBeNull] RelationalTypeMapping typeMapping) { @@ -50,27 +125,54 @@ public static SqlFunctionExpression CreateNiladic( Check.NotNull(type, nameof(type)); return new SqlFunctionExpression( - instance, schema: null, name, niladic: true, arguments: null, builtIn: true, type, typeMapping); + instance, + schema: null, + name, + niladic: true, + arguments: null, + nullResultAllowed, + instancPropagatesNullability, + argumentsPropagateNullability: null, + builtIn: true, + type, + typeMapping); } public static SqlFunctionExpression Create( [NotNull] SqlExpression instance, [NotNull] string name, [NotNull] IEnumerable arguments, + bool nullResultAllowed, + bool instancPropagatesNullability, + [NotNull] IEnumerable argumentsPropagateNullability, [NotNull] Type type, [CanBeNull] RelationalTypeMapping typeMapping) { Check.NotNull(instance, nameof(instance)); Check.NotEmpty(name, nameof(name)); Check.NotNull(arguments, nameof(arguments)); + Check.NotNull(argumentsPropagateNullability, nameof(argumentsPropagateNullability)); Check.NotNull(type, nameof(type)); - return new SqlFunctionExpression(instance, schema: null, name, niladic: false, arguments, builtIn: true, type, typeMapping); + return new SqlFunctionExpression( + instance, + schema: null, + name, + niladic: false, + arguments, + nullResultAllowed, + instancPropagatesNullability, + argumentsPropagateNullability, + builtIn: true, + type, + typeMapping); } public static SqlFunctionExpression Create( [NotNull] string name, [NotNull] IEnumerable arguments, + bool nullResultAllowed, + [NotNull] IEnumerable argumentsPropagateNullability, [NotNull] Type type, [CanBeNull] RelationalTypeMapping typeMapping) { @@ -79,13 +181,25 @@ public static SqlFunctionExpression Create( Check.NotNull(type, nameof(type)); return new SqlFunctionExpression( - instance: null, schema: null, name, niladic: false, arguments, builtIn: true, type, typeMapping); + instance: null, + schema: null, + name, + niladic: false, + arguments, + nullResultAllowed, + instancPropagatesNullability: null, + argumentsPropagateNullability, + builtIn: true, + type, + typeMapping); } public static SqlFunctionExpression Create( [CanBeNull] string schema, [NotNull] string name, [NotNull] IEnumerable arguments, + bool nullResultAllowed, + [NotNull] IEnumerable argumentsPropagateNullability, [NotNull] Type type, [CanBeNull] RelationalTypeMapping typeMapping) { @@ -93,7 +207,18 @@ public static SqlFunctionExpression Create( Check.NotNull(arguments, nameof(arguments)); Check.NotNull(type, nameof(type)); - return new SqlFunctionExpression(instance: null, schema, name, niladic: false, arguments, builtIn: false, type, typeMapping); + return new SqlFunctionExpression( + instance: null, + schema, + name, + niladic: false, + arguments, + nullResultAllowed, + instancPropagatesNullability: null, + argumentsPropagateNullability, + builtIn: false, + type, + typeMapping); } public SqlFunctionExpression( @@ -102,6 +227,9 @@ public SqlFunctionExpression( [NotNull] string name, bool niladic, [CanBeNull] IEnumerable arguments, + bool nullResultAllowed, + bool? instancPropagatesNullability, + [CanBeNull] IEnumerable argumentsPropagateNullability, bool builtIn, [NotNull] Type type, [CanBeNull] RelationalTypeMapping typeMapping) @@ -116,6 +244,9 @@ public SqlFunctionExpression( IsNiladic = niladic; IsBuiltIn = builtIn; Arguments = (arguments ?? Array.Empty()).ToList(); + NullResultAllowed = nullResultAllowed; + InstancPropagatesNullability = instancPropagatesNullability; + ArgumentsPropagateNullability = (argumentsPropagateNullability ?? Array.Empty()).ToList(); } public virtual string Name { get; } @@ -125,6 +256,30 @@ public SqlFunctionExpression( public virtual IReadOnlyList Arguments { get; } public virtual SqlExpression Instance { get; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool NullResultAllowed { get; private set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? InstancPropagatesNullability { get; private set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList ArgumentsPropagateNullability { get; private set; } + protected override Expression VisitChildren(ExpressionVisitor visitor) { Check.NotNull(visitor, nameof(visitor)); @@ -146,6 +301,9 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) Name, IsNiladic, arguments, + NullResultAllowed, + InstancPropagatesNullability, + ArgumentsPropagateNullability, IsBuiltIn, Type, TypeMapping) @@ -159,6 +317,9 @@ public virtual SqlFunctionExpression ApplyTypeMapping([CanBeNull] RelationalType Name, IsNiladic, Arguments, + NullResultAllowed, + InstancPropagatesNullability, + ArgumentsPropagateNullability, IsBuiltIn, Type, typeMapping ?? TypeMapping); @@ -166,7 +327,18 @@ public virtual SqlFunctionExpression ApplyTypeMapping([CanBeNull] RelationalType public virtual SqlFunctionExpression Update([CanBeNull] SqlExpression instance, [CanBeNull] IReadOnlyList arguments) { return instance != Instance || !arguments.SequenceEqual(Arguments) - ? new SqlFunctionExpression(instance, Schema, Name, IsNiladic, arguments, IsBuiltIn, Type, TypeMapping) + ? new SqlFunctionExpression( + instance, + Schema, + Name, + IsNiladic, + arguments, + NullResultAllowed, + InstancPropagatesNullability, + ArgumentsPropagateNullability, + IsBuiltIn, + Type, + TypeMapping) : this; } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryCollectionMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryCollectionMemberTranslator.cs index 86843c9cc17..158079a0aa4 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryCollectionMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryCollectionMemberTranslator.cs @@ -32,6 +32,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member instance, "STNumGeometries", Array.Empty(), + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: Array.Empty(), returnType); } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryCollectionMethodTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryCollectionMethodTranslator.cs index 60d1b3f4e2b..5b118c63f73 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryCollectionMethodTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryCollectionMethodTranslator.cs @@ -42,6 +42,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method arguments[0], _sqlExpressionFactory.Constant(1)) }, + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: new[] { false }, method.ReturnType, _typeMappingSource.FindMapping(typeof(Geometry), instance.TypeMapping.StoreType)); } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryMemberTranslator.cs index 8ec71fcfaa8..d6662692bce 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryMemberTranslator.cs @@ -73,6 +73,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member instance, functionName, Array.Empty(), + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: Array.Empty(), returnType, resultTypeMapping); } @@ -121,6 +124,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member instance, "STGeometryType", Array.Empty(), + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: Array.Empty(), typeof(string)), whenClauses.ToArray()); } @@ -130,6 +136,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member return _sqlExpressionFactory.Function( instance, "STSrid", + nullResultAllowed: true, + instancePropagatesNullability: true, returnType); } } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryMethodTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryMethodTranslator.cs index 9a9522e9934..6a41b8b34a9 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryMethodTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerGeometryMethodTranslator.cs @@ -97,10 +97,21 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method ? _typeMappingSource.FindMapping(method.ReturnType, storeType) : _typeMappingSource.FindMapping(method.ReturnType); + var finalArguments = Simplify(typeMappedArguments, isGeography); + + var argumentsPropagateNullability = functionName == "STBuffer" + ? new[] { false } + : functionName == "STRelate" + ? new[] { true, false } + : finalArguments.Select(a => true).ToArray(); + return _sqlExpressionFactory.Function( instance, functionName, - Simplify(typeMappedArguments, isGeography), + finalArguments, + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability, method.ReturnType, resultTypeMapping); } @@ -116,6 +127,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method arguments[0], _sqlExpressionFactory.Constant(1)) }, + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: new[] { false }, method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, storeType)); } @@ -136,11 +150,16 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method : _typeMappingSource.FindMapping(argument.Type))); } + var finalArguments = Simplify(new[] { typeMappedArguments[0] }, isGeography); + return _sqlExpressionFactory.LessThanOrEqual( _sqlExpressionFactory.Function( instance, "STDistance", - Simplify(new[] { typeMappedArguments[0] }, isGeography), + finalArguments, + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: finalArguments.Select(a => true), typeof(double)), typeMappedArguments[1]); } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerLineStringMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerLineStringMemberTranslator.cs index fe8d7d8cd97..abb93b3c660 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerLineStringMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerLineStringMemberTranslator.cs @@ -60,6 +60,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member instance, functionName, Enumerable.Empty(), + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: Enumerable.Empty(), returnType, resultTypeMapping); } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerLineStringMethodTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerLineStringMethodTranslator.cs index 1595a98cb0c..b9c9fe2b217 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerLineStringMethodTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerLineStringMethodTranslator.cs @@ -44,6 +44,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method arguments[0], _sqlExpressionFactory.Constant(1)) }, + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: new[] { true }, method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, instance.TypeMapping.StoreType)); } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerMultiLineStringMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerMultiLineStringMemberTranslator.cs index efdb59fb223..97e7c20afa9 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerMultiLineStringMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerMultiLineStringMemberTranslator.cs @@ -32,6 +32,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member instance, "STIsClosed", Array.Empty(), + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: Array.Empty(), returnType); } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPointMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPointMemberTranslator.cs index 339e459633e..b73d8fc2090 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPointMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPointMemberTranslator.cs @@ -51,6 +51,8 @@ public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type r return _sqlExpressionFactory.Function( instance, propertyName, + nullResultAllowed: true, + instancePropagatesNullability: true, returnType); } } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPolygonMemberTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPolygonMemberTranslator.cs index 04e2d547066..8c27bc524ac 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPolygonMemberTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPolygonMemberTranslator.cs @@ -53,6 +53,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member instance, "RingN", new[] { _sqlExpressionFactory.Constant(1) }, + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: new[] { false }, returnType, _typeMappingSource.FindMapping(returnType, storeType)); } @@ -64,6 +67,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member instance, "NumRings", Array.Empty(), + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: Array.Empty(), returnType), _sqlExpressionFactory.Constant(1)); } @@ -79,6 +85,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member instance, functionName, Array.Empty(), + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: Array.Empty(), returnType, resultTypeMapping); } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPolygonMethodTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPolygonMethodTranslator.cs index 7dcfd0409c3..9671022e62b 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPolygonMethodTranslator.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerPolygonMethodTranslator.cs @@ -50,6 +50,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method arguments[0], _sqlExpressionFactory.Constant(2)) }, + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: new[] { true }, method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, storeType)); } @@ -63,6 +66,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method arguments[0], _sqlExpressionFactory.Constant(1)) }, + nullResultAllowed: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: new[] { true }, method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, storeType)); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerByteArrayMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerByteArrayMethodTranslator.cs index 32131d867ee..9dcad381237 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerByteArrayMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerByteArrayMethodTranslator.cs @@ -36,7 +36,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method : _sqlExpressionFactory.Convert(arguments[1], typeof(byte[]), sourceTypeMapping); return _sqlExpressionFactory.GreaterThan( - _sqlExpressionFactory.Function("CHARINDEX", new[] { value, source }, typeof(int)), + _sqlExpressionFactory.Function( + "CHARINDEX", + new[] { value, source }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, + typeof(int)), _sqlExpressionFactory.Constant(0)); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerConvertTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerConvertTranslator.cs index 0097d6431ed..12401dd0810 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerConvertTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerConvertTranslator.cs @@ -62,6 +62,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method ? _sqlExpressionFactory.Function( "CONVERT", new[] { _sqlExpressionFactory.Fragment(_typeMapping[method.Name]), arguments[0] }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false, true }, method.ReturnType) : null; } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerDateDiffFunctionsTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerDateDiffFunctionsTranslator.cs index 2109771d9ff..8058b63ec11 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerDateDiffFunctionsTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerDateDiffFunctionsTranslator.cs @@ -355,6 +355,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "DATEDIFF", new[] { _sqlExpressionFactory.Fragment(datePart), startDate, endDate }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false, true, true }, typeof(int)); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMemberTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMemberTranslator.cs index 6aa4c0e51a0..7b29371c9db 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMemberTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMemberTranslator.cs @@ -50,6 +50,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member return _sqlExpressionFactory.Function( "DATEPART", new[] { _sqlExpressionFactory.Fragment(datePart), instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false, true }, returnType); } @@ -59,6 +61,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member return _sqlExpressionFactory.Function( "CONVERT", new[] { _sqlExpressionFactory.Fragment("date"), instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false, true }, returnType, declaringType == typeof(DateTime) ? instance.TypeMapping @@ -71,12 +75,16 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member return _sqlExpressionFactory.Function( declaringType == typeof(DateTime) ? "GETDATE" : "SYSDATETIMEOFFSET", Array.Empty(), + nullResultAllowed: false, + argumentsPropagateNullability: Array.Empty(), returnType); case nameof(DateTime.UtcNow): var serverTranslation = _sqlExpressionFactory.Function( declaringType == typeof(DateTime) ? "GETUTCDATE" : "SYSUTCDATETIME", Array.Empty(), + nullResultAllowed: false, + argumentsPropagateNullability: Array.Empty(), returnType); return declaringType == typeof(DateTime) @@ -92,8 +100,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member _sqlExpressionFactory.Function( "GETDATE", Array.Empty(), + nullResultAllowed: false, + argumentsPropagateNullability: Array.Empty(), typeof(DateTime)) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false, true }, returnType); } } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs index a4df834f582..1a4ecfca54c 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerDateTimeMethodTranslator.cs @@ -61,6 +61,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Convert(arguments[0], typeof(int)), instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false, true, true }, instance.Type, instance.TypeMapping); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerFromPartsFunctionTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerFromPartsFunctionTranslator.cs index ad7c24bea29..449aa3048a0 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerFromPartsFunctionTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerFromPartsFunctionTranslator.cs @@ -50,6 +50,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "DATEFROMPARTS", arguments.Skip(1), + nullResultAllowed: true, + argumentsPropagateNullability: arguments.Skip(1).Select(a => true), _dateFromPartsMethodInfo.ReturnType, _typeMappingSource.FindMapping(typeof(DateTime), "date")); } @@ -59,6 +61,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "DATETIMEFROMPARTS", arguments.Skip(1), + nullResultAllowed: true, + argumentsPropagateNullability: arguments.Skip(1).Select(a => true), _dateTimeFromPartsMethodInfo.ReturnType, _typeMappingSource.FindMapping(typeof(DateTime), "datetime")); } @@ -68,6 +72,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "DATETIME2FROMPARTS", arguments.Skip(1), + nullResultAllowed: true, + argumentsPropagateNullability: arguments.Skip(1).Select(a => true), _dateTime2FromPartsMethodInfo.ReturnType, _typeMappingSource.FindMapping(typeof(DateTime), "datetime2")); } @@ -77,6 +83,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "DATETIMEOFFSETFROMPARTS", arguments.Skip(1), + nullResultAllowed: true, + argumentsPropagateNullability: arguments.Skip(1).Select(a => true), _dateTimeOffsetFromPartsMethodInfo.ReturnType, _typeMappingSource.FindMapping(typeof(DateTimeOffset), "datetimeoffset")); } @@ -86,6 +94,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "SMALLDATETIMEFROMPARTS", arguments.Skip(1), + nullResultAllowed: true, + argumentsPropagateNullability: arguments.Skip(1).Select(a => true), _smallDateTimeFromPartsMethodInfo.ReturnType, _typeMappingSource.FindMapping(typeof(DateTime), "smalldatetime")); } @@ -95,6 +105,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "TIMEFROMPARTS", arguments.Skip(1), + nullResultAllowed: true, + argumentsPropagateNullability: arguments.Skip(1).Select(a => true), _timeFromPartsMethodInfo.ReturnType, _typeMappingSource.FindMapping(typeof(TimeSpan), "time")); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerFullTextSearchFunctionsTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerFullTextSearchFunctionsTranslator.cs index ba01e1ae470..fdf16dff182 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerFullTextSearchFunctionsTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerFullTextSearchFunctionsTranslator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; @@ -80,6 +81,9 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( functionName, functionArguments, + nullResultAllowed: true, + // TODO: don't propagate for now + argumentsPropagateNullability: functionArguments.Select(a => false).ToList(), typeof(bool)); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerIsDateFunctionTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerIsDateFunctionTranslator.cs index 0059fd0d141..1187aaaabe1 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerIsDateFunctionTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerIsDateFunctionTranslator.cs @@ -30,6 +30,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Function( "ISDATE", new[] { arguments[1] }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, _methodInfo.ReturnType), _methodInfo.ReturnType) : null; diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerMathTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerMathTranslator.cs index aa9c76367f2..cf3f4a20a33 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerMathTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerMathTranslator.cs @@ -92,6 +92,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( sqlFunctionName, newArguments, + nullResultAllowed: true, + argumentsPropagateNullability: newArguments.Select(a => true).ToArray(), method.ReturnType, sqlFunctionName == "SIGN" ? null : typeMapping); } @@ -103,6 +105,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "ROUND", new[] { argument, _sqlExpressionFactory.Constant(0), _sqlExpressionFactory.Constant(1) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, false, false }, method.ReturnType, argument.TypeMapping); } @@ -115,6 +119,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "ROUND", new[] { argument, digits }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, method.ReturnType, argument.TypeMapping); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerNewGuidTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerNewGuidTranslator.cs index 2c823c7c5b5..19c772bd348 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerNewGuidTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerNewGuidTranslator.cs @@ -30,6 +30,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method ? _sqlExpressionFactory.Function( "NEWID", Array.Empty(), + nullResultAllowed: false, + argumentsPropagateNullability: Array.Empty(), method.ReturnType) : null; } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs index 3f686195549..2750d4e9bee 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs @@ -58,6 +58,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method ? _sqlExpressionFactory.Function( "CONVERT", new[] { _sqlExpressionFactory.Fragment(storeType), instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new bool[] { false, true }, typeof(string)) : null; } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index e6b695f315b..5b0e62d8cc0 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -67,6 +67,8 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction schema: "dbo", sqlFunctionExpression.Name, sqlFunctionExpression.Arguments, + sqlFunctionExpression.NullResultAllowed, + sqlFunctionExpression.ArgumentsPropagateNullability, sqlFunctionExpression.Type, sqlFunctionExpression.TypeMapping); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs index c1d98795e58..3ff5754462a 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs @@ -74,7 +74,11 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) var isBinaryMaxDataType = GetProviderType(sqlExpression) == "varbinary(max)" || sqlExpression is SqlParameterExpression; var dataLengthSqlFunction = SqlExpressionFactory.Function( - "DATALENGTH", new[] { sqlExpression }, isBinaryMaxDataType ? typeof(long) : typeof(int)); + "DATALENGTH", + new[] { sqlExpression }, + nullResultAllowed: true, + argumentsPropagateNullability: new bool[] { true }, + isBinaryMaxDataType ? typeof(long) : typeof(int)); return isBinaryMaxDataType ? (Expression)SqlExpressionFactory.Convert(dataLengthSqlFunction, typeof(int)) @@ -93,7 +97,12 @@ public override SqlExpression TranslateLongCount(Expression expression = null) } return SqlExpressionFactory.ApplyDefaultTypeMapping( - SqlExpressionFactory.Function("COUNT_BIG", new[] { SqlExpressionFactory.Fragment("*") }, typeof(long))); + SqlExpressionFactory.Function( + "COUNT_BIG", + new[] { SqlExpressionFactory.Fragment("*") }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { false }, + typeof(long))); } private static string GetProviderType(SqlExpression expression) diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerStringMemberTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerStringMemberTranslator.cs index 853c78e2c79..338fa565bd8 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerStringMemberTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerStringMemberTranslator.cs @@ -28,7 +28,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member && instance?.Type == typeof(string)) { return _sqlExpressionFactory.Convert( - _sqlExpressionFactory.Function("LEN", new[] { instance }, typeof(long)), + _sqlExpressionFactory.Function( + "LEN", + new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + typeof(long)), returnType); } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs index 584fe0e92c9..33c4da6d355 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerStringMethodTranslator.cs @@ -90,6 +90,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method charIndexExpression = _sqlExpressionFactory.Function( "CHARINDEX", new[] { argument, _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping) }, + nullResultAllowed: true, + argumentsPropagateNullability: new [] { true, true }, typeof(long)); charIndexExpression = _sqlExpressionFactory.Convert(charIndexExpression, typeof(int)); @@ -99,6 +101,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method charIndexExpression = _sqlExpressionFactory.Function( "CHARINDEX", new[] { argument, _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, method.ReturnType); } @@ -129,6 +133,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "REPLACE", new[] { instance, firstArgument, secondArgument }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true, true }, method.ReturnType, stringTypeMapping); } @@ -139,6 +145,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( _toLowerMethodInfo.Equals(method) ? "LOWER" : "UPPER", new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, method.ReturnType, instance.TypeMapping); } @@ -155,6 +163,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Constant(1)), arguments[1] }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true, true }, method.ReturnType, instance.TypeMapping); } @@ -173,9 +183,13 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Function( "RTRIM", new[] { argument }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, argument.Type, argument.TypeMapping) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, argument.Type, argument.TypeMapping), _sqlExpressionFactory.Constant(string.Empty, argument.TypeMapping))); @@ -189,6 +203,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "LTRIM", new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, instance.Type, instance.TypeMapping); } @@ -201,6 +217,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "RTRIM", new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, instance.Type, instance.TypeMapping); } @@ -217,9 +235,13 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Function( "RTRIM", new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, instance.Type, instance.TypeMapping) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, instance.Type, instance.TypeMapping); } @@ -245,6 +267,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Function( "CHARINDEX", new[] { pattern, instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, typeof(int)), _sqlExpressionFactory.Constant(0)); } @@ -257,6 +281,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Function( "CHARINDEX", new[] { pattern, instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, typeof(int)), _sqlExpressionFactory.Constant(0))); } @@ -312,7 +338,18 @@ private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpress return _sqlExpressionFactory.Equal( _sqlExpressionFactory.Function( "LEFT", - new[] { instance, _sqlExpressionFactory.Function("LEN", new[] { pattern }, typeof(int)) }, + new[] + { + instance, + _sqlExpressionFactory.Function( + "LEN", + new[] { pattern }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + typeof(int)) + }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, typeof(string), stringTypeMapping), pattern); @@ -321,7 +358,18 @@ private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpress return _sqlExpressionFactory.Equal( _sqlExpressionFactory.Function( "RIGHT", - new[] { instance, _sqlExpressionFactory.Function("LEN", new[] { pattern }, typeof(int)) }, + new[] + { + instance, + _sqlExpressionFactory.Function( + "LEN", + new[] { pattern }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + typeof(int)) + }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, typeof(string), stringTypeMapping), pattern); diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteByteArrayMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteByteArrayMethodTranslator.cs index 74d856083ac..d5965da3161 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteByteArrayMethodTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteByteArrayMethodTranslator.cs @@ -28,10 +28,20 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method var value = arguments[1] is SqlConstantExpression constantValue ? (SqlExpression)_sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value }, source.TypeMapping) - : _sqlExpressionFactory.Function("char", new[] { arguments[1] }, typeof(string)); + : _sqlExpressionFactory.Function( + "char", + new[] { arguments[1] }, + nullResultAllowed: false, + argumentsPropagateNullability: new[] { false }, + typeof(string)); return _sqlExpressionFactory.GreaterThan( - _sqlExpressionFactory.Function("instr", new[] { source, value }, typeof(int)), + _sqlExpressionFactory.Function( + "instr", + new[] { source, value }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, + typeof(int)), _sqlExpressionFactory.Constant(0)); } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeAddTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeAddTranslator.cs index d00454aa978..6051cf62e39 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeAddTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeAddTranslator.cs @@ -87,9 +87,13 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method new[] { modifier }), _sqlExpressionFactory.Constant("0") }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, false }, method.ReturnType), _sqlExpressionFactory.Constant(".") }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, false }, method.ReturnType); } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeMemberTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeMemberTranslator.cs index 79024111325..42a8b6dca15 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeMemberTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteDateTimeMemberTranslator.cs @@ -61,6 +61,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member _sqlExpressionFactory.Function( "julianday", new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, typeof(double)), _sqlExpressionFactory.Constant(1721425.5)), // NB: Result of julianday('0001-01-01 00:00:00') _sqlExpressionFactory.Constant(TimeSpan.TicksPerDay)), @@ -135,9 +137,13 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member modifiers), _sqlExpressionFactory.Constant("0") }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, false }, returnType), _sqlExpressionFactory.Constant(".") }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, false }, returnType); } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteExpression.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteExpression.cs index 2b974227cfb..d4c9735e101 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteExpression.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteExpression.cs @@ -53,9 +53,13 @@ public static SqlFunctionExpression Strftime( modifiers = strftimeFunction.Arguments.Skip(2).Concat(modifiers); } + var finalArguments = new[] { sqlExpressionFactory.Constant(format), timestring }.Concat(modifiers); + return sqlExpressionFactory.Function( "strftime", - new[] { sqlExpressionFactory.Constant(format), timestring }.Concat(modifiers), + finalArguments, + nullResultAllowed: true, + argumentsPropagateNullability: finalArguments.Select(a => true), returnType, typeMapping); } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMathTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMathTranslator.cs index 11b40981c5a..a7162651881 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteMathTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteMathTranslator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query; @@ -75,9 +76,13 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method typeMapping = arguments[0].TypeMapping; } + var finalArguments = newArguments ?? arguments; + return _sqlExpressionFactory.Function( sqlFunctionName, - newArguments ?? arguments, + finalArguments, + nullResultAllowed: true, + argumentsPropagateNullability: finalArguments.Select(a => true).ToList(), method.ReturnType, typeMapping); } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs index 9363decca59..6397d74ec29 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteSqlTranslatingExpressionVisitor.cs @@ -91,7 +91,12 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) && unaryExpression.Operand.Type == typeof(byte[])) { return base.Visit(unaryExpression.Operand) is SqlExpression sqlExpression - ? SqlExpressionFactory.Function("length", new[] { sqlExpression }, typeof(int)) + ? SqlExpressionFactory.Function( + "length", + new[] { sqlExpression }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + typeof(int)) : null; } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringLengthTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringLengthTranslator.cs index 7619b6e8f87..cdb83178bcb 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringLengthTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringLengthTranslator.cs @@ -26,7 +26,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member return instance?.Type == typeof(string) && member.Name == nameof(string.Length) - ? _sqlExpressionFactory.Function("length", new[] { instance }, returnType) + ? _sqlExpressionFactory.Function( + "length", + new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + returnType) : null; } } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringMethodTranslator.cs index 3e9cca3c5df..da69e7357ff 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringMethodTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteStringMethodTranslator.cs @@ -97,6 +97,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(argument, stringTypeMapping) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, method.ReturnType), _sqlExpressionFactory.Constant(1)); } @@ -115,6 +117,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(secondArgument, stringTypeMapping) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true, true }, method.ReturnType, stringTypeMapping); } @@ -125,6 +129,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( _toLowerMethodInfo.Equals(method) ? "lower" : "upper", new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, method.ReturnType, instance.TypeMapping); } @@ -134,6 +140,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "substr", new[] { instance, _sqlExpressionFactory.Add(arguments[0], _sqlExpressionFactory.Constant(1)), arguments[1] }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true, true }, method.ReturnType, instance.TypeMapping); } @@ -146,7 +154,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.IsNull(argument), _sqlExpressionFactory.Equal( _sqlExpressionFactory.Function( - "trim", new[] { argument }, argument.Type, argument.TypeMapping), + "trim", + new[] { argument }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + argument.Type, + argument.TypeMapping), _sqlExpressionFactory.Constant(string.Empty))); } @@ -187,6 +200,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Function( "instr", new[] { instance, pattern }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, typeof(int)), _sqlExpressionFactory.Constant(0))); } @@ -258,8 +273,15 @@ private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpress { instance, _sqlExpressionFactory.Constant(1), - _sqlExpressionFactory.Function("length", new[] { pattern }, typeof(int)) + _sqlExpressionFactory.Function( + "length", + new[] { pattern }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + typeof(int)) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, false, true }, typeof(string), stringTypeMapping), pattern)), @@ -276,8 +298,15 @@ private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpress { instance, _sqlExpressionFactory.Negate( - _sqlExpressionFactory.Function("length", new[] { pattern }, typeof(int))) + _sqlExpressionFactory.Function( + "length", + new[] { pattern }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + typeof(int))) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, typeof(string), stringTypeMapping), pattern), @@ -343,6 +372,9 @@ private SqlExpression ProcessTrimMethod(SqlExpression instance, IReadOnlyList true).ToList(), typeof(string), typeMapping); } diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryCollectionMemberTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryCollectionMemberTranslator.cs index 3d41c8a98cc..2a127c34a08 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryCollectionMemberTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryCollectionMemberTranslator.cs @@ -27,7 +27,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member Check.NotNull(returnType, nameof(returnType)); return Equals(member, _count) - ? _sqlExpressionFactory.Function("NumGeometries", new[] { instance }, returnType) + ? _sqlExpressionFactory.Function( + "NumGeometries", + new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + returnType) : null; } } diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryCollectionMethodTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryCollectionMethodTranslator.cs index 82297091c13..9e861f73ade 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryCollectionMethodTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryCollectionMethodTranslator.cs @@ -37,6 +37,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method arguments[0], _sqlExpressionFactory.Constant(1)) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, method.ReturnType); } diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryMemberTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryMemberTranslator.cs index 5a6fe2d179f..d12049e0626 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryMemberTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryMemberTranslator.cs @@ -48,21 +48,26 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member if (_memberToFunctionName.TryGetValue(member, out var functionName)) { - SqlExpression translation = _sqlExpressionFactory.Function(functionName, new[] { instance }, returnType); - - if (returnType == typeof(bool)) - { - translation = _sqlExpressionFactory.Case( + return returnType == typeof(bool) + ? _sqlExpressionFactory.Case( new[] { new CaseWhenClause( _sqlExpressionFactory.IsNotNull(instance), - translation) + _sqlExpressionFactory.Function( + functionName, + new[] { instance }, + nullResultAllowed: false, + argumentsPropagateNullability: new[] { false }, + returnType)) }, - null); - } - - return translation; + null) + : (SqlExpression)_sqlExpressionFactory.Function( + functionName, + new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + returnType); } if (Equals(member, _geometryType)) @@ -75,9 +80,13 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member _sqlExpressionFactory.Function( "GeometryType", new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, returnType), _sqlExpressionFactory.Constant(" ZM") }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, returnType), new CaseWhenClause(_sqlExpressionFactory.Constant("POINT"), _sqlExpressionFactory.Constant("Point")), new CaseWhenClause(_sqlExpressionFactory.Constant("LINESTRING"), _sqlExpressionFactory.Constant("LineString")), @@ -100,9 +109,13 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member _sqlExpressionFactory.Function( "GeometryType", new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, typeof(string)), _sqlExpressionFactory.Constant(" ZM") }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, typeof(string)), new CaseWhenClause(_sqlExpressionFactory.Constant("POINT"), _sqlExpressionFactory.Constant(OgcGeometryType.Point)), new CaseWhenClause( diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryMethodTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryMethodTranslator.cs index b82f7f530b3..58e70520a99 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryMethodTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteGeometryMethodTranslator.cs @@ -64,19 +64,39 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method if (_methodToFunctionName.TryGetValue(method, out var functionName)) { - SqlExpression translation = _sqlExpressionFactory.Function( - functionName, - new[] { instance }.Concat(arguments), - method.ReturnType); + var finalArguments = new[] { instance }.Concat(arguments); if (method.ReturnType == typeof(bool)) { - translation = _sqlExpressionFactory.Case( - new[] { new CaseWhenClause(_sqlExpressionFactory.IsNotNull(instance), translation) }, - null); + var nullCheck = (SqlExpression)_sqlExpressionFactory.IsNotNull(instance); + foreach (var argument in arguments) + { + nullCheck = _sqlExpressionFactory.AndAlso( + nullCheck, + _sqlExpressionFactory.IsNotNull(argument)); + } + + return _sqlExpressionFactory.Case( + new[] + { + new CaseWhenClause( + nullCheck, + _sqlExpressionFactory.Function( + functionName, + finalArguments, + nullResultAllowed: false, + finalArguments.Select(a => false), + method.ReturnType)) + }, + null); } - return translation; + return _sqlExpressionFactory.Function( + functionName, + finalArguments, + nullResultAllowed: true, + finalArguments.Select(a => true), + method.ReturnType); } if (Equals(method, _getGeometryN)) @@ -90,6 +110,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method arguments[0], _sqlExpressionFactory.Constant(1)) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, method.ReturnType); } @@ -99,6 +121,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method _sqlExpressionFactory.Function( "Distance", new[] { instance, arguments[0] }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, typeof(double)), arguments[1]); } diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteLineStringMemberTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteLineStringMemberTranslator.cs index 401263fff13..f6fde8a67d8 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteLineStringMemberTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteLineStringMemberTranslator.cs @@ -38,22 +38,26 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member if (_memberToFunctionName.TryGetValue(member, out var functionName)) { - SqlExpression translation = _sqlExpressionFactory.Function( - functionName, new[] { instance }, returnType); - - if (returnType == typeof(bool)) - { - translation = _sqlExpressionFactory.Case( + return returnType == typeof(bool) + ? _sqlExpressionFactory.Case( new[] { new CaseWhenClause( _sqlExpressionFactory.IsNotNull(instance), - translation) + _sqlExpressionFactory.Function( + functionName, + new[] { instance }, + nullResultAllowed: false, + argumentsPropagateNullability: new[] { false }, + returnType)) }, - null); - } - - return translation; + null) + : (SqlExpression)_sqlExpressionFactory.Function( + functionName, + new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + returnType); } return null; diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteLineStringMethodTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteLineStringMethodTranslator.cs index 1da9272b8de..311139c4ee0 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteLineStringMethodTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteLineStringMethodTranslator.cs @@ -39,6 +39,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method arguments[0], _sqlExpressionFactory.Constant(1)) }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true, true }, method.ReturnType); } diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteMultiLineStringMemberTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteMultiLineStringMemberTranslator.cs index c24b3170763..69abd08c7f6 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteMultiLineStringMemberTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteMultiLineStringMemberTranslator.cs @@ -36,6 +36,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member _sqlExpressionFactory.Function( "IsClosed", new[] { instance }, + nullResultAllowed: false, + argumentsPropagateNullability: new[] { false }, returnType)) }, null); diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePointMemberTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePointMemberTranslator.cs index b7f4655ef8e..60db74be625 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePointMemberTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePointMemberTranslator.cs @@ -35,7 +35,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member Check.NotNull(returnType, nameof(returnType)); return _memberToFunctionName.TryGetValue(member, out var functionName) - ? _sqlExpressionFactory.Function(functionName, new[] { instance }, returnType) + ? _sqlExpressionFactory.Function( + functionName, + new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + returnType) : null; } } diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePolygonMemberTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePolygonMemberTranslator.cs index ac112d59118..4e8d976fc89 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePolygonMemberTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePolygonMemberTranslator.cs @@ -34,7 +34,12 @@ public virtual SqlExpression Translate(SqlExpression instance, MemberInfo member Check.NotNull(returnType, nameof(returnType)); return _memberToFunctionName.TryGetValue(member, out var functionName) - ? _sqlExpressionFactory.Function(functionName, new[] { instance }, returnType) + ? _sqlExpressionFactory.Function( + functionName, + new[] { instance }, + nullResultAllowed: true, + argumentsPropagateNullability: new[] { true }, + returnType) : null; } } diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePolygonMethodTranslator.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePolygonMethodTranslator.cs index 17c5dee6f39..78d14928f92 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePolygonMethodTranslator.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqlitePolygonMethodTranslator.cs @@ -33,6 +33,8 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method return _sqlExpressionFactory.Function( "InteriorRingN", new[] { instance, _sqlExpressionFactory.Add(arguments[0], _sqlExpressionFactory.Constant(1)) }, + nullResultAllowed: true, + argumentsPropagateNullability: new [] { true, true }, method.ReturnType); } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs index cbdbfeaba0d..c476cb2cc0c 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SpatialQueryInMemoryTest.cs @@ -1,6 +1,9 @@ // 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.Threading.Tasks; +using Xunit; + namespace Microsoft.EntityFrameworkCore.Query { public class SpatialQueryInMemoryTest : SpatialQueryTestBase @@ -9,5 +12,23 @@ public SpatialQueryInMemoryTest(SpatialQueryInMemoryFixture fixture) : base(fixture) { } + + [ConditionalTheory(Skip = "issue #19661")] + public override Task Distance_constant_lhs(bool async) + { + return base.Distance_constant_lhs(async); + } + + [ConditionalTheory(Skip = "issue #19664")] + public override Task Intersects_equal_to_null(bool async) + { + return base.Intersects_equal_to_null(async); + } + + [ConditionalTheory(Skip = "issue #19664")] + public override Task Intersects_not_equal_to_null(bool async) + { + return base.Intersects_not_equal_to_null(async); + } } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index 4e089347708..4b31c84c510 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -126,12 +126,24 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasTranslation(args => new SqlFragmentExpression("'Two'")); var isDateMethodInfo = typeof(UDFSqlContext).GetMethod(nameof(IsDateStatic)); modelBuilder.HasDbFunction(isDateMethodInfo) - .HasTranslation(args => SqlFunctionExpression.Create("IsDate", args, isDateMethodInfo.ReturnType, null)); + .HasTranslation(args => SqlFunctionExpression.Create( + "IsDate", + args, + nullResultAllowed: true, + argumentsPropagateNullability: args.Select(a => true).ToList(), + isDateMethodInfo.ReturnType, + null)); var methodInfo = typeof(UDFSqlContext).GetMethod(nameof(MyCustomLengthStatic)); modelBuilder.HasDbFunction(methodInfo) - .HasTranslation(args => SqlFunctionExpression.Create("len", args, methodInfo.ReturnType, null)); + .HasTranslation(args => SqlFunctionExpression.Create( + "len", + args, + nullResultAllowed: true, + argumentsPropagateNullability: args.Select(a => true).ToList(), + methodInfo.ReturnType, + null)); //Instance modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(CustomerOrderCountInstance))) @@ -146,14 +158,26 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasName("GetReportingPeriodStartDate"); var isDateMethodInfo2 = typeof(UDFSqlContext).GetMethod(nameof(IsDateInstance)); modelBuilder.HasDbFunction(isDateMethodInfo2) - .HasTranslation(args => SqlFunctionExpression.Create("IsDate", args, isDateMethodInfo2.ReturnType, null)); + .HasTranslation(args => SqlFunctionExpression.Create( + "IsDate", + args, + nullResultAllowed: true, + argumentsPropagateNullability: args.Select(a => true).ToList(), + isDateMethodInfo2.ReturnType, + null)); modelBuilder.HasDbFunction(typeof(UDFSqlContext).GetMethod(nameof(DollarValueInstance))).HasName("DollarValue"); var methodInfo2 = typeof(UDFSqlContext).GetMethod(nameof(MyCustomLengthInstance)); modelBuilder.HasDbFunction(methodInfo2) - .HasTranslation(args => SqlFunctionExpression.Create("len", args, methodInfo2.ReturnType, null)); + .HasTranslation(args => SqlFunctionExpression.Create( + "len", + args, + nullResultAllowed: true, + argumentsPropagateNullability: args.Select(a => true).ToList(), + methodInfo2.ReturnType, + null)); } } diff --git a/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs index 2aaa7f801b4..24f61eb17c5 100644 --- a/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs @@ -79,6 +79,22 @@ public virtual Task Area(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task AsBinary(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Select(e => new { e.Id, Binary = e.Point.AsBinary() }), + ss => ss.Set().Select(e => new { e.Id, Binary = e.Point == null ? null : e.Point.AsBinary() }), + elementSorter: x => x.Id, + elementAsserter: (e, a) => + { + Assert.Equal(e.Id, a.Id); + Assert.Equal(e.Binary, a.Binary); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task AsBinary_with_null_check(bool async) { return AssertQuery( async, @@ -97,6 +113,7 @@ public virtual Task AsText(bool async) { return AssertQuery( async, + ss => ss.Set().Select(e => new { e.Id, Text = e.Point.AsText() }), ss => ss.Set().Select(e => new { e.Id, Text = e.Point == null ? null : e.Point.AsText() }), elementSorter: x => x.Id, elementAsserter: (e, a) => @@ -127,6 +144,7 @@ public virtual Task Buffer(bool async) { return AssertQuery( async, + ss => ss.Set().Select(e => new { e.Id, Buffer = e.Polygon.Buffer(1.0) }), ss => ss.Set().Select(e => new { e.Id, Buffer = e.Polygon == null ? null : e.Polygon.Buffer(1.0) }), elementSorter: x => x.Id, elementAsserter: (e, a) => @@ -151,6 +169,7 @@ public virtual Task Buffer_quadrantSegments(bool async) { return AssertQuery( async, + ss => ss.Set().Select(e => new { e.Id, Buffer = e.Polygon.Buffer(1.0, 8) }), ss => ss.Set().Select(e => new { e.Id, Buffer = e.Polygon == null ? null : e.Polygon.Buffer(1.0, 8) }), elementSorter: x => x.Id, elementAsserter: (e, a) => @@ -192,8 +211,8 @@ public virtual Task Contains(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Contains = e.Polygon == null ? (bool?)null : e.Polygon.Contains(point) }), + ss => ss.Set().Select(e => new { e.Id, Contains = (bool?)e.Polygon.Contains(point) }), + ss => ss.Set().Select(e => new { e.Id, Contains = e.Polygon == null ? (bool?)null : e.Polygon.Contains(point) }), elementSorter: x => x.Id); } @@ -203,6 +222,7 @@ public virtual Task ConvexHull(bool async) { return AssertQuery( async, + ss => ss.Set().Select(e => new { e.Id, ConvexHull = e.Polygon.ConvexHull() }), ss => ss.Set().Select(e => new { e.Id, ConvexHull = e.Polygon == null ? null : e.Polygon.ConvexHull() }), elementSorter: x => x.Id, elementAsserter: (e, a) => @@ -246,9 +266,8 @@ public virtual Task CoveredBy(bool async) return AssertQuery( async, - ss => ss.Set() - .Select( - e => new { e.Id, CoveredBy = e.Point == null ? (bool?)null : e.Point.CoveredBy(polygon) }), + ss => ss.Set().Select(e => new { e.Id, CoveredBy = (bool?)e.Point.CoveredBy(polygon) }), + ss => ss.Set().Select(e => new { e.Id, CoveredBy = e.Point == null ? (bool?)null : e.Point.CoveredBy(polygon) }), elementSorter: x => x.Id); } @@ -260,6 +279,7 @@ public virtual Task Covers(bool async) return AssertQuery( async, + ss => ss.Set().Select(e => new { e.Id, Covers = (bool?)e.Polygon.Covers(point) }), ss => ss.Set().Select(e => new { e.Id, Covers = e.Polygon == null ? (bool?)null : e.Polygon.Covers(point) }), elementSorter: x => x.Id); } @@ -273,8 +293,8 @@ public virtual Task Crosses(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Crosses = e.LineString == null ? (bool?)null : e.LineString.Crosses(lineString) }), + ss => ss.Set().Select(e => new { e.Id, Crosses = (bool?)e.LineString.Crosses(lineString) }), + ss => ss.Set().Select(e => new { e.Id, Crosses = e.LineString == null ? (bool?)null : e.LineString.Crosses(lineString) }), elementSorter: x => x.Id); } @@ -287,8 +307,8 @@ public virtual Task Difference(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Difference = e.Polygon == null ? null : e.Polygon.Difference(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Difference = e.Polygon.Difference(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Difference = e.Polygon == null ? null : e.Polygon.Difference(polygon) }), elementSorter: x => x.Id, elementAsserter: (e, a) => { @@ -309,27 +329,100 @@ public virtual Task Dimension(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Disjoint(bool async) + public virtual Task Disjoint_with_cast_to_nullable(bool async) { var point = Fixture.GeometryFactory.CreatePoint(new Coordinate(1, 1)); return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Disjoint = e.Polygon == null ? (bool?)null : e.Polygon.Disjoint(point) }), + ss => ss.Set().Select(e => new { e.Id, Disjoint = (bool?)e.Polygon.Disjoint(point) }), + ss => ss.Set().Select(e => new { e.Id, Disjoint = e.Polygon == null ? (bool?)null : e.Polygon.Disjoint(point) }), elementSorter: x => x.Id); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Distance(bool async) + public virtual Task Disjoint_without_cast_to_nullable(bool async) + { + var point = Fixture.GeometryFactory.CreatePoint(new Coordinate(1, 1)); + + return AssertQuery( + async, + ss => ss.Set().Select(e => new { e.Id, Disjoint = e.Polygon.Disjoint(point) }), + ss => ss.Set().Select(e => new { e.Id, Disjoint = (e.Polygon == null ? false : e.Polygon.Disjoint(point)) }), + elementSorter: x => x.Id); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Disjoint_with_null_check(bool async) + { + var point = Fixture.GeometryFactory.CreatePoint(new Coordinate(1, 1)); + + return AssertQuery( + async, + ss => ss.Set().Select(e => new { e.Id, Disjoint = e.Polygon == null ? (bool?)null : e.Polygon.Disjoint(point) }), + elementSorter: x => x.Id); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Distance_without_null_check(bool async) { var point = Fixture.GeometryFactory.CreatePoint(new Coordinate(0, 1)); return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Distance = e.Point == null ? (double?)null : e.Point.Distance(point) }), + ss => ss.Set().Select(e => new { e.Id, Distance = e.Point.Distance(point) }), + ss => ss.Set().Select(e => new { e.Id, Distance = e.Point == null ? default : e.Point.Distance(point) }), + elementSorter: e => e.Id, + elementAsserter: (e, a) => + { + Assert.Equal(e.Id, a.Id); + + if (AssertDistances) + { + Assert.Equal(e.Distance, a.Distance); + } + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Distance_with_null_check(bool async) + { + var point = Fixture.GeometryFactory.CreatePoint(new Coordinate(0, 1)); + + return AssertQuery( + async, + ss => ss.Set().Select(e => new { e.Id, Distance = (double?)e.Point.Distance(point) }), + ss => ss.Set().Select(e => new { e.Id, Distance = (e.Point == null ? (double?)null : e.Point.Distance(point)) }), + elementSorter: e => e.Id, + elementAsserter: (e, a) => + { + Assert.Equal(e.Id, a.Id); + + if (e.Distance == null) + { + Assert.Null(a.Distance); + } + else if (AssertDistances) + { + Assert.Equal(e.Distance, a.Distance); + } + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Distance_with_cast_to_nullable(bool async) + { + var point = Fixture.GeometryFactory.CreatePoint(new Coordinate(0, 1)); + + return AssertQuery( + async, + ss => ss.Set().Select(e => new { e.Id, Distance = (double?)e.Point.Distance(point) }), + ss => ss.Set().Select(e => new { e.Id, Distance = e.Point == null ? (double?)null : e.Point.Distance(point) }), elementSorter: e => e.Id, elementAsserter: (e, a) => { @@ -346,6 +439,7 @@ public virtual Task Distance(bool async) }); } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Distance_geometry(bool async) @@ -354,8 +448,8 @@ public virtual Task Distance_geometry(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Distance = e.Geometry == null ? (double?)null : e.Geometry.Distance(point) }), + ss => ss.Set().Select(e => new { e.Id, Distance = (double?)e.Geometry.Distance(point) }), + ss => ss.Set().Select(e => new { e.Id, Distance = e.Geometry == null ? (double?)null : e.Geometry.Distance(point) }), elementSorter: e => e.Id, elementAsserter: (e, a) => { @@ -378,8 +472,8 @@ public virtual Task Distance_constant(bool async) { return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Distance = e.Point == null ? (double?)null : e.Point.Distance(new Point(0, 1)) }), + ss => ss.Set().Select(e => new { e.Id, Distance = (double?)e.Point.Distance(new Point(0, 1)) }), + ss => ss.Set().Select(e => new { e.Id, Distance = e.Point == null ? (double?)null : e.Point.Distance(new Point(0, 1)) }), elementSorter: e => e.Id, elementAsserter: (e, a) => { @@ -402,8 +496,8 @@ public virtual Task Distance_constant_srid_4326(bool async) { return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Distance = e.Point == null ? (double?)null : e.Point.Distance(new Point(1, 1) { SRID = 4326 }) }), + ss => ss.Set().Select(e => new { e.Id, Distance = (double?)e.Point.Distance(new Point(1, 1) { SRID = 4326 }) }), + ss => ss.Set().Select(e => new { e.Id, Distance = e.Point == null ? (double?)null : e.Point.Distance(new Point(1, 1) { SRID = 4326 }) }), elementSorter: e => e.Id, elementAsserter: (e, a) => { @@ -426,8 +520,8 @@ public virtual Task Distance_constant_lhs(bool async) { return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Distance = e.Point == null ? (double?)null : new Point(0, 1).Distance(e.Point) }), + ss => ss.Set().Select(e => new { e.Id, Distance = (double?)new Point(0, 1).Distance(e.Point) }), + ss => ss.Set().Select(e => new { e.Id, Distance = e.Point == null ? (double?)null : new Point(0, 1).Distance(e.Point) }), elementSorter: e => e.Id, elementAsserter: (e, a) => { @@ -543,9 +637,8 @@ public virtual Task EqualsTopologically(bool async) return AssertQuery( async, - ss => ss.Set() - .Select( - e => new { e.Id, EqualsTopologically = e.Point == null ? (bool?)null : e.Point.EqualsTopologically(point) }), + ss => ss.Set().Select(e => new { e.Id, EqualsTopologically = (bool?)e.Point.EqualsTopologically(point) }), + ss => ss.Set().Select(e => new { e.Id, EqualsTopologically = e.Point == null ? (bool?)null : e.Point.EqualsTopologically(point) }), elementSorter: x => x.Id); } @@ -576,8 +669,19 @@ public virtual Task GetGeometryN(bool async) { return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Geometry0 = e.MultiLineString == null ? null : e.MultiLineString.GetGeometryN(0) }), + ss => ss.Set().Select(e => new { e.Id, Geometry0 = e.MultiLineString.GetGeometryN(0) }), + ss => ss.Set().Select(e => new { e.Id, Geometry0 = e.MultiLineString == null ? null : e.MultiLineString.GetGeometryN(0) }), + elementSorter: x => x.Id); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetGeometryN_with_null_argument(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Select(e => new { e.Id, Geometry0 = e.MultiLineString.GetGeometryN(ss.Set().Where(ee => false).Max(ee => ee.Id)) }), + ss => ss.Set().Select(e => new { e.Id, Geometry0 = default(Geometry) }), elementSorter: x => x.Id); } @@ -587,6 +691,14 @@ public virtual Task GetInteriorRingN(bool async) { return AssertQuery( async, + ss => ss.Set().Select( + e => new + { + e.Id, + InteriorRing0 = e.Polygon.NumInteriorRings == 0 + ? null + : e.Polygon.GetInteriorRingN(0) + }), ss => ss.Set().Select( e => new { @@ -604,8 +716,8 @@ public virtual Task GetPointN(bool async) { return AssertQuery( async, - ss => ss.Set() - .Select(e => new { e.Id, Point0 = e.LineString == null ? null : e.LineString.GetPointN(0) }), + ss => ss.Set().Select(e => new { e.Id, Point0 = e.LineString.GetPointN(0) }), + ss => ss.Set().Select(e => new { e.Id, Point0 = e.LineString == null ? null : e.LineString.GetPointN(0) }), elementSorter: x => x.Id); } @@ -647,8 +759,8 @@ public virtual Task Intersection(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Intersection = e.Polygon == null ? null : e.Polygon.Intersection(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Intersection = e.Polygon.Intersection(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Intersection = e.Polygon == null ? null : e.Polygon.Intersection(polygon) }), elementSorter: x => x.Id, elementAsserter: (e, a) => { @@ -666,8 +778,8 @@ public virtual Task Intersects(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Intersects = e.LineString == null ? (bool?)null : e.LineString.Intersects(lineString) }), + ss => ss.Set().Select(e => new { e.Id, Intersects = (bool?)e.LineString.Intersects(lineString) }), + ss => ss.Set().Select(e => new { e.Id, Intersects = e.LineString == null ? (bool?)null : e.LineString.Intersects(lineString) }), elementSorter: x => x.Id); } @@ -746,8 +858,8 @@ public virtual Task IsWithinDistance(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, IsWithinDistance = e.Point == null ? (bool?)null : e.Point.IsWithinDistance(point, 1) }), + ss => ss.Set().Select(e => new { e.Id, IsWithinDistance = (bool?)e.Point.IsWithinDistance(point, 1) }), + ss => ss.Set().Select(e => new { e.Id, IsWithinDistance = e.Point == null ? (bool?)null : e.Point.IsWithinDistance(point, 1) }), elementSorter: e => e.Id, elementAsserter: (e, a) => { @@ -770,8 +882,8 @@ public virtual Task Item(bool async) { return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Item0 = e.MultiLineString == null ? null : e.MultiLineString[0] }), + ss => ss.Set().Select(e => new { e.Id, Item0 = e.MultiLineString[0] }), + ss => ss.Set().Select(e => new { e.Id, Item0 = e.MultiLineString == null ? null : e.MultiLineString[0] }), elementSorter: x => x.Id); } @@ -875,8 +987,8 @@ public virtual Task Overlaps(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Overlaps = e.Polygon == null ? (bool?)null : e.Polygon.Overlaps(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Overlaps = (bool?)e.Polygon.Overlaps(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Overlaps = e.Polygon == null ? (bool?)null : e.Polygon.Overlaps(polygon) }), elementSorter: x => x.Id); } @@ -918,8 +1030,8 @@ public virtual Task Relate(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Relate = e.Polygon == null ? (bool?)null : e.Polygon.Relate(polygon, "212111212") }), + ss => ss.Set().Select(e => new { e.Id, Relate = (bool?)e.Polygon.Relate(polygon, "212111212") }), + ss => ss.Set().Select(e => new { e.Id, Relate = e.Polygon == null ? (bool?)null : e.Polygon.Relate(polygon, "212111212") }), elementSorter: x => x.Id); } @@ -929,6 +1041,7 @@ public virtual Task Reverse(bool async) { return AssertQuery( async, + ss => ss.Set().Select(e => new { e.Id, Reverse = e.LineString.Reverse() }), ss => ss.Set().Select(e => new { e.Id, Reverse = e.LineString == null ? null : e.LineString.Reverse() }), elementSorter: x => x.Id); } @@ -975,8 +1088,8 @@ public virtual Task SymmetricDifference(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, SymmetricDifference = e.Polygon == null ? null : e.Polygon.SymmetricDifference(polygon) }), + ss => ss.Set().Select(e => new { e.Id, SymmetricDifference = e.Polygon.SymmetricDifference(polygon) }), + ss => ss.Set().Select(e => new { e.Id, SymmetricDifference = e.Polygon == null ? null : e.Polygon.SymmetricDifference(polygon) }), elementSorter: x => x.Id, elementAsserter: (e, a) => { @@ -991,6 +1104,7 @@ public virtual Task ToBinary(bool async) { return AssertQuery( async, + ss => ss.Set().Select(e => new { e.Id, Binary = e.Point.ToBinary() }), ss => ss.Set().Select(e => new { e.Id, Binary = e.Point == null ? null : e.Point.ToBinary() }), elementSorter: e => e.Id, elementAsserter: (e, a) => @@ -1006,6 +1120,7 @@ public virtual Task ToText(bool async) { return AssertQuery( async, + ss => ss.Set().Select(e => new { e.Id, Text = e.Point.ToText() }), ss => ss.Set().Select(e => new { e.Id, Text = e.Point == null ? null : e.Point.ToText() }), elementSorter: e => e.Id, elementAsserter: (e, a) => @@ -1024,8 +1139,8 @@ public virtual Task Touches(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Touches = e.Polygon == null ? (bool?)null : e.Polygon.Touches(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Touches = (bool?)e.Polygon.Touches(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Touches = e.Polygon == null ? (bool?)null : e.Polygon.Touches(polygon) }), elementSorter: x => x.Id); } @@ -1038,8 +1153,8 @@ public virtual Task Union(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Union = e.Polygon == null ? null : e.Polygon.Union(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Union = e.Polygon.Union(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Union = e.Polygon == null ? null : e.Polygon.Union(polygon) }), elementSorter: x => x.Id, elementAsserter: (e, a) => { @@ -1054,8 +1169,8 @@ public virtual Task Union_void(bool async) { return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Union = e.MultiLineString == null ? null : e.MultiLineString.Union() }), + ss => ss.Set().Select(e => new { e.Id, Union = e.MultiLineString.Union() }), + ss => ss.Set().Select(e => new { e.Id, Union = e.MultiLineString == null ? null : e.MultiLineString.Union() }), elementSorter: x => x.Id); } @@ -1071,8 +1186,8 @@ public virtual Task Within(bool async) return AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Within = e.Point == null ? (bool?)null : e.Point.Within(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Within = (bool?)e.Point.Within(polygon) }), + ss => ss.Set().Select(e => new { e.Id, Within = e.Point == null ? (bool?)null : e.Point.Within(polygon) }), elementSorter: x => x.Id); } @@ -1118,5 +1233,71 @@ public virtual Task Z(bool async) } }); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task IsEmpty_equal_to_null(bool async) + { + return AssertQueryScalar( + async, +#pragma warning disable CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => (bool?)e.Point.IsEmpty == null).Select(e => e.Id), +#pragma warning restore CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => e.Point == null).Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task IsEmpty_not_equal_to_null(bool async) + { + return AssertQueryScalar( + async, +#pragma warning disable CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => (bool?)e.Point.IsEmpty != null).Select(e => e.Id), +#pragma warning restore CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => e.Point != null).Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Intersects_equal_to_null(bool async) + { + var lineString = Fixture.GeometryFactory.CreateLineString(new[] { new Coordinate(0.5, -0.5), new Coordinate(0.5, 0.5) }); + + await AssertQueryScalar( + async, +#pragma warning disable CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => (bool?)e.LineString.Intersects(lineString) == null).Select(e => e.Id), +#pragma warning restore CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => e.LineString == null).Select(e => e.Id)); + + await AssertQueryScalar( + async, +#pragma warning disable CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => (bool?)lineString.Intersects(e.LineString) == null).Select(e => e.Id), +#pragma warning restore CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => e.LineString == null).Select(e => e.Id)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Intersects_not_equal_to_null(bool async) + { + var lineString = Fixture.GeometryFactory.CreateLineString(new[] { new Coordinate(0.5, -0.5), new Coordinate(0.5, 0.5) }); + + await AssertQueryScalar( + async, +#pragma warning disable CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => (bool?)e.LineString.Intersects(lineString) != null).Select(e => e.Id), +#pragma warning restore CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => e.LineString != null).Select(e => e.Id)); + + await AssertQueryScalar( + async, +#pragma warning disable CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => (bool?)lineString.Intersects(e.LineString) != null).Select(e => e.Id), +#pragma warning restore CS0472 // The result of the expression is always the same since a value of this type is never equal to 'null' + ss => ss.Set().Where(e => e.LineString != null).Select(e => e.Id)); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 852fc64e496..589f8cc15e1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -315,7 +315,7 @@ public override async Task Optional_navigation_inside_nested_method_call_transla @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] FROM [LevelOne] AS [l] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] -WHERE UPPER([l0].[Name]) IS NOT NULL AND (UPPER([l0].[Name]) LIKE N'L%')"); +WHERE [l0].[Name] IS NOT NULL AND (UPPER([l0].[Name]) LIKE N'L%')"); } public override async Task Method_call_on_optional_navigation_translates_to_null_conditional_properly_for_arguments(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs index 7cd2674ad6f..efdaafc3de3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs @@ -202,13 +202,13 @@ FROM [FunkyCustomers] AS [f] SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm6_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND ((LEFT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0) OR LEFT([f].[FirstName], LEN(@__prm6_0)) IS NULL))", +WHERE (@__prm6_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0))", // @"@__prm7_0='' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm7_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND ((LEFT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0) OR LEFT([f].[FirstName], LEN(@__prm7_0)) IS NULL))", +WHERE (@__prm7_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND (LEFT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0))", // @"SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] @@ -274,7 +274,7 @@ public override async Task String_starts_with_on_argument_with_wildcard_column_n @"SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND ((LEFT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName]) OR LEFT([f].[FirstName], LEN([f0].[LastName])) IS NULL)))"); +WHERE (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (LEFT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName])))"); } public override async Task String_ends_with_on_argument_with_wildcard_constant(bool async) @@ -351,13 +351,13 @@ FROM [FunkyCustomers] AS [f] SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm6_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND ((RIGHT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0) OR RIGHT([f].[FirstName], LEN(@__prm6_0)) IS NULL))", +WHERE (@__prm6_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm6_0)) <> @__prm6_0))", // @"@__prm7_0='' (Size = 4000) SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] -WHERE (@__prm7_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND ((RIGHT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0) OR RIGHT([f].[FirstName], LEN(@__prm7_0)) IS NULL))", +WHERE (@__prm7_0 <> N'') AND ([f].[FirstName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN(@__prm7_0)) <> @__prm7_0))", // @"SELECT [f].[FirstName] FROM [FunkyCustomers] AS [f] @@ -383,7 +383,7 @@ public override async Task String_ends_with_on_argument_with_wildcard_column_neg @"SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND ((RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName]) OR RIGHT([f].[FirstName], LEN([f0].[LastName])) IS NULL)))"); +WHERE (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName])))"); } public override async Task String_ends_with_inside_conditional(bool async) @@ -409,7 +409,7 @@ public override async Task String_ends_with_inside_conditional_negated(bool asyn FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE CASE - WHEN (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND ((RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName]) OR RIGHT([f].[FirstName], LEN([f0].[LastName])) IS NULL))) THEN CAST(1 AS bit) + WHEN (([f0].[LastName] <> N'') OR [f0].[LastName] IS NULL) AND ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) <> [f0].[LastName]))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = CAST(1 AS bit)"); } @@ -423,7 +423,7 @@ public override async Task String_ends_with_equals_nullable_column(bool async) FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE CASE - WHEN (([f0].[LastName] = N'') AND [f0].[LastName] IS NOT NULL) OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND ((RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]) AND RIGHT([f].[FirstName], LEN([f0].[LastName])) IS NOT NULL))) THEN CAST(1 AS bit) + WHEN (([f0].[LastName] = N'') AND [f0].[LastName] IS NOT NULL) OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END = [f].[NullableBool]"); } @@ -437,7 +437,7 @@ public override async Task String_ends_with_not_equals_nullable_column(bool asyn FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] WHERE (CASE - WHEN (([f0].[LastName] = N'') AND [f0].[LastName] IS NOT NULL) OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND ((RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]) AND RIGHT([f].[FirstName], LEN([f0].[LastName])) IS NOT NULL))) THEN CAST(1 AS bit) + WHEN (([f0].[LastName] = N'') AND [f0].[LastName] IS NOT NULL) OR ([f].[FirstName] IS NOT NULL AND ([f0].[LastName] IS NOT NULL AND (RIGHT([f].[FirstName], LEN([f0].[LastName])) = [f0].[LastName]))) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END <> [f].[NullableBool]) OR [f].[NullableBool] IS NULL"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index e155c525a58..9ff818d6977 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -959,7 +959,7 @@ public override async Task Select_null_propagation_negative1(bool async) AssertSql( @"SELECT CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN (CAST(LEN([g].[Nickname]) AS int) = 5) AND LEN([g].[Nickname]) IS NOT NULL THEN CAST(1 AS bit) + WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END ELSE NULL @@ -1053,7 +1053,7 @@ public override async Task Select_null_propagation_negative6(bool async) 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 LEN([g].[LeaderNickname]) IS NULL) AND LEN([g].[LeaderNickname]) IS NOT NULL THEN CAST(1 AS bit) + WHEN CAST(LEN([g].[LeaderNickname]) AS int) <> CAST(LEN([g].[LeaderNickname]) AS int) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END ELSE NULL @@ -1101,7 +1101,7 @@ public override async Task Select_null_propagation_negative9(bool async) AssertSql( @"SELECT CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN COALESCE(CASE - WHEN (CAST(LEN([g].[Nickname]) AS int) = 5) AND LEN([g].[Nickname]) IS NOT NULL THEN CAST(1 AS bit) + WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END, CAST(0 AS bit)) ELSE NULL @@ -2628,7 +2628,7 @@ public override async Task Where_datetimeoffset_now(bool async) AssertSql( @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] -WHERE ([m].[Timeline] <> SYSDATETIMEOFFSET()) OR SYSDATETIMEOFFSET() IS NULL"); +WHERE [m].[Timeline] <> SYSDATETIMEOFFSET()"); } public override async Task Where_datetimeoffset_utcnow(bool async) @@ -2638,7 +2638,7 @@ public override async Task Where_datetimeoffset_utcnow(bool async) AssertSql( @"SELECT [m].[Id], [m].[CodeName], [m].[Rating], [m].[Timeline] FROM [Missions] AS [m] -WHERE ([m].[Timeline] <> CAST(SYSUTCDATETIME() AS datetimeoffset)) OR SYSUTCDATETIME() IS NULL"); +WHERE [m].[Timeline] <> CAST(SYSUTCDATETIME() AS datetimeoffset)"); } public override async Task Where_datetimeoffset_date_component(bool async) @@ -6224,7 +6224,7 @@ public override async Task OrderBy_same_expression_containing_IsNull_correctly_d AssertSql( @"SELECT CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN (CAST(LEN([g].[Nickname]) AS int) = 5) AND LEN([g].[Nickname]) IS NOT NULL THEN CAST(1 AS bit) + WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END ELSE NULL @@ -6234,7 +6234,7 @@ WHERE [g].[Discriminator] IN (N'Gear', N'Officer') ORDER BY CASE WHEN CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CASE - WHEN (CAST(LEN([g].[Nickname]) AS int) = 5) AND LEN([g].[Nickname]) IS NOT NULL THEN CAST(1 AS bit) + WHEN CAST(LEN([g].[Nickname]) AS int) = 5 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END ELSE NULL @@ -6355,7 +6355,7 @@ FROM [Gears] AS [g] WHERE [g].[Discriminator] IN (N'Gear', N'Officer') ) AS [t0] ON ([t].[GearNickName] = [t0].[Nickname]) AND ([t].[GearSquadId] = [t0].[SquadId]) LEFT JOIN [Squads] AS [s] ON [t0].[SquadId] = [s].[Id] -WHERE (SUBSTRING([t].[Note], 0 + 1, CAST(LEN([s].[Name]) AS int)) = [t].[GearNickName]) OR (SUBSTRING([t].[Note], 0 + 1, CAST(LEN([s].[Name]) AS int)) IS NULL AND [t].[GearNickName] IS NULL)"); +WHERE (SUBSTRING([t].[Note], 0 + 1, CAST(LEN([s].[Name]) AS int)) = [t].[GearNickName]) OR (([t].[Note] IS NULL OR [s].[Name] IS NULL) AND [t].[GearNickName] IS NULL)"); } public override async Task Filter_with_new_Guid(bool async) @@ -7316,7 +7316,7 @@ public override void Byte_array_filter_by_length_parameter_compiled() SELECT COUNT(*) FROM [Squads] AS [s] -WHERE (CAST(DATALENGTH([s].[Banner]) AS int) = CAST(DATALENGTH(@__byteArrayParam) AS int)) OR (DATALENGTH([s].[Banner]) IS NULL AND DATALENGTH(@__byteArrayParam) IS NULL)"); +WHERE CAST(DATALENGTH([s].[Banner]) AS int) = CAST(DATALENGTH(@__byteArrayParam) AS int)"); } public override async Task Byte_array_contains_parameter(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs index 4cc80797b6b..47660310fe5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindFunctionsQuerySqlServerTest.cs @@ -873,7 +873,7 @@ public override async Task Where_guid_newguid(bool async) AssertSql( @"SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] FROM [Order Details] AS [o] -WHERE (NEWID() <> '00000000-0000-0000-0000-000000000000') OR NEWID() IS NULL"); +WHERE NEWID() <> '00000000-0000-0000-0000-000000000000'"); } public override async Task Where_string_to_upper(bool async) @@ -1141,35 +1141,35 @@ public override async Task Convert_ToString(bool async) AssertSql( @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[CustomerID] = N'ALFKI') AND ((CONVERT(nvarchar(max), CONVERT(tinyint, [o].[OrderID] % 1)) <> N'10') OR CONVERT(nvarchar(max), CONVERT(tinyint, [o].[OrderID] % 1)) IS NULL)", +WHERE ([o].[CustomerID] = N'ALFKI') AND (CONVERT(nvarchar(max), CONVERT(tinyint, [o].[OrderID] % 1)) <> N'10')", // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[CustomerID] = N'ALFKI') AND ((CONVERT(nvarchar(max), CONVERT(decimal(18, 2), [o].[OrderID] % 1)) <> N'10') OR CONVERT(nvarchar(max), CONVERT(decimal(18, 2), [o].[OrderID] % 1)) IS NULL)", +WHERE ([o].[CustomerID] = N'ALFKI') AND (CONVERT(nvarchar(max), CONVERT(decimal(18, 2), [o].[OrderID] % 1)) <> N'10')", // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[CustomerID] = N'ALFKI') AND ((CONVERT(nvarchar(max), CONVERT(float, [o].[OrderID] % 1)) <> N'10') OR CONVERT(nvarchar(max), CONVERT(float, [o].[OrderID] % 1)) IS NULL)", +WHERE ([o].[CustomerID] = N'ALFKI') AND (CONVERT(nvarchar(max), CONVERT(float, [o].[OrderID] % 1)) <> N'10')", // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[CustomerID] = N'ALFKI') AND ((CONVERT(nvarchar(max), CAST(CONVERT(float, [o].[OrderID] % 1) AS real)) <> N'10') OR CONVERT(nvarchar(max), CAST(CONVERT(float, [o].[OrderID] % 1) AS real)) IS NULL)", +WHERE ([o].[CustomerID] = N'ALFKI') AND (CONVERT(nvarchar(max), CAST(CONVERT(float, [o].[OrderID] % 1) AS real)) <> N'10')", // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[CustomerID] = N'ALFKI') AND ((CONVERT(nvarchar(max), CONVERT(smallint, [o].[OrderID] % 1)) <> N'10') OR CONVERT(nvarchar(max), CONVERT(smallint, [o].[OrderID] % 1)) IS NULL)", +WHERE ([o].[CustomerID] = N'ALFKI') AND (CONVERT(nvarchar(max), CONVERT(smallint, [o].[OrderID] % 1)) <> N'10')", // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[CustomerID] = N'ALFKI') AND ((CONVERT(nvarchar(max), CONVERT(int, [o].[OrderID] % 1)) <> N'10') OR CONVERT(nvarchar(max), CONVERT(int, [o].[OrderID] % 1)) IS NULL)", +WHERE ([o].[CustomerID] = N'ALFKI') AND (CONVERT(nvarchar(max), CONVERT(int, [o].[OrderID] % 1)) <> N'10')", // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[CustomerID] = N'ALFKI') AND ((CONVERT(nvarchar(max), CONVERT(bigint, [o].[OrderID] % 1)) <> N'10') OR CONVERT(nvarchar(max), CONVERT(bigint, [o].[OrderID] % 1)) IS NULL)", +WHERE ([o].[CustomerID] = N'ALFKI') AND (CONVERT(nvarchar(max), CONVERT(bigint, [o].[OrderID] % 1)) <> N'10')", // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[CustomerID] = N'ALFKI') AND ((CONVERT(nvarchar(max), CONVERT(nvarchar(max), [o].[OrderID] % 1)) <> N'10') OR CONVERT(nvarchar(max), CONVERT(nvarchar(max), [o].[OrderID] % 1)) IS NULL)", +WHERE ([o].[CustomerID] = N'ALFKI') AND (CONVERT(nvarchar(max), CONVERT(nvarchar(max), [o].[OrderID] % 1)) <> N'10')", // @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 6d8bfd4f7db..a2315245039 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -1460,7 +1460,7 @@ public override async Task All_top_level_column(bool async) WHEN NOT EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE (([c].[ContactName] <> N'') OR [c].[ContactName] IS NULL) AND ([c].[ContactName] IS NULL OR ((LEFT([c].[ContactName], LEN([c].[ContactName])) <> [c].[ContactName]) OR LEFT([c].[ContactName], LEN([c].[ContactName])) IS NULL))) 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"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index b4b7757bddb..354a5f55616 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -407,7 +407,7 @@ public override void Select_nested_collection_multi_level6() SELECT TOP(1) ( SELECT TOP(1) [o].[ProductID] FROM [Order Details] AS [o] - WHERE ([o0].[OrderID] = [o].[OrderID]) AND (([o].[OrderID] <> CAST(LEN([c].[CustomerID]) AS int)) OR LEN([c].[CustomerID]) IS NULL)) + WHERE ([o0].[OrderID] = [o].[OrderID]) AND ([o].[OrderID] <> CAST(LEN([c].[CustomerID]) AS int))) FROM [Orders] AS [o0] WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND ([o0].[OrderID] < 10500)) AS [Order] FROM [Customers] AS [c] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 7768d32ea2e..8a1ff942efb 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -715,7 +715,7 @@ public override async Task Where_datetime_now(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE (GETDATE() <> @__myDatetime_0) OR GETDATE() IS NULL"); +WHERE GETDATE() <> @__myDatetime_0"); } public override async Task Where_datetime_utcnow(bool async) @@ -727,7 +727,7 @@ public override async Task Where_datetime_utcnow(bool async) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE (GETUTCDATE() <> @__myDatetime_0) OR GETUTCDATE() IS NULL"); +WHERE GETUTCDATE() <> @__myDatetime_0"); } public override async Task Where_datetime_today(bool async) @@ -737,7 +737,7 @@ public override async Task Where_datetime_today(bool async) AssertSql( @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE (CONVERT(date, GETDATE()) = CONVERT(date, GETDATE())) OR CONVERT(date, GETDATE()) IS NULL"); +WHERE CONVERT(date, GETDATE()) = CONVERT(date, GETDATE())"); } public override async Task Where_datetime_date_component(bool async) @@ -849,7 +849,7 @@ public override async Task Where_datetimeoffset_now_component(bool async) AssertSql( @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE (CAST([o].[OrderDate] AS datetimeoffset) = SYSDATETIMEOFFSET()) OR ([o].[OrderDate] IS NULL AND SYSDATETIMEOFFSET() IS NULL)"); +WHERE CAST([o].[OrderDate] AS datetimeoffset) = SYSDATETIMEOFFSET()"); } public override async Task Where_datetimeoffset_utcnow_component(bool async) @@ -859,7 +859,7 @@ public override async Task Where_datetimeoffset_utcnow_component(bool async) AssertSql( @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE (CAST([o].[OrderDate] AS datetimeoffset) = CAST(SYSUTCDATETIME() AS datetimeoffset)) OR ([o].[OrderDate] IS NULL AND SYSUTCDATETIME() IS NULL)"); +WHERE CAST([o].[OrderDate] AS datetimeoffset) = CAST(SYSUTCDATETIME() AS datetimeoffset)"); } public override async Task Where_simple_reversed(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 63aab27ccb3..8b3f97c6f02 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -1370,11 +1370,11 @@ public override async Task Null_semantics_applied_when_comparing_two_functions_w AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE (REPLACE([e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC]) = [e].[NullableStringA]) OR (REPLACE([e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC]) IS NULL AND [e].[NullableStringA] IS NULL)", +WHERE (REPLACE([e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC]) = [e].[NullableStringA]) OR ((([e].[NullableStringA] IS NULL OR [e].[NullableStringB] IS NULL) OR [e].[NullableStringC] IS NULL) AND [e].[NullableStringA] IS NULL)", // @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE ((REPLACE([e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC]) <> [e].[NullableStringA]) OR (REPLACE([e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC]) IS NULL OR [e].[NullableStringA] IS NULL)) AND (REPLACE([e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC]) IS NOT NULL OR [e].[NullableStringA] IS NOT NULL)"); +WHERE ((REPLACE([e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC]) <> [e].[NullableStringA]) OR ((([e].[NullableStringA] IS NULL OR [e].[NullableStringB] IS NULL) OR [e].[NullableStringC] IS NULL) OR [e].[NullableStringA] IS NULL)) AND ((([e].[NullableStringA] IS NOT NULL AND [e].[NullableStringB] IS NOT NULL) AND [e].[NullableStringC] IS NOT NULL) OR [e].[NullableStringA] IS NOT NULL)"); } public override async Task Null_semantics_coalesce(bool async) @@ -1442,7 +1442,7 @@ public override async Task Null_semantics_function(bool async) AssertSql( @"SELECT [e].[Id] FROM [Entities1] AS [e] -WHERE ((SUBSTRING([e].[NullableStringA], 0 + 1, [e].[IntA]) <> [e].[NullableStringB]) OR (SUBSTRING([e].[NullableStringA], 0 + 1, [e].[IntA]) IS NULL OR [e].[NullableStringB] IS NULL)) AND (SUBSTRING([e].[NullableStringA], 0 + 1, [e].[IntA]) IS NOT NULL OR [e].[NullableStringB] IS NOT NULL)"); +WHERE ((SUBSTRING([e].[NullableStringA], 0 + 1, [e].[IntA]) <> [e].[NullableStringB]) OR ([e].[NullableStringA] IS NULL OR [e].[NullableStringB] IS NULL)) AND ([e].[NullableStringA] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL)"); } public override async Task Null_semantics_join_with_composite_key(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 145dd9db521..64885e89dfe 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -100,7 +100,7 @@ public async Task Where_not_equals_DateTime_Now(bool async) AssertSql( @"SELECT [d].[Id], [d].[DateTime], [d].[DateTime2], [d].[DateTime2_0], [d].[DateTime2_1], [d].[DateTime2_2], [d].[DateTime2_3], [d].[DateTime2_4], [d].[DateTime2_5], [d].[DateTime2_6], [d].[DateTime2_7], [d].[SmallDateTime] FROM [Dates] AS [d] -WHERE (((([d].[DateTime2_2] <> GETDATE()) OR GETDATE() IS NULL) AND (([d].[DateTime2_7] <> GETDATE()) OR GETDATE() IS NULL)) AND (([d].[DateTime] <> GETDATE()) OR GETDATE() IS NULL)) AND (([d].[SmallDateTime] <> GETDATE()) OR GETDATE() IS NULL)"); +WHERE ((([d].[DateTime2_2] <> GETDATE()) AND ([d].[DateTime2_7] <> GETDATE())) AND ([d].[DateTime] <> GETDATE())) AND ([d].[SmallDateTime] <> GETDATE())"); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerFixture.cs index 1fb8632011a..1bce1648c00 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerFixture.cs @@ -37,9 +37,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con typeof(GeoExtensions).GetMethod(nameof(GeoExtensions.Distance)), b => b.HasTranslation( e => SqlFunctionExpression.Create( - e.First(), + instance: e.First(), "STDistance", - e.Skip(1), + arguments: e.Skip(1), + nullResultAllowed: true, + instancPropagatesNullability: true, + argumentsPropagateNullability: e.Skip(1).Select(a => true), typeof(double), null))); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs index b518c2e4f1f..17b14006c45 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs @@ -50,20 +50,30 @@ public override async Task AsBinary(bool async) { await base.AsBinary(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].STAsBinary() AS [Binary] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].STAsBinary() AS [Binary] +FROM [PointEntity] AS [p]"); + } + + public override async Task AsBinary_with_null_check(bool async) + { + await base.AsBinary_with_null_check(async); + + AssertSql( + @"SELECT [p].[Id], CASE + WHEN [p].[Point] IS NULL THEN NULL + ELSE [p].[Point].STAsBinary() +END AS [Binary] +FROM [PointEntity] AS [p]"); } public override async Task AsText(bool async) { await base.AsText(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].AsTextZM() AS [Text] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].AsTextZM() AS [Text] +FROM [PointEntity] AS [p]"); } // No SqlServer Translation @@ -76,10 +86,9 @@ public override async Task Buffer(bool async) { await base.Buffer(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Polygon].STBuffer(1.0E0) AS [Buffer] -//FROM [PolygonEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Polygon].STBuffer(1.0E0) AS [Buffer] +FROM [PolygonEntity] AS [p]"); } // No SqlServer Translation @@ -98,22 +107,20 @@ public override async Task Contains(bool async) { await base.Contains(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0xE6100000010C000000000000D03F000000000000D03F' (Size = 22) (DbType = Binary) + AssertSql( + @"@__point_0='0xE6100000010C000000000000D03F000000000000D03F' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STContains(@__point_0) AS [Contains] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STContains(@__point_0) AS [Contains] +FROM [PolygonEntity] AS [p]"); } public override async Task ConvexHull(bool async) { await base.ConvexHull(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Polygon].STConvexHull() AS [ConvexHull] -//FROM [PolygonEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Polygon].STConvexHull() AS [ConvexHull] +FROM [PolygonEntity] AS [p]"); } public override async Task IGeometryCollection_Count(bool async) @@ -156,12 +163,11 @@ public override async Task Difference(bool async) { await base.Difference(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Binary) + AssertSql( + @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STDifference(@__polygon_0) AS [Difference] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STDifference(@__polygon_0) AS [Difference] +FROM [PolygonEntity] AS [p]"); } public override async Task Dimension(bool async) @@ -173,40 +179,84 @@ public override async Task Dimension(bool async) FROM [PointEntity] AS [p]"); } - public override async Task Disjoint(bool async) + public override async Task Disjoint_with_cast_to_nullable(bool async) { - await base.Disjoint(async); + await base.Disjoint_with_cast_to_nullable(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0xE6100000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Binary) + AssertSql( + @"@__point_0='0xE6100000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STDisjoint(@__point_0) AS [Disjoint] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STDisjoint(@__point_0) AS [Disjoint] +FROM [PolygonEntity] AS [p]"); } - public override async Task Distance(bool async) + public override async Task Disjoint_without_cast_to_nullable(bool async) { - await base.Distance(async); + await base.Disjoint_without_cast_to_nullable(async); + + AssertSql( + @"@__point_0='0xE6100000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) - // issue #16050 -// AssertSql( -// @"@__point_0='0xE6100000010C000000000000F03F0000000000000000' (Size = 22) (DbType = Binary) +SELECT [p].[Id], [p].[Polygon].STDisjoint(@__point_0) AS [Disjoint] +FROM [PolygonEntity] AS [p]"); + } + + public override async Task Disjoint_with_null_check(bool async) + { + await base.Disjoint_with_null_check(async); + + AssertSql( + @"@__point_0='0xE6100000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) + +SELECT [p].[Id], CASE + WHEN [p].[Polygon] IS NULL THEN NULL + ELSE [p].[Polygon].STDisjoint(@__point_0) +END AS [Disjoint] +FROM [PolygonEntity] AS [p]"); + } + + public override async Task Distance_without_null_check(bool async) + { + await base.Distance_without_null_check(async); + + AssertSql( + @"@__point_0='0xE6100000010C000000000000F03F0000000000000000' (Size = 22) (DbType = Object) + +SELECT [p].[Id], [p].[Point].STDistance(@__point_0) AS [Distance] +FROM [PointEntity] AS [p]"); + } + + public override async Task Distance_with_null_check(bool async) + { + await base.Distance_with_null_check(async); + + AssertSql( + @"@__point_0='0xE6100000010C000000000000F03F0000000000000000' (Size = 22) (DbType = Object) + +SELECT [p].[Id], [p].[Point].STDistance(@__point_0) AS [Distance] +FROM [PointEntity] AS [p]"); + } + + public override async Task Distance_with_cast_to_nullable(bool async) + { + await base.Distance_with_cast_to_nullable(async); + + AssertSql( + @"@__point_0='0xE6100000010C000000000000F03F0000000000000000' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Point].STDistance(@__point_0) AS [Distance] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], [p].[Point].STDistance(@__point_0) AS [Distance] +FROM [PointEntity] AS [p]"); } public override async Task Distance_geometry(bool async) { await base.Distance_geometry(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0xE6100000010C000000000000F03F0000000000000000' (Size = 22) (DbType = Binary) + AssertSql( + @"@__point_0='0xE6100000010C000000000000F03F0000000000000000' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Geometry].STDistance(@__point_0) AS [Distance] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], [p].[Geometry].STDistance(@__point_0) AS [Distance] +FROM [PointEntity] AS [p]"); } // Mixing SRIDs not supported @@ -219,10 +269,9 @@ public override async Task Distance_constant_srid_4326(bool async) { await base.Distance_constant_srid_4326(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].STDistance('POINT (1 1)') AS [Distance] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].STDistance('POINT (1 1)') AS [Distance] +FROM [PointEntity] AS [p]"); } // Mixing SRIDs not supported @@ -284,12 +333,11 @@ public override async Task EqualsTopologically(bool async) { await base.EqualsTopologically(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0xE6100000010C00000000000000000000000000000000' (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0xE6100000010C00000000000000000000000000000000' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Point].STEquals(@__point_0) AS [EqualsTopologically] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], [p].[Point].STEquals(@__point_0) AS [EqualsTopologically] +FROM [PointEntity] AS [p]"); } public override async Task ExteriorRing(bool async) @@ -314,10 +362,15 @@ public override async Task GetGeometryN(bool async) { await base.GetGeometryN(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[MultiLineString].STGeometryN(0 + 1) AS [Geometry0] -//FROM [MultiLineStringEntity] AS [e]"); + AssertSql( + @"SELECT [m].[Id], [m].[MultiLineString].STGeometryN(0 + 1) AS [Geometry0] +FROM [MultiLineStringEntity] AS [m]"); + } + + public override Task GetGeometryN_with_null_argument(bool async) + { + // 'geometry::STGeometryN' failed because parameter 1 is not allowed to be null. + return Task.CompletedTask; } public override async Task GetInteriorRingN(bool async) @@ -326,7 +379,7 @@ public override async Task GetInteriorRingN(bool async) AssertSql( @"SELECT [p].[Id], CASE - WHEN [p].[Polygon] IS NULL OR (([p].[Polygon].NumRings() - 1) = 0) THEN NULL + WHEN ([p].[Polygon].NumRings() - 1) = 0 THEN NULL ELSE [p].[Polygon].RingN(0 + 2) END AS [InteriorRing0] FROM [PolygonEntity] AS [p]"); @@ -336,10 +389,9 @@ public override async Task GetPointN(bool async) { await base.GetPointN(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[LineString].STPointN(0 + 1) AS [Point0] -//FROM [LineStringEntity] AS [e]"); + AssertSql( + @"SELECT [l].[Id], [l].[LineString].STPointN(0 + 1) AS [Point0] +FROM [LineStringEntity] AS [l]"); } // No SqlServer Translation @@ -352,24 +404,22 @@ public override async Task Intersection(bool async) { await base.Intersection(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STIntersection(@__polygon_0) AS [Intersection] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STIntersection(@__polygon_0) AS [Intersection] +FROM [PolygonEntity] AS [p]"); } public override async Task Intersects(bool async) { await base.Intersects(async); - // issue #16050 -// AssertSql( -// @"@__lineString_0='0xE61000000114000000000000E0BF000000000000E03F000000000000E03F0000...' (Size = 38) (DbType = Object) + AssertSql( + @"@__lineString_0='0xE61000000114000000000000E0BF000000000000E03F000000000000E03F0000...' (Size = 38) (DbType = Object) -//SELECT [e].[Id], [e].[LineString].STIntersects(@__lineString_0) AS [Intersects] -//FROM [LineStringEntity] AS [e]"); +SELECT [l].[Id], [l].[LineString].STIntersects(@__lineString_0) AS [Intersects] +FROM [LineStringEntity] AS [l]"); } public override async Task ICurve_IsClosed(bool async) @@ -428,11 +478,8 @@ public override async Task IsWithinDistance(bool async) @"@__point_0='0xE6100000010C000000000000F03F0000000000000000' (Size = 22) (DbType = Object) SELECT [p].[Id], CASE - WHEN [p].[Point] IS NULL THEN NULL - ELSE CASE - WHEN [p].[Point].STDistance(@__point_0) <= 1.0E0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [p].[Point].STDistance(@__point_0) <= 1.0E0 THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) END AS [IsWithinDistance] FROM [PointEntity] AS [p]"); } @@ -441,10 +488,9 @@ public override async Task Item(bool async) { await base.Item(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[MultiLineString].STGeometryN(0 + 1) AS [Item0] -//FROM [MultiLineStringEntity] AS [e]"); + AssertSql( + @"SELECT [m].[Id], [m].[MultiLineString].STGeometryN(0 + 1) AS [Item0] +FROM [MultiLineStringEntity] AS [m]"); } public override async Task Length(bool async) @@ -517,12 +563,11 @@ public override async Task Overlaps(bool async) { await base.Overlaps(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STOverlaps(@__polygon_0) AS [Overlaps] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STOverlaps(@__polygon_0) AS [Overlaps] +FROM [PolygonEntity] AS [p]"); } // No SqlServer Translation @@ -574,32 +619,29 @@ public override async Task SymmetricDifference(bool async) { await base.SymmetricDifference(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STSymDifference(@__polygon_0) AS [SymmetricDifference] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STSymDifference(@__polygon_0) AS [SymmetricDifference] +FROM [PolygonEntity] AS [p]"); } public override async Task ToBinary(bool async) { await base.ToBinary(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].STAsBinary() AS [Binary] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].STAsBinary() AS [Binary] +FROM [PointEntity] AS [p]"); } public override async Task ToText(bool async) { await base.ToText(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].AsTextZM() AS [Text] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].AsTextZM() AS [Text] +FROM [PointEntity] AS [p]"); } // No SqlServer Translation @@ -612,12 +654,11 @@ public override async Task Union(bool async) { await base.Union(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0xE610000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STUnion(@__polygon_0) AS [Union] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STUnion(@__polygon_0) AS [Union] +FROM [PolygonEntity] AS [p]"); } // No SqlServer Translation @@ -630,12 +671,11 @@ public override async Task Within(bool async) { await base.Within(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0xE6100000010405000000000000000000F0BF000000000000F0BF000000000000...' (Size = 112) (DbType = Object) + AssertSql( + @"@__polygon_0='0xE6100000010405000000000000000000F0BF000000000000F0BF000000000000...' (Size = 112) (DbType = Object) -//SELECT [e].[Id], [e].[Point].STWithin(@__polygon_0) AS [Within] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], [p].[Point].STWithin(@__polygon_0) AS [Within] +FROM [PointEntity] AS [p]"); } public override async Task X(bool async) @@ -665,6 +705,54 @@ public override async Task Z(bool async) FROM [PointEntity] AS [p]"); } + public override async Task IsEmpty_equal_to_null(bool async) + { + await base.IsEmpty_equal_to_null(async); + + AssertSql( + @"SELECT [p].[Id] +FROM [PointEntity] AS [p] +WHERE [p].[Point] IS NULL"); + } + + public override async Task IsEmpty_not_equal_to_null(bool async) + { + await base.IsEmpty_not_equal_to_null(async); + + AssertSql( + @"SELECT [p].[Id] +FROM [PointEntity] AS [p] +WHERE [p].[Point] IS NOT NULL"); + } + + public override async Task Intersects_equal_to_null(bool async) + { + await base.Intersects_equal_to_null(async); + + AssertSql( + @"SELECT [l].[Id] +FROM [LineStringEntity] AS [l] +WHERE [l].[LineString] IS NULL", + // + @"SELECT [l].[Id] +FROM [LineStringEntity] AS [l] +WHERE [l].[LineString] IS NULL"); + } + + public override async Task Intersects_not_equal_to_null(bool async) + { + await base.Intersects_not_equal_to_null(async); + + AssertSql( + @"SELECT [l].[Id] +FROM [LineStringEntity] AS [l] +WHERE [l].[LineString] IS NOT NULL", + // + @"SELECT [l].[Id] +FROM [LineStringEntity] AS [l] +WHERE [l].[LineString] IS NOT NULL"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs index d51c6a1fe27..dd1b1ede365 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs @@ -50,20 +50,30 @@ public override async Task AsBinary(bool async) { await base.AsBinary(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].STAsBinary() AS [Binary] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].STAsBinary() AS [Binary] +FROM [PointEntity] AS [p]"); + } + + public override async Task AsBinary_with_null_check(bool async) + { + await base.AsBinary_with_null_check(async); + + AssertSql( + @"SELECT [p].[Id], CASE + WHEN [p].[Point] IS NULL THEN NULL + ELSE [p].[Point].STAsBinary() +END AS [Binary] +FROM [PointEntity] AS [p]"); } public override async Task AsText(bool async) { await base.AsText(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].AsTextZM() AS [Text] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].AsTextZM() AS [Text] +FROM [PointEntity] AS [p]"); } public override async Task Boundary(bool async) @@ -79,10 +89,9 @@ public override async Task Buffer(bool async) { await base.Buffer(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Polygon].STBuffer(1.0E0) AS [Buffer] -//FROM [PolygonEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Polygon].STBuffer(1.0E0) AS [Buffer] +FROM [PolygonEntity] AS [p]"); } // No SqlServer Translation @@ -104,22 +113,20 @@ public override async Task Contains(bool async) { await base.Contains(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x00000000010C000000000000D03F000000000000D03F' (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0x00000000010C000000000000D03F000000000000D03F' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STContains(@__point_0) AS [Contains] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STContains(@__point_0) AS [Contains] +FROM [PolygonEntity] AS [p]"); } public override async Task ConvexHull(bool async) { await base.ConvexHull(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Polygon].STConvexHull() AS [ConvexHull] -//FROM [PolygonEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Polygon].STConvexHull() AS [ConvexHull] +FROM [PolygonEntity] AS [p]"); } public override async Task IGeometryCollection_Count(bool async) @@ -143,7 +150,7 @@ public override async Task LineString_Count(bool async) // No SqlServer Translation public override Task CoveredBy(bool async) { - return base.CoveredBy(async); + return Task.CompletedTask; } // No SqlServer Translation @@ -156,48 +163,44 @@ public override async Task Crosses(bool async) { await base.Crosses(async); - // issue #16050 -// AssertSql( -// @"@__lineString_0='0x000000000114000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 38) (DbType = Object) + AssertSql( + @"@__lineString_0='0x000000000114000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 38) (DbType = Object) -//SELECT [e].[Id], [e].[LineString].STCrosses(@__lineString_0) AS [Crosses] -//FROM [LineStringEntity] AS [e]"); +SELECT [l].[Id], [l].[LineString].STCrosses(@__lineString_0) AS [Crosses] +FROM [LineStringEntity] AS [l]"); } public override async Task Difference(bool async) { await base.Difference(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STDifference(@__polygon_0) AS [Difference] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STDifference(@__polygon_0) AS [Difference] +FROM [PolygonEntity] AS [p]"); } public override async Task Distance_on_converted_geometry_type(bool async) { await base.Distance_on_converted_geometry_type(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x00000000010C000000000000F03F0000000000000000' (Nullable = false) (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0x00000000010C000000000000F03F0000000000000000' (Nullable = false) (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Location].STDistance(@__point_0) AS [Distance] -//FROM [GeoPointEntity] AS [e]"); +SELECT [g].[Id], [g].[Location].STDistance(@__point_0) AS [Distance] +FROM [GeoPointEntity] AS [g]"); } public override async Task Distance_on_converted_geometry_type_lhs(bool async) { await base.Distance_on_converted_geometry_type_lhs(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x00000000010C000000000000F03F0000000000000000' (Nullable = false) (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0x00000000010C000000000000F03F0000000000000000' (Nullable = false) (Size = 22) (DbType = Object) -//SELECT [e].[Id], @__point_0.STDistance([e].[Location]) AS [Distance] -//FROM [GeoPointEntity] AS [e]"); +SELECT [g].[Id], @__point_0.STDistance([g].[Location]) AS [Distance] +FROM [GeoPointEntity] AS [g]"); } public override async Task Distance_on_converted_geometry_type_constant(bool async) @@ -222,18 +225,17 @@ public override async Task Distance_constant(bool async) { await base.Distance_constant(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].STDistance('POINT (0 1)') AS [Distance] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].STDistance('POINT (0 1)') AS [Distance] +FROM [PointEntity] AS [p]"); } public override async Task Distance_constant_srid_4326(bool async) { await AssertQuery( async, - ss => ss.Set().Select( - e => new { e.Id, Distance = e.Point == null ? (double?)null : e.Point.Distance(new Point(1, 1) { SRID = 4326 }) }), + ss => ss.Set().Select(e => new { e.Id, Distance = (double?)e.Point.Distance(new Point(1, 1) { SRID = 4326 }) }), + ss => ss.Set().Select(e => new { e.Id, Distance = e.Point == null ? (double?)null : e.Point.Distance(new Point(1, 1) { SRID = 4326 }) }), elementSorter: e => e.Id, elementAsserter: (e, a) => { @@ -241,20 +243,18 @@ await AssertQuery( Assert.Null(a.Distance); }); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].STDistance(geometry::STGeomFromText('POINT (1 1)', 4326)) AS [Distance] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].STDistance(geometry::STGeomFromText('POINT (1 1)', 4326)) AS [Distance] +FROM [PointEntity] AS [p]"); } public override async Task Distance_constant_lhs(bool async) { await base.Distance_constant_lhs(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], geometry::Parse('POINT (0 1)').STDistance([e].[Point]) AS [Distance] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], geometry::Parse('POINT (0 1)').STDistance([p].[Point]) AS [Distance] +FROM [PointEntity] AS [p]"); } public override async Task Dimension(bool async) @@ -266,40 +266,84 @@ public override async Task Dimension(bool async) FROM [PointEntity] AS [p]"); } - public override async Task Disjoint(bool async) + public override async Task Disjoint_with_cast_to_nullable(bool async) + { + await base.Disjoint_with_cast_to_nullable(async); + + AssertSql( + @"@__point_0='0x00000000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) + +SELECT [p].[Id], [p].[Polygon].STDisjoint(@__point_0) AS [Disjoint] +FROM [PolygonEntity] AS [p]"); + } + + public override async Task Disjoint_without_cast_to_nullable(bool async) + { + await base.Disjoint_without_cast_to_nullable(async); + + AssertSql( + @"@__point_0='0x00000000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) + +SELECT [p].[Id], [p].[Polygon].STDisjoint(@__point_0) AS [Disjoint] +FROM [PolygonEntity] AS [p]"); + } + + public override async Task Disjoint_with_null_check(bool async) + { + await base.Disjoint_with_null_check(async); + + AssertSql( + @"@__point_0='0x00000000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) + +SELECT [p].[Id], CASE + WHEN [p].[Polygon] IS NULL THEN NULL + ELSE [p].[Polygon].STDisjoint(@__point_0) +END AS [Disjoint] +FROM [PolygonEntity] AS [p]"); + } + + public override async Task Distance_without_null_check(bool async) + { + await base.Distance_without_null_check(async); + + AssertSql( + @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) + +SELECT [p].[Id], [p].[Point].STDistance(@__point_0) AS [Distance] +FROM [PointEntity] AS [p]"); + } + + public override async Task Distance_with_null_check(bool async) { - await base.Disjoint(async); + await base.Distance_with_null_check(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x00000000010C000000000000F03F000000000000F03F' (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STDisjoint(@__point_0) AS [Disjoint] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Point].STDistance(@__point_0) AS [Distance] +FROM [PointEntity] AS [p]"); } - public override async Task Distance(bool async) + public override async Task Distance_with_cast_to_nullable(bool async) { - await base.Distance(async); + await base.Distance_with_cast_to_nullable(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Point].STDistance(@__point_0) AS [Distance] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], [p].[Point].STDistance(@__point_0) AS [Distance] +FROM [PointEntity] AS [p]"); } public override async Task Distance_geometry(bool async) { await base.Distance_geometry(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Geometry].STDistance(@__point_0) AS [Distance] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], [p].[Geometry].STDistance(@__point_0) AS [Distance] +FROM [PointEntity] AS [p]"); } public override async Task EndPoint(bool async) @@ -324,12 +368,11 @@ public override async Task EqualsTopologically(bool async) { await base.EqualsTopologically(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x00000000010C00000000000000000000000000000000' (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0x00000000010C00000000000000000000000000000000' (Size = 22) (DbType = Object) -//SELECT [e].[Id], [e].[Point].STEquals(@__point_0) AS [EqualsTopologically] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], [p].[Point].STEquals(@__point_0) AS [EqualsTopologically] +FROM [PointEntity] AS [p]"); } public override async Task ExteriorRing(bool async) @@ -354,10 +397,15 @@ public override async Task GetGeometryN(bool async) { await base.GetGeometryN(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[MultiLineString].STGeometryN(0 + 1) AS [Geometry0] -//FROM [MultiLineStringEntity] AS [e]"); + AssertSql( + @"SELECT [m].[Id], [m].[MultiLineString].STGeometryN(0 + 1) AS [Geometry0] +FROM [MultiLineStringEntity] AS [m]"); + } + + public override Task GetGeometryN_with_null_argument(bool async) + { + // 'geometry::STGeometryN' failed because parameter 1 is not allowed to be null. + return Task.CompletedTask; } public override async Task GetInteriorRingN(bool async) @@ -366,7 +414,7 @@ public override async Task GetInteriorRingN(bool async) AssertSql( @"SELECT [p].[Id], CASE - WHEN [p].[Polygon] IS NULL OR ([p].[Polygon].STNumInteriorRing() = 0) THEN NULL + WHEN [p].[Polygon].STNumInteriorRing() = 0 THEN NULL ELSE [p].[Polygon].STInteriorRingN(0 + 1) END AS [InteriorRing0] FROM [PolygonEntity] AS [p]"); @@ -376,10 +424,9 @@ public override async Task GetPointN(bool async) { await base.GetPointN(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[LineString].STPointN(0 + 1) AS [Point0] -//FROM [LineStringEntity] AS [e]"); + AssertSql( + @"SELECT [l].[Id], [l].[LineString].STPointN(0 + 1) AS [Point0] +FROM [LineStringEntity] AS [l]"); } public override async Task InteriorPoint(bool async) @@ -395,24 +442,22 @@ public override async Task Intersection(bool async) { await base.Intersection(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STIntersection(@__polygon_0) AS [Intersection] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STIntersection(@__polygon_0) AS [Intersection] +FROM [PolygonEntity] AS [p]"); } public override async Task Intersects(bool async) { await base.Intersects(async); - // issue #16050 -// AssertSql( -// @"@__lineString_0='0x000000000114000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 38) (DbType = Object) + AssertSql( + @"@__lineString_0='0x000000000114000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 38) (DbType = Object) -//SELECT [e].[Id], [e].[LineString].STIntersects(@__lineString_0) AS [Intersects] -//FROM [LineStringEntity] AS [e]"); +SELECT [l].[Id], [l].[LineString].STIntersects(@__lineString_0) AS [Intersects] +FROM [LineStringEntity] AS [l]"); } public override async Task ICurve_IsClosed(bool async) @@ -473,25 +518,23 @@ public override async Task IsWithinDistance(bool async) { await base.IsWithinDistance(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) + AssertSql( + @"@__point_0='0x00000000010C0000000000000000000000000000F03F' (Size = 22) (DbType = Object) -//SELECT [e].[Id], CASE -// WHEN [e].[Point].STDistance(@__point_0) <= 1.0E0 -// THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -//END AS [IsWithinDistance] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], CASE + WHEN [p].[Point].STDistance(@__point_0) <= 1.0E0 THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END AS [IsWithinDistance] +FROM [PointEntity] AS [p]"); } public override async Task Item(bool async) { await base.Item(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[MultiLineString].STGeometryN(0 + 1) AS [Item0] -//FROM [MultiLineStringEntity] AS [e]"); + AssertSql( + @"SELECT [m].[Id], [m].[MultiLineString].STGeometryN(0 + 1) AS [Item0] +FROM [MultiLineStringEntity] AS [m]"); } public override async Task Length(bool async) @@ -563,12 +606,11 @@ public override async Task Overlaps(bool async) { await base.Overlaps(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STOverlaps(@__polygon_0) AS [Overlaps] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STOverlaps(@__polygon_0) AS [Overlaps] +FROM [PolygonEntity] AS [p]"); } public override async Task PointOnSurface(bool async) @@ -584,18 +626,17 @@ public override async Task Relate(bool async) { await base.Relate(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STRelate(@__polygon_0, N'212111212') AS [Relate] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STRelate(@__polygon_0, N'212111212') AS [Relate] +FROM [PolygonEntity] AS [p]"); } // No SqlServer Translation public override Task Reverse(bool async) { - return base.Reverse(async); + return Task.CompletedTask; } public override async Task SRID(bool async) @@ -629,56 +670,51 @@ public override async Task SymmetricDifference(bool async) { await base.SymmetricDifference(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STSymDifference(@__polygon_0) AS [SymmetricDifference] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STSymDifference(@__polygon_0) AS [SymmetricDifference] +FROM [PolygonEntity] AS [p]"); } public override async Task ToBinary(bool async) { await base.ToBinary(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].STAsBinary() AS [Binary] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].STAsBinary() AS [Binary] +FROM [PointEntity] AS [p]"); } public override async Task ToText(bool async) { await base.ToText(async); - // issue #16050 -// AssertSql( -// @"SELECT [e].[Id], [e].[Point].AsTextZM() AS [Text] -//FROM [PointEntity] AS [e]"); + AssertSql( + @"SELECT [p].[Id], [p].[Point].AsTextZM() AS [Text] +FROM [PointEntity] AS [p]"); } public override async Task Touches(bool async) { await base.Touches(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x000000000104040000000000000000000000000000000000F03F000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0x000000000104040000000000000000000000000000000000F03F000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STTouches(@__polygon_0) AS [Touches] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STTouches(@__polygon_0) AS [Touches] +FROM [PolygonEntity] AS [p]"); } public override async Task Union(bool async) { await base.Union(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) + AssertSql( + @"@__polygon_0='0x0000000001040400000000000000000000000000000000000000000000000000...' (Size = 96) (DbType = Object) -//SELECT [e].[Id], [e].[Polygon].STUnion(@__polygon_0) AS [Union] -//FROM [PolygonEntity] AS [e]"); +SELECT [p].[Id], [p].[Polygon].STUnion(@__polygon_0) AS [Union] +FROM [PolygonEntity] AS [p]"); } // No SqlServer Translation @@ -691,12 +727,11 @@ public override async Task Within(bool async) { await base.Within(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x00000000010405000000000000000000F0BF000000000000F0BF000000000000...' (Size = 112) (DbType = Object) + AssertSql( + @"@__polygon_0='0x00000000010405000000000000000000F0BF000000000000F0BF000000000000...' (Size = 112) (DbType = Object) -//SELECT [e].[Id], [e].[Point].STWithin(@__polygon_0) AS [Within] -//FROM [PointEntity] AS [e]"); +SELECT [p].[Id], [p].[Point].STWithin(@__polygon_0) AS [Within] +FROM [PointEntity] AS [p]"); } public override async Task X(bool async) @@ -726,6 +761,53 @@ public override async Task Z(bool async) FROM [PointEntity] AS [p]"); } + public override async Task IsEmpty_equal_to_null(bool async) + { + await base.IsEmpty_equal_to_null(async); + + AssertSql( + @"SELECT [p].[Id] +FROM [PointEntity] AS [p] +WHERE [p].[Point] IS NULL"); + } + + public override async Task IsEmpty_not_equal_to_null(bool async) + { + await base.IsEmpty_not_equal_to_null(async); + + AssertSql( + @"SELECT [p].[Id] +FROM [PointEntity] AS [p] +WHERE [p].[Point] IS NOT NULL"); + } + + public override async Task Intersects_equal_to_null(bool async) + { + await base.Intersects_equal_to_null(async); + + AssertSql( + @"SELECT [l].[Id] +FROM [LineStringEntity] AS [l] +WHERE [l].[LineString] IS NULL", + // + @"SELECT [l].[Id] +FROM [LineStringEntity] AS [l] +WHERE [l].[LineString] IS NULL"); } + + public override async Task Intersects_not_equal_to_null(bool async) + { + await base.Intersects_not_equal_to_null(async); + + AssertSql( + @"SELECT [l].[Id] +FROM [LineStringEntity] AS [l] +WHERE [l].[LineString] IS NOT NULL", + // + @"SELECT [l].[Id] +FROM [LineStringEntity] AS [l] +WHERE [l].[LineString] IS NOT NULL"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 9480811a5f8..c147349ef0b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -134,7 +134,7 @@ public override void Byte_array_filter_by_length_parameter_compiled() SELECT COUNT(*) FROM ""Squads"" AS ""s"" -WHERE (length(""s"".""Banner"") = length(@__byteArrayParam)) OR (length(""s"".""Banner"") IS NULL AND length(@__byteArrayParam) IS NULL)"); +WHERE length(""s"".""Banner"") = length(@__byteArrayParam)"); } private void AssertSql(params string[] expected) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs index e9401c7903d..c82a7de6b44 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs @@ -53,7 +53,7 @@ public override async Task Where_datetime_now(bool async) SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" FROM ""Customers"" AS ""c"" -WHERE (rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime'), '0'), '.') <> @__myDatetime_0) OR rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime'), '0'), '.') IS NULL"); +WHERE rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime'), '0'), '.') <> @__myDatetime_0"); } public override async Task Where_datetime_utcnow(bool async) @@ -65,7 +65,7 @@ public override async Task Where_datetime_utcnow(bool async) SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" FROM ""Customers"" AS ""c"" -WHERE (rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now'), '0'), '.') <> @__myDatetime_0) OR rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now'), '0'), '.') IS NULL"); +WHERE rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now'), '0'), '.') <> @__myDatetime_0"); } public override async Task Where_datetime_today(bool async) @@ -75,7 +75,7 @@ public override async Task Where_datetime_today(bool async) AssertSql( @"SELECT ""e"".""EmployeeID"", ""e"".""City"", ""e"".""Country"", ""e"".""FirstName"", ""e"".""ReportsTo"", ""e"".""Title"" FROM ""Employees"" AS ""e"" -WHERE (rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime', 'start of day'), '0'), '.') = rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime', 'start of day'), '0'), '.')) OR rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime', 'start of day'), '0'), '.') IS NULL"); +WHERE rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime', 'start of day'), '0'), '.') = rtrim(rtrim(strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime', 'start of day'), '0'), '.')"); } public override async Task Where_datetime_date_component(bool async) @@ -188,7 +188,7 @@ public override async Task Where_string_indexof(bool async) AssertSql( @"SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" FROM ""Customers"" AS ""c"" -WHERE ((instr(""c"".""City"", 'Sea') - 1) <> -1) OR instr(""c"".""City"", 'Sea') IS NULL"); +WHERE ((instr(""c"".""City"", 'Sea') - 1) <> -1) OR ""c"".""City"" IS NULL"); } public override async Task Where_string_replace(bool async) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteFixture.cs index b64ce5f34bf..cedb9ae4699 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteFixture.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Linq; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; @@ -39,7 +40,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b => b.HasTranslation( e => SqlFunctionExpression.Create( "Distance", - e, + arguments: e, + nullResultAllowed: true, + argumentsPropagateNullability: e.Select(a => true).ToList(), typeof(double), null))); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteTest.cs index 570efcd5c3c..31133bd4cde 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/SpatialQuerySqliteTest.cs @@ -88,20 +88,30 @@ public override async Task AsBinary(bool async) { await base.AsBinary(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", AsBinary(""e"".""Point"") AS ""Binary"" -//FROM ""PointEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", AsBinary(""p"".""Point"") AS ""Binary"" +FROM ""PointEntity"" AS ""p"""); + } + + public override async Task AsBinary_with_null_check(bool async) + { + await base.AsBinary_with_null_check(async); + + AssertSql( + @"SELECT ""p"".""Id"", CASE + WHEN ""p"".""Point"" IS NULL THEN NULL + ELSE AsBinary(""p"".""Point"") +END AS ""Binary"" +FROM ""PointEntity"" AS ""p"""); } public override async Task AsText(bool async) { await base.AsText(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", AsText(""e"".""Point"") AS ""Text"" -//FROM ""PointEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", AsText(""p"".""Point"") AS ""Text"" +FROM ""PointEntity"" AS ""p"""); } public override async Task Boundary(bool async) @@ -117,20 +127,18 @@ public override async Task Buffer(bool async) { await base.Buffer(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", Buffer(""e"".""Polygon"", 1.0) AS ""Buffer"" -//FROM ""PolygonEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", Buffer(""p"".""Polygon"", 1.0) AS ""Buffer"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task Buffer_quadrantSegments(bool async) { await base.Buffer_quadrantSegments(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", Buffer(""e"".""Polygon"", 1.0, 8) AS ""Buffer"" -//FROM ""PolygonEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", Buffer(""p"".""Polygon"", 1.0, 8) AS ""Buffer"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task Centroid(bool async) @@ -146,24 +154,22 @@ public override async Task Contains(bool async) { await base.Contains(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x000100000000000000000000D03F000000000000D03F000000000000D03F0000...' (Size = 60) (DbType = String) + AssertSql( + @"@__point_0='0x000100000000000000000000D03F000000000000D03F000000000000D03F0000...' (Size = 60) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Polygon"" IS NOT NULL THEN Contains(""e"".""Polygon"", @__point_0) -//END AS ""Contains"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Polygon"" IS NOT NULL THEN Contains(""p"".""Polygon"", @__point_0) +END AS ""Contains"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task ConvexHull(bool async) { await base.ConvexHull(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", ConvexHull(""e"".""Polygon"") AS ""ConvexHull"" -//FROM ""PolygonEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", ConvexHull(""p"".""Polygon"") AS ""ConvexHull"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task IGeometryCollection_Count(bool async) @@ -188,54 +194,50 @@ public override async Task CoveredBy(bool async) { await base.CoveredBy(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x000100000000000000000000F0BF000000000000F0BF00000000000000400000...' (Size = 132) (DbType = String) + AssertSql( + @"@__polygon_0='0x000100000000000000000000F0BF000000000000F0BF00000000000000400000...' (Size = 132) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Point"" IS NOT NULL THEN CoveredBy(""e"".""Point"", @__polygon_0) -//END AS ""CoveredBy"" -//FROM ""PointEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Point"" IS NOT NULL THEN CoveredBy(""p"".""Point"", @__polygon_0) +END AS ""CoveredBy"" +FROM ""PointEntity"" AS ""p"""); } public override async Task Covers(bool async) { await base.Covers(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x000100000000000000000000D03F000000000000D03F000000000000D03F0000...' (Size = 60) (DbType = String) + AssertSql( + @"@__point_0='0x000100000000000000000000D03F000000000000D03F000000000000D03F0000...' (Size = 60) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Polygon"" IS NOT NULL THEN Covers(""e"".""Polygon"", @__point_0) -//END AS ""Covers"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Polygon"" IS NOT NULL THEN Covers(""p"".""Polygon"", @__point_0) +END AS ""Covers"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task Crosses(bool async) { await base.Crosses(async); - // issue #16050 -// AssertSql( -// @"@__lineString_0='0x000100000000000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 80) (DbType = String) + AssertSql( + @"@__lineString_0='0x000100000000000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 80) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""LineString"" IS NOT NULL THEN Crosses(""e"".""LineString"", @__lineString_0) -//END AS ""Crosses"" -//FROM ""LineStringEntity"" AS ""e"""); +SELECT ""l"".""Id"", CASE + WHEN ""l"".""LineString"" IS NOT NULL THEN Crosses(""l"".""LineString"", @__lineString_0) +END AS ""Crosses"" +FROM ""LineStringEntity"" AS ""l"""); } public override async Task Difference(bool async) { await base.Difference(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) + AssertSql( + @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) -//SELECT ""e"".""Id"", Difference(""e"".""Polygon"", @__polygon_0) AS ""Difference"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", Difference(""p"".""Polygon"", @__polygon_0) AS ""Difference"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task Dimension(bool async) @@ -247,72 +249,115 @@ public override async Task Dimension(bool async) FROM ""PointEntity"" AS ""p"""); } - public override async Task Disjoint(bool async) + public override async Task Disjoint_with_cast_to_nullable(bool async) { - await base.Disjoint(async); + await base.Disjoint_with_cast_to_nullable(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x000100000000000000000000F03F000000000000F03F000000000000F03F0000...' (Size = 60) (DbType = String) + AssertSql( + @"@__point_0='0x000100000000000000000000F03F000000000000F03F000000000000F03F0000...' (Size = 60) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Polygon"" IS NOT NULL THEN Disjoint(""e"".""Polygon"", @__point_0) -//END AS ""Disjoint"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Polygon"" IS NOT NULL THEN Disjoint(""p"".""Polygon"", @__point_0) +END AS ""Disjoint"" +FROM ""PolygonEntity"" AS ""p"""); } - public override async Task Distance(bool async) + public override async Task Disjoint_without_cast_to_nullable(bool async) { - await base.Distance(async); + await base.Disjoint_without_cast_to_nullable(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) + AssertSql( + @"@__point_0='0x000100000000000000000000F03F000000000000F03F000000000000F03F0000...' (Size = 60) (DbType = String) -//SELECT ""e"".""Id"", Distance(""e"".""Point"", @__point_0) AS ""Distance"" -//FROM ""PointEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Polygon"" IS NOT NULL THEN Disjoint(""p"".""Polygon"", @__point_0) +END AS ""Disjoint"" +FROM ""PolygonEntity"" AS ""p"""); + } + + public override async Task Disjoint_with_null_check(bool async) + { + await base.Disjoint_with_null_check(async); + + AssertSql( + @"@__point_0='0x000100000000000000000000F03F000000000000F03F000000000000F03F0000...' (Size = 60) (DbType = String) + +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Polygon"" IS NULL THEN NULL + WHEN ""p"".""Polygon"" IS NOT NULL THEN Disjoint(""p"".""Polygon"", @__point_0) +END AS ""Disjoint"" +FROM ""PolygonEntity"" AS ""p"""); + } + + public override async Task Distance_without_null_check(bool async) + { + await base.Distance_without_null_check(async); + + AssertSql( + @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) + +SELECT ""p"".""Id"", Distance(""p"".""Point"", @__point_0) AS ""Distance"" +FROM ""PointEntity"" AS ""p"""); + } + + public override async Task Distance_with_null_check(bool async) + { + await base.Distance_with_null_check(async); + + AssertSql( + @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) + +SELECT ""p"".""Id"", Distance(""p"".""Point"", @__point_0) AS ""Distance"" +FROM ""PointEntity"" AS ""p"""); + } + + public override async Task Distance_with_cast_to_nullable(bool async) + { + await base.Distance_with_cast_to_nullable(async); + + AssertSql( + @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) + +SELECT ""p"".""Id"", Distance(""p"".""Point"", @__point_0) AS ""Distance"" +FROM ""PointEntity"" AS ""p"""); } public override async Task Distance_geometry(bool async) { await base.Distance_geometry(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) + AssertSql( + @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) -//SELECT ""e"".""Id"", Distance(""e"".""Geometry"", @__point_0) AS ""Distance"" -//FROM ""PointEntity"" AS ""e"""); +SELECT ""p"".""Id"", Distance(""p"".""Geometry"", @__point_0) AS ""Distance"" +FROM ""PointEntity"" AS ""p"""); } public override async Task Distance_constant(bool async) { await base.Distance_constant(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", Distance(""e"".""Point"", GeomFromText('POINT (0 1)')) AS ""Distance"" -//FROM ""PointEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", Distance(""p"".""Point"", GeomFromText('POINT (0 1)')) AS ""Distance"" +FROM ""PointEntity"" AS ""p"""); } public override async Task Distance_constant_srid_4326(bool async) { await base.Distance_constant_srid_4326(async); - // isse #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", Distance(""e"".""Point"", GeomFromText('POINT (1 1)', 4326)) AS ""Distance"" -//FROM ""PointEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", Distance(""p"".""Point"", GeomFromText('POINT (1 1)', 4326)) AS ""Distance"" +FROM ""PointEntity"" AS ""p"""); } public override async Task Distance_constant_lhs(bool async) { await base.Distance_constant_lhs(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", Distance(GeomFromText('POINT (0 1)'), ""e"".""Point"") AS ""Distance"" -//FROM ""PointEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", Distance(GeomFromText('POINT (0 1)'), ""p"".""Point"") AS ""Distance"" +FROM ""PointEntity"" AS ""p"""); } public override async Task EndPoint(bool async) @@ -337,14 +382,13 @@ public override async Task EqualsTopologically(bool async) { await base.EqualsTopologically(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x0001000000000000000000000000000000000000000000000000000000000000...' (Size = 60) (DbType = String) + AssertSql( + @"@__point_0='0x0001000000000000000000000000000000000000000000000000000000000000...' (Size = 60) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Point"" IS NOT NULL THEN Equals(""e"".""Point"", @__point_0) -//END AS ""EqualsTopologically"" -//FROM ""PointEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Point"" IS NOT NULL THEN Equals(""p"".""Point"", @__point_0) +END AS ""EqualsTopologically"" +FROM ""PointEntity"" AS ""p"""); } public override async Task ExteriorRing(bool async) @@ -377,33 +421,42 @@ public override async Task GetGeometryN(bool async) { await base.GetGeometryN(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", GeometryN(""e"".""MultiLineString"", 0 + 1) AS ""Geometry0"" -//FROM ""MultiLineStringEntity"" AS ""e"""); + AssertSql( + @"SELECT ""m"".""Id"", GeometryN(""m"".""MultiLineString"", 0 + 1) AS ""Geometry0"" +FROM ""MultiLineStringEntity"" AS ""m"""); + } + + public override async Task GetGeometryN_with_null_argument(bool async) + { + await base.GetGeometryN_with_null_argument(async); + + AssertSql( + @"SELECT ""m0"".""Id"", GeometryN(""m0"".""MultiLineString"", ( + SELECT MAX(""m"".""Id"") + FROM ""MultiLineStringEntity"" AS ""m"" + WHERE 0) + 1) AS ""Geometry0"" +FROM ""MultiLineStringEntity"" AS ""m0"""); } public override async Task GetInteriorRingN(bool async) { await base.GetInteriorRingN(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Polygon"" IS NULL OR (NumInteriorRing(""e"".""Polygon"") = 0) -// THEN NULL ELSE InteriorRingN(""e"".""Polygon"", 0 + 1) -//END AS ""InteriorRing0"" -//FROM ""PolygonEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", CASE + WHEN NumInteriorRing(""p"".""Polygon"") = 0 THEN NULL + ELSE InteriorRingN(""p"".""Polygon"", 0 + 1) +END AS ""InteriorRing0"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task GetPointN(bool async) { await base.GetPointN(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", PointN(""e"".""LineString"", 0 + 1) AS ""Point0"" -//FROM ""LineStringEntity"" AS ""e"""); + AssertSql( + @"SELECT ""l"".""Id"", PointN(""l"".""LineString"", 0 + 1) AS ""Point0"" +FROM ""LineStringEntity"" AS ""l"""); } public override async Task InteriorPoint(bool async) @@ -419,26 +472,24 @@ public override async Task Intersection(bool async) { await base.Intersection(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) + AssertSql( + @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) -//SELECT ""e"".""Id"", Intersection(""e"".""Polygon"", @__polygon_0) AS ""Intersection"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", Intersection(""p"".""Polygon"", @__polygon_0) AS ""Intersection"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task Intersects(bool async) { await base.Intersects(async); - // issue 16050 -// AssertSql( -// @"@__lineString_0='0x000100000000000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 80) (DbType = String) + AssertSql( + @"@__lineString_0='0x000100000000000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 80) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""LineString"" IS NOT NULL THEN Intersects(""e"".""LineString"", @__lineString_0) -//END AS ""Intersects"" -//FROM ""LineStringEntity"" AS ""e"""); +SELECT ""l"".""Id"", CASE + WHEN ""l"".""LineString"" IS NOT NULL THEN Intersects(""l"".""LineString"", @__lineString_0) +END AS ""Intersects"" +FROM ""LineStringEntity"" AS ""l"""); } public override async Task ICurve_IsClosed(bool async) @@ -511,25 +562,20 @@ public override async Task IsWithinDistance(bool async) { await base.IsWithinDistance(async); - // issue #16050 -// AssertSql( -// @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) + AssertSql( + @"@__point_0='0x0001000000000000000000000000000000000000F03F00000000000000000000...' (Size = 60) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN Distance(""e"".""Point"", @__point_0) <= 1.0 -// THEN 1 ELSE 0 -//END AS ""IsWithinDistance"" -//FROM ""PointEntity"" AS ""e"""); +SELECT ""p"".""Id"", Distance(""p"".""Point"", @__point_0) <= 1.0 AS ""IsWithinDistance"" +FROM ""PointEntity"" AS ""p"""); } public override async Task Item(bool async) { await base.Item(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", GeometryN(""e"".""MultiLineString"", 0 + 1) AS ""Item0"" -//FROM ""MultiLineStringEntity"" AS ""e"""); + AssertSql( + @"SELECT ""m"".""Id"", GeometryN(""m"".""MultiLineString"", 0 + 1) AS ""Item0"" +FROM ""MultiLineStringEntity"" AS ""m"""); } public override async Task Length(bool async) @@ -598,14 +644,13 @@ public override async Task Overlaps(bool async) { await base.Overlaps(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) + AssertSql( + @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Polygon"" IS NOT NULL THEN Overlaps(""e"".""Polygon"", @__polygon_0) -//END AS ""Overlaps"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Polygon"" IS NOT NULL THEN Overlaps(""p"".""Polygon"", @__polygon_0) +END AS ""Overlaps"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task PointOnSurface(bool async) @@ -621,24 +666,22 @@ public override async Task Relate(bool async) { await base.Relate(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) + AssertSql( + @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Polygon"" IS NOT NULL THEN Relate(""e"".""Polygon"", @__polygon_0, '212111212') -//END AS ""Relate"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Polygon"" IS NOT NULL THEN Relate(""p"".""Polygon"", @__polygon_0, '212111212') +END AS ""Relate"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task Reverse(bool async) { await base.Reverse(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", ST_Reverse(""e"".""LineString"") AS ""Reverse"" -//FROM ""LineStringEntity"" AS ""e"""); + AssertSql( + @"SELECT ""l"".""Id"", ST_Reverse(""l"".""LineString"") AS ""Reverse"" +FROM ""LineStringEntity"" AS ""l"""); } public override async Task SRID(bool async) @@ -672,82 +715,75 @@ public override async Task SymmetricDifference(bool async) { await base.SymmetricDifference(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) + AssertSql( + @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) -//SELECT ""e"".""Id"", SymDifference(""e"".""Polygon"", @__polygon_0) AS ""SymmetricDifference"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", SymDifference(""p"".""Polygon"", @__polygon_0) AS ""SymmetricDifference"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task ToBinary(bool async) { await base.ToBinary(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", AsBinary(""e"".""Point"") AS ""Binary"" -//FROM ""PointEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", AsBinary(""p"".""Point"") AS ""Binary"" +FROM ""PointEntity"" AS ""p"""); } public override async Task ToText(bool async) { await base.ToText(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", AsText(""e"".""Point"") AS ""Text"" -//FROM ""PointEntity"" AS ""e"""); + AssertSql( + @"SELECT ""p"".""Id"", AsText(""p"".""Point"") AS ""Text"" +FROM ""PointEntity"" AS ""p"""); } public override async Task Touches(bool async) { await base.Touches(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) + AssertSql( + @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Polygon"" IS NOT NULL THEN Touches(""e"".""Polygon"", @__polygon_0) -//END AS ""Touches"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Polygon"" IS NOT NULL THEN Touches(""p"".""Polygon"", @__polygon_0) +END AS ""Touches"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task Union(bool async) { await base.Union(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) + AssertSql( + @"@__polygon_0='0x00010000000000000000000000000000000000000000000000000000F03F0000...' (Size = 116) (DbType = String) -//SELECT ""e"".""Id"", GUnion(""e"".""Polygon"", @__polygon_0) AS ""Union"" -//FROM ""PolygonEntity"" AS ""e"""); +SELECT ""p"".""Id"", GUnion(""p"".""Polygon"", @__polygon_0) AS ""Union"" +FROM ""PolygonEntity"" AS ""p"""); } public override async Task Union_void(bool async) { await base.Union_void(async); - // issue #16050 -// AssertSql( -// @"SELECT ""e"".""Id"", UnaryUnion(""e"".""MultiLineString"") AS ""Union"" -//FROM ""MultiLineStringEntity"" AS ""e"""); + AssertSql( + @"SELECT ""m"".""Id"", UnaryUnion(""m"".""MultiLineString"") AS ""Union"" +FROM ""MultiLineStringEntity"" AS ""m"""); } public override async Task Within(bool async) { await base.Within(async); - // issue #16050 -// AssertSql( -// @"@__polygon_0='0x000100000000000000000000F0BF000000000000F0BF00000000000000400000...' (Size = 132) (DbType = String) + AssertSql( + @"@__polygon_0='0x000100000000000000000000F0BF000000000000F0BF00000000000000400000...' (Size = 132) (DbType = String) -//SELECT ""e"".""Id"", CASE -// WHEN ""e"".""Point"" IS NOT NULL THEN Within(""e"".""Point"", @__polygon_0) -//END AS ""Within"" -//FROM ""PointEntity"" AS ""e"""); +SELECT ""p"".""Id"", CASE + WHEN ""p"".""Point"" IS NOT NULL THEN Within(""p"".""Point"", @__polygon_0) +END AS ""Within"" +FROM ""PointEntity"" AS ""p"""); } public override async Task X(bool async) @@ -777,6 +813,74 @@ public override async Task Z(bool async) FROM ""PointEntity"" AS ""p"""); } + public override async Task IsEmpty_equal_to_null(bool async) + { + await base.IsEmpty_equal_to_null(async); + + AssertSql( + @"SELECT ""p"".""Id"" +FROM ""PointEntity"" AS ""p"" +WHERE CASE + WHEN ""p"".""Point"" IS NOT NULL THEN IsEmpty(""p"".""Point"") +END IS NULL"); + } + + public override async Task IsEmpty_not_equal_to_null(bool async) + { + await base.IsEmpty_not_equal_to_null(async); + + AssertSql( + @"SELECT ""p"".""Id"" +FROM ""PointEntity"" AS ""p"" +WHERE CASE + WHEN ""p"".""Point"" IS NOT NULL THEN IsEmpty(""p"".""Point"") +END IS NOT NULL"); + } + + public override async Task Intersects_equal_to_null(bool async) + { + await base.Intersects_equal_to_null(async); + + AssertSql( + @"@__lineString_0='0x000100000000000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 80) (DbType = String) + +SELECT ""l"".""Id"" +FROM ""LineStringEntity"" AS ""l"" +WHERE CASE + WHEN ""l"".""LineString"" IS NOT NULL THEN Intersects(""l"".""LineString"", @__lineString_0) +END IS NULL", + // + @"@__lineString_0='0x000100000000000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 80) (DbType = String) + +SELECT ""l"".""Id"" +FROM ""LineStringEntity"" AS ""l"" +WHERE CASE + WHEN ""l"".""LineString"" IS NOT NULL THEN Intersects(@__lineString_0, ""l"".""LineString"") +END IS NULL"); + } + + public override async Task Intersects_not_equal_to_null(bool async) + { + await base.Intersects_not_equal_to_null(async); + + AssertSql( + @"@__lineString_0='0x000100000000000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 80) (DbType = String) + +SELECT ""l"".""Id"" +FROM ""LineStringEntity"" AS ""l"" +WHERE CASE + WHEN ""l"".""LineString"" IS NOT NULL THEN Intersects(""l"".""LineString"", @__lineString_0) +END IS NOT NULL", + // + @"@__lineString_0='0x000100000000000000000000E03F000000000000E0BF000000000000E03F0000...' (Size = 80) (DbType = String) + +SELECT ""l"".""Id"" +FROM ""LineStringEntity"" AS ""l"" +WHERE CASE + WHEN ""l"".""LineString"" IS NOT NULL THEN Intersects(@__lineString_0, ""l"".""LineString"") +END IS NOT NULL"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); }