From 0fb62aa1c35032980204395e19e73bfc39b068c4 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 25 May 2024 17:32:21 +0200 Subject: [PATCH 1/2] Compute nullability in `VisitSqlFunction` --- .../Query/SqlNullabilityProcessor.cs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 1a0777b8379..c17c378b096 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -1400,18 +1400,26 @@ protected virtual SqlExpression VisitSqlFunction( return sqlFunctionExpression.Update(sqlFunctionExpression.Instance, coalesceArguments); } - var instance = Visit(sqlFunctionExpression.Instance, out _); - nullable = sqlFunctionExpression.IsNullable; + var useNullabilityPropagation = sqlFunctionExpression is { InstancePropagatesNullability: true }; + + var instance = Visit(sqlFunctionExpression.Instance, out var nullableInstance); + var hasNullableArgument = nullableInstance && sqlFunctionExpression is { InstancePropagatesNullability: true }; if (sqlFunctionExpression.IsNiladic) { - return sqlFunctionExpression.Update(instance, sqlFunctionExpression.Arguments); + sqlFunctionExpression = sqlFunctionExpression.Update(instance, sqlFunctionExpression.Arguments); } - - var arguments = new SqlExpression[sqlFunctionExpression.Arguments.Count]; - for (var i = 0; i < arguments.Length; i++) + else { - arguments[i] = Visit(sqlFunctionExpression.Arguments[i], out _); + var arguments = new SqlExpression[sqlFunctionExpression.Arguments.Count]; + for (var i = 0; i < arguments.Length; i++) + { + arguments[i] = Visit(sqlFunctionExpression.Arguments[i], out var nullableArgument); + useNullabilityPropagation |= sqlFunctionExpression.ArgumentsPropagateNullability[i]; + hasNullableArgument |= nullableArgument && sqlFunctionExpression.ArgumentsPropagateNullability[i]; + } + + sqlFunctionExpression = sqlFunctionExpression.Update(instance, arguments); } if (sqlFunctionExpression.IsBuiltIn @@ -1420,12 +1428,15 @@ protected virtual SqlExpression VisitSqlFunction( nullable = false; return _sqlExpressionFactory.Coalesce( - sqlFunctionExpression.Update(instance, arguments), + sqlFunctionExpression, _sqlExpressionFactory.Constant(0, sqlFunctionExpression.TypeMapping), sqlFunctionExpression.TypeMapping); } - return sqlFunctionExpression.Update(instance, arguments); + // if some of the {Instance,Arguments}PropagateNullability are true, use + // the computed nullability information; otherwise rely only on IsNullable + nullable = sqlFunctionExpression.IsNullable && (!useNullabilityPropagation || hasNullableArgument); + return sqlFunctionExpression; } /// From f24ca355d8a31c6b6e42606cf4d5600e1d6bdde8 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Thu, 23 May 2024 09:46:31 +0200 Subject: [PATCH 2/2] Update tests --- .../Query/FunkyDataQuerySqlServerTest.cs | 2 +- .../Query/GearsOfWarQuerySqlServerTest.cs | 5 +---- .../Query/NorthwindWhereQuerySqlServerTest.cs | 1 - .../Query/TPCGearsOfWarQuerySqlServerTest.cs | 5 +---- .../Query/TPTGearsOfWarQuerySqlServerTest.cs | 5 +---- .../Query/TemporalGearsOfWarQuerySqlServerTest.cs | 5 +---- .../Query/GearsOfWarQuerySqliteTest.cs | 2 +- .../Query/NorthwindWhereQuerySqliteTest.cs | 1 - 8 files changed, 6 insertions(+), 20 deletions(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs index 9b36e499a29..45af4624f1e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FunkyDataQuerySqlServerTest.cs @@ -158,7 +158,7 @@ public override async Task String_contains_on_argument_with_wildcard_column_nega SELECT [f].[FirstName] AS [fn], [f0].[LastName] AS [ln] FROM [FunkyCustomers] AS [f] CROSS JOIN [FunkyCustomers] AS [f0] -WHERE NOT ([f].[FirstName] IS NOT NULL AND [f0].[LastName] IS NOT NULL AND (CHARINDEX([f0].[LastName], [f].[FirstName]) > 0 OR [f0].[LastName] LIKE N'')) +WHERE [f].[FirstName] IS NULL OR [f0].[LastName] IS NULL OR (CHARINDEX([f0].[LastName], [f].[FirstName]) <= 0 AND [f0].[LastName] NOT LIKE N'') """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 6198c915ad7..2cc24ac5c8c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -1162,10 +1162,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) THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(0 AS bit) ELSE NULL END FROM [Gears] AS [g] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 4ec23075327..327a8a3d1e6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -878,7 +878,6 @@ public override async Task Where_datetime_today(bool async) """ SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] FROM [Employees] AS [e] -WHERE CONVERT(date, GETDATE()) = CONVERT(date, GETDATE()) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index fa76dc4a5c6..e8e339ef27f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -1638,10 +1638,7 @@ public override async Task Select_null_propagation_negative6(bool async) AssertSql( """ SELECT CASE - WHEN [u].[LeaderNickname] IS NOT NULL THEN CASE - WHEN CAST(LEN([u].[LeaderNickname]) AS int) <> CAST(LEN([u].[LeaderNickname]) AS int) THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(0 AS bit) ELSE NULL END FROM ( diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 6df09080b56..01391120231 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -1392,10 +1392,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) THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(0 AS bit) ELSE NULL END FROM [Gears] AS [g] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 4804b9024ed..5e9ba7444b6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -1785,10 +1785,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) THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) - END + WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(0 AS bit) ELSE NULL END FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 0ff7f0f8cf9..84c20f2a955 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -3791,7 +3791,7 @@ public override async Task Select_null_propagation_negative6(bool async) AssertSql( """ SELECT CASE - WHEN "g"."LeaderNickname" IS NOT NULL THEN length("g"."LeaderNickname") <> length("g"."LeaderNickname") + WHEN "g"."LeaderNickname" IS NOT NULL THEN 0 ELSE NULL END FROM "Gears" AS "g" diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs index 42befdc62b8..0c225b48f72 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs @@ -82,7 +82,6 @@ public override async Task Where_datetime_today(bool async) """ 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'), '.') """); }