From f1f2086f6fee237936b878d223884e5bafc358e5 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Mon, 15 Aug 2022 12:27:27 -0700 Subject: [PATCH] ExecuteUpdate: Allow using other tables in the query to generate result set Part of #795 --- .../Query/Internal/RelationalCommandCache.cs | 1 - .../Query/QuerySqlGenerator.cs | 53 ++++++- ...yableMethodTranslatingExpressionVisitor.cs | 41 ++++-- .../Internal/SqlServerQuerySqlGenerator.cs | 5 +- ...yableMethodTranslatingExpressionVisitor.cs | 46 ++++++ .../NorthwindBulkUpdatesTestBase.cs | 130 +++++++++++++++++ .../NorthwindBulkUpdatesSqlServerTest.cs | 136 +++++++++++++++++ ...FiltersInheritanceBulkUpdatesSqliteTest.cs | 4 - .../InheritanceBulkUpdatesSqliteTest.cs | 4 - .../NorthwindBulkUpdatesSqliteTest.cs | 138 +++++++++++++++++- ...FiltersInheritanceBulkUpdatesSqliteTest.cs | 3 - .../TPCInheritanceBulkUpdatesSqliteTest.cs | 3 - ...FiltersInheritanceBulkUpdatesSqliteTest.cs | 2 - .../TPTInheritanceBulkUpdatesSqliteTest.cs | 2 - 14 files changed, 524 insertions(+), 44 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs index 0606cae494f..d0c18bb5afd 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Concurrent; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.Extensions.Caching.Memory; namespace Microsoft.EntityFrameworkCore.Query.Internal; diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index 5dd0a830c74..6453c48fe7d 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -1237,8 +1237,6 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression) && selectExpression.Having == null && selectExpression.Orderings.Count == 0 && selectExpression.GroupBy.Count == 0 - && selectExpression.Tables.Count == 1 - && selectExpression.Tables[0] == updateExpression.Table && selectExpression.Projection.Count == 0) { _relationalCommandBuilder.Append("UPDATE "); @@ -1255,13 +1253,58 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression) }, joinAction: e => e.AppendLine(",")); - _relationalCommandBuilder.AppendLine(); } - if (selectExpression.Predicate != null) + var predicate = selectExpression.Predicate; + var firstTablePrinted = false; + if (selectExpression.Tables.Count > 1) + { + _relationalCommandBuilder.AppendLine().Append("FROM "); + for (var i = 0; i < selectExpression.Tables.Count; i++) + { + var table = selectExpression.Tables[i]; + var joinExpression = table as JoinExpressionBase; + + if (ReferenceEquals(updateExpression.Table, joinExpression?.Table ?? table)) + { + LiftPredicate(table); + continue; + } + + if (firstTablePrinted) + { + _relationalCommandBuilder.AppendLine(); + } + else + { + firstTablePrinted = true; + LiftPredicate(table); + table = joinExpression?.Table ?? table; + } + + Visit(table); + + void LiftPredicate(TableExpressionBase joinTable) + { + if (joinTable is PredicateJoinExpressionBase predicateJoinExpression) + { + predicate = predicate == null + ? predicateJoinExpression.JoinPredicate + : new SqlBinaryExpression( + ExpressionType.AndAlso, + predicateJoinExpression.JoinPredicate, + predicate, + typeof(bool), + predicate.TypeMapping); + } + } + } + } + + if (predicate != null) { _relationalCommandBuilder.AppendLine().Append("WHERE "); - Visit(selectExpression.Predicate); + Visit(predicate); } return updateExpression; diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 895a6e2e8e9..e4460c53a14 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -1130,6 +1130,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp foreach (var (propertyExpression, valueExpression) in propertyValueLambdaExpressions) { var left = RemapLambdaBody(source, propertyExpression); + left = left.UnwrapTypeConversion(out _); if (!IsValidPropertyAccess(left, out var ese)) { AddTranslationErrorDetails(RelationalStrings.InvalidPropertyInSetProperty(propertyExpression.Print())); @@ -1148,6 +1149,10 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } var right = RemapLambdaBody(source, valueExpression); + if (right.Type != left.Type) + { + right = Expression.Convert(right, left.Type); + } // We generate equality between property = value while translating sothat value infer tye type mapping from property correctly. // Later we decompose it back into left/right components so that the equality is not in the tree which can get affected by // null semantics or other visitor. @@ -1305,7 +1310,7 @@ static bool IsValidPropertyAccess(Expression expression, [NotNullWhen(true)] out /// The select expression to validate. /// The entity shaper expression on which the delete operation is being applied. /// The table expression from which rows are being deleted. - /// das + /// Returns if the current select expression can be used for delete as-is, otherwise. protected virtual bool IsValidSelectExpressionForExecuteDelete( SelectExpression selectExpression, EntityShaperExpression entityShaperExpression, @@ -1330,13 +1335,12 @@ protected virtual bool IsValidSelectExpressionForExecuteDelete( return false; } - // TODO: Update this documentation. /// - /// Validates if the current select expression can be used for execute update operation or it requires to be pushed into a subquery. + /// Validates if the current select expression can be used for execute update operation or it requires to be joined as a subquery. /// /// /// - /// By default, only single-table select expressions are supported, and optionally with a predicate. + /// By default, only muli-table select expressions are supported, and optionally with a predicate. /// /// /// Providers can override this to allow more select expression features to be supported without pushing down into a subquery. @@ -1347,7 +1351,7 @@ protected virtual bool IsValidSelectExpressionForExecuteDelete( /// The select expression to validate. /// The entity shaper expression on which the update operation is being applied. /// The table expression from which rows are being deleted. - /// das + /// Returns if the current select expression can be used for update as-is, otherwise. protected virtual bool IsValidSelectExpressionForExecuteUpdate( SelectExpression selectExpression, EntityShaperExpression entityShaperExpression, @@ -1359,13 +1363,30 @@ protected virtual bool IsValidSelectExpressionForExecuteUpdate( && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null) && selectExpression.GroupBy.Count == 0 && selectExpression.Having == null - && selectExpression.Orderings.Count == 0 - && selectExpression.Tables.Count == 1 - && selectExpression.Tables[0] is TableExpression expression) + && selectExpression.Orderings.Count == 0) { - tableExpression = expression; + TableExpressionBase table; + if (selectExpression.Tables.Count == 1) + { + table = selectExpression.Tables[0]; + } + else + { + var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; + var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression); + var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First()); + table = column.Table; + if (table is JoinExpressionBase joinExpressionBase) + { + table = joinExpressionBase.Table; + } + } - return true; + if (table is TableExpression te) + { + tableExpression = te; + return true; + } } tableExpression = null; diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index 85d8489f975..95b67577259 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -80,15 +80,14 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression) var selectExpression = updateExpression.SelectExpression; if (selectExpression.Offset == null - && selectExpression.Limit == null && selectExpression.Having == null && selectExpression.Orderings.Count == 0 && selectExpression.GroupBy.Count == 0 - && selectExpression.Tables.Count == 1 - && selectExpression.Tables[0] == updateExpression.Table && selectExpression.Projection.Count == 0) { Sql.Append("UPDATE "); + GenerateTop(selectExpression); + Sql.AppendLine($"{Dependencies.SqlGenerationHelper.DelimitIdentifier(updateExpression.Table.Alias)}"); using (Sql.Indent()) { diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs index ab05a121594..53fbc0f3ddc 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs @@ -146,6 +146,52 @@ protected override bool IsValidSelectExpressionForExecuteDelete( return false; } + /// + /// 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. + /// + protected override bool IsValidSelectExpressionForExecuteUpdate( + SelectExpression selectExpression, + EntityShaperExpression entityShaperExpression, + [NotNullWhen(true)] out TableExpression? tableExpression) + { + if (selectExpression.Offset == null + // If entity type has primary key then Distinct is no-op + && (!selectExpression.IsDistinct || entityShaperExpression.EntityType.FindPrimaryKey() != null) + && selectExpression.GroupBy.Count == 0 + && selectExpression.Having == null + && selectExpression.Orderings.Count == 0) + { + TableExpressionBase table; + if (selectExpression.Tables.Count == 1) + { + table = selectExpression.Tables[0]; + } + else + { + var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; + var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression); + var column = entityProjectionExpression.BindProperty(entityShaperExpression.EntityType.GetProperties().First()); + table = column.Table; + if (table is JoinExpressionBase joinExpressionBase) + { + table = joinExpressionBase.Table; + } + } + + if (table is TableExpression te) + { + tableExpression = te; + return true; + } + } + + tableExpression = null; + return false; + } + private sealed class TemporalAnnotationApplyingExpressionVisitor : ExpressionVisitor { private readonly Func _annotationApplyingFunc; diff --git a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs index a68e4cf351b..0533f0bcedc 100644 --- a/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs @@ -343,6 +343,29 @@ public virtual Task Update_where_constant(bool async) rowsAffectedCount: 8, (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Update_where_parameter_in_predicate(bool async) + { + var customer = "ALFKI"; + await AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID == customer), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 1, + (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + + customer = null; + await AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID == customer), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 0, + (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_where_parameter(bool async) @@ -357,6 +380,113 @@ public virtual Task Update_where_parameter(bool async) (b, a) => a.ForEach(c => Assert.Equal("Abc", c.ContactName))); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_take_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Take(4), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 4, + (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_group_by_aggregate_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set() + .Where(c => c.CustomerID == ss.Set() + .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.Key).FirstOrDefault()), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 1, + (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_group_by_first_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set() + .Where(c => c.CustomerID == ss.Set() + .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.First().CustomerID).FirstOrDefault()), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 1, + (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory(Skip = "Issue#26753")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_group_by_first_constant_2(bool async) + => AssertUpdate( + async, + ss => ss.Set() + .Where(c => c == ss.Set() + .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.First().Customer).FirstOrDefault()), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 1, + (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory(Skip = "Issue#28524")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_group_by_first_constant_3(bool async) + => AssertUpdate( + async, + ss => ss.Set() + .Where(c => ss.Set() + .GroupBy(e => e.CustomerID).Where(g => g.Count() > 11).Select(e => e.First().Customer).Contains(c)), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 1, + (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_distinct_constant(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).Distinct(), + e => e, + s => s.SetProperty(c => c.ContactName, c => "Updated"), + rowsAffectedCount: 8, + (b, a) => a.ForEach(c => Assert.Equal("Updated", c.ContactName))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_using_navigation(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(o => o.Customer.City == "Seattle"), + e => e, + s => s.SetProperty(c => c.OrderDate, c => null), + rowsAffectedCount: 14, + (b, a) => a.ForEach(c => Assert.Null(c.OrderDate))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_using_navigation_2(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(od => od.Order.Customer.City == "Seattle"), + e => e, + s => s.SetProperty(c => c.Quantity, c => 1), + rowsAffectedCount: 40, + (b, a) => a.ForEach(c => Assert.Equal(1, c.Quantity))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Update_where_select_many(bool async) + => AssertUpdate( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).SelectMany(c => c.Orders), + e => e, + s => s.SetProperty(c => c.OrderDate, c => null), + rowsAffectedCount: 63, + (b, a) => a.ForEach(c => Assert.Null(c.OrderDate))); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Update_where_using_property_plus_constant(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs index 3893c12c948..b08ada7c255 100644 --- a/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs @@ -513,6 +513,34 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } + public override async Task Update_where_parameter_in_predicate(bool async) + { + await base.Update_where_parameter_in_predicate(async); + + AssertExecuteUpdateSql( + @"@__customer_0='ALFKI' (Size = 5) (DbType = StringFixedLength) + +UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = @__customer_0", + // + @"@__customer_0='ALFKI' (Size = 5) (DbType = StringFixedLength) + +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = @__customer_0", + // + @"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 0 = 1", + // + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE 0 = 1"); + } + public override async Task Update_where_parameter(bool async) { await base.Update_where_parameter(async); @@ -526,6 +554,114 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'F%'"); } + public override async Task Update_where_take_constant(bool async) + { + await base.Update_where_take_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='4' + +UPDATE TOP(@__p_0) [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_where_group_by_aggregate_constant(bool async) + { + await base.Update_where_group_by_aggregate_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = ( + SELECT TOP(1) [o].[CustomerID] + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] + HAVING COUNT(*) > 11)"); + } + + public override async Task Update_where_group_by_first_constant(bool async) + { + await base.Update_where_group_by_first_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE [c].[CustomerID] = ( + SELECT TOP(1) ( + SELECT TOP(1) [o0].[CustomerID] + FROM [Orders] AS [o0] + WHERE [o].[CustomerID] = [o0].[CustomerID] OR ([o].[CustomerID] IS NULL AND [o0].[CustomerID] IS NULL)) + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] + HAVING COUNT(*) > 11)"); + } + + public override async Task Update_where_group_by_first_constant_2(bool async) + { + await base.Update_where_group_by_first_constant_2(async); + + AssertExecuteUpdateSql(); + } + + public override async Task Update_where_group_by_first_constant_3(bool async) + { + await base.Update_where_group_by_first_constant_3(async); + + AssertExecuteUpdateSql(); + } + + public override async Task Update_where_distinct_constant(bool async) + { + await base.Update_where_distinct_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_where_using_navigation(bool async) + { + await base.Update_where_using_navigation(async); + + AssertExecuteUpdateSql( + @"UPDATE [o] + SET [o].[OrderDate] = NULL +FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] +WHERE [c].[City] = N'Seattle'"); + } + + public override async Task Update_where_using_navigation_2(bool async) + { + await base.Update_where_using_navigation_2(async); + + AssertExecuteUpdateSql( + @"UPDATE [o] + SET [o].[Quantity] = CAST(1 AS smallint) +FROM [Order Details] AS [o] +INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] +LEFT JOIN [Customers] AS [c] ON [o0].[CustomerID] = [c].[CustomerID] +WHERE [c].[City] = N'Seattle'"); + } + + public override async Task Update_where_select_many(bool async) + { + await base.Update_where_select_many(async); + + AssertExecuteUpdateSql( + @"UPDATE [o] + SET [o].[OrderDate] = NULL +FROM [Customers] AS [c] +INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + public override async Task Update_where_using_property_plus_constant(bool async) { await base.Update_where_using_property_plus_constant(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs index 6bc4bf8078d..362f5e7b3e1 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/FiltersInheritanceBulkUpdatesSqliteTest.cs @@ -78,7 +78,6 @@ public override async Task Update_where_hierarchy(bool async) AssertExecuteUpdateSql( @"UPDATE ""Animals"" AS ""a"" SET ""Name"" = 'Animal' - WHERE ""a"".""CountryId"" = 1 AND ""a"".""Name"" = 'Great spotted kiwi'"); } @@ -96,7 +95,6 @@ public override async Task Update_where_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Animals"" AS ""a"" SET ""Name"" = 'Kiwi' - WHERE ""a"".""Discriminator"" = 'Kiwi' AND ""a"".""CountryId"" = 1 AND ""a"".""Name"" = 'Great spotted kiwi'"); } @@ -107,7 +105,6 @@ public override async Task Update_where_using_hierarchy(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ""Animals"" AS ""a"" @@ -121,7 +118,6 @@ public override async Task Update_where_using_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ""Animals"" AS ""a"" diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs index c4dd13c0280..15da6f75101 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/InheritanceBulkUpdatesSqliteTest.cs @@ -78,7 +78,6 @@ public override async Task Update_where_hierarchy(bool async) AssertExecuteUpdateSql( @"UPDATE ""Animals"" AS ""a"" SET ""Name"" = 'Animal' - WHERE ""a"".""Name"" = 'Great spotted kiwi'"); } @@ -96,7 +95,6 @@ public override async Task Update_where_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Animals"" AS ""a"" SET ""Name"" = 'Kiwi' - WHERE ""a"".""Discriminator"" = 'Kiwi' AND ""a"".""Name"" = 'Great spotted kiwi'"); } @@ -107,7 +105,6 @@ public override async Task Update_where_using_hierarchy(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ""Animals"" AS ""a"" @@ -121,7 +118,6 @@ public override async Task Update_where_using_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ""Animals"" AS ""a"" diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs index dfff622f6b8..ae8c31f367e 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs @@ -488,10 +488,35 @@ public override async Task Update_where_constant(bool async) AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" SET ""ContactName"" = 'Updated' - WHERE ""c"".""CustomerID"" LIKE 'F%'"); } + public override async Task Update_where_parameter_in_predicate(bool async) + { + await base.Update_where_parameter_in_predicate(async); + + AssertExecuteUpdateSql( + @"@__customer_0='ALFKI' (Size = 5) + +UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +WHERE ""c"".""CustomerID"" = @__customer_0", + // + @"@__customer_0='ALFKI' (Size = 5) + +SELECT ""c"".""CustomerID"", ""c"".""Address"", ""c"".""City"", ""c"".""CompanyName"", ""c"".""ContactName"", ""c"".""ContactTitle"", ""c"".""Country"", ""c"".""Fax"", ""c"".""Phone"", ""c"".""PostalCode"", ""c"".""Region"" +FROM ""Customers"" AS ""c"" +WHERE ""c"".""CustomerID"" = @__customer_0", + // + @"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 0", + // + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +WHERE 0"); + } + public override async Task Update_where_parameter(bool async) { await base.Update_where_parameter(async); @@ -501,10 +526,115 @@ public override async Task Update_where_parameter(bool async) UPDATE ""Customers"" AS ""c"" SET ""ContactName"" = @__value_0 +WHERE ""c"".""CustomerID"" LIKE 'F%'"); + } + + [ConditionalTheory(Skip = "Issue#28661")] + public override async Task Update_where_take_constant(bool async) + { + await base.Update_where_take_constant(async); + + AssertExecuteUpdateSql( + @"@__p_0='4' + +UPDATE TOP(@__p_0) [c] + SET [c].[ContactName] = N'Updated' +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%'"); + } + + public override async Task Update_where_group_by_aggregate_constant(bool async) + { + await base.Update_where_group_by_aggregate_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +WHERE ""c"".""CustomerID"" = ( + SELECT ""o"".""CustomerID"" + FROM ""Orders"" AS ""o"" + GROUP BY ""o"".""CustomerID"" + HAVING COUNT(*) > 11 + LIMIT 1)"); + } + public override async Task Update_where_group_by_first_constant(bool async) + { + await base.Update_where_group_by_first_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' +WHERE ""c"".""CustomerID"" = ( + SELECT ( + SELECT ""o0"".""CustomerID"" + FROM ""Orders"" AS ""o0"" + WHERE ""o"".""CustomerID"" = ""o0"".""CustomerID"" OR (""o"".""CustomerID"" IS NULL AND ""o0"".""CustomerID"" IS NULL) + LIMIT 1) + FROM ""Orders"" AS ""o"" + GROUP BY ""o"".""CustomerID"" + HAVING COUNT(*) > 11 + LIMIT 1)"); + } + + public override async Task Update_where_group_by_first_constant_2(bool async) + { + await base.Update_where_group_by_first_constant_2(async); + + AssertExecuteUpdateSql(); + } + + public override async Task Update_where_group_by_first_constant_3(bool async) + { + await base.Update_where_group_by_first_constant_3(async); + + AssertExecuteUpdateSql(); + } + + public override async Task Update_where_distinct_constant(bool async) + { + await base.Update_where_distinct_constant(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Customers"" AS ""c"" + SET ""ContactName"" = 'Updated' WHERE ""c"".""CustomerID"" LIKE 'F%'"); } + public override async Task Update_where_using_navigation(bool async) + { + await base.Update_where_using_navigation(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Orders"" AS ""o"" + SET ""OrderDate"" = NULL +FROM ""Customers"" AS ""c"" +WHERE ""o"".""CustomerID"" = ""c"".""CustomerID"" AND ""c"".""City"" = 'Seattle'"); + } + + public override async Task Update_where_using_navigation_2(bool async) + { + await base.Update_where_using_navigation_2(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Order Details"" AS ""o"" + SET ""Quantity"" = CAST(1 AS INTEGER) +FROM ""Orders"" AS ""o0"" +LEFT JOIN ""Customers"" AS ""c"" ON ""o0"".""CustomerID"" = ""c"".""CustomerID"" +WHERE ""o"".""OrderID"" = ""o0"".""OrderID"" AND ""c"".""City"" = 'Seattle'"); + } + + public override async Task Update_where_select_many(bool async) + { + await base.Update_where_select_many(async); + + AssertExecuteUpdateSql( + @"UPDATE ""Orders"" AS ""o"" + SET ""OrderDate"" = NULL +FROM ""Customers"" AS ""c"" +WHERE ""c"".""CustomerID"" = ""o"".""CustomerID"" AND (""c"".""CustomerID"" LIKE 'F%')"); + } + public override async Task Update_where_using_property_plus_constant(bool async) { await base.Update_where_using_property_plus_constant(async); @@ -512,7 +642,6 @@ public override async Task Update_where_using_property_plus_constant(bool async) AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" SET ""ContactName"" = COALESCE(""c"".""ContactName"", '') || 'Abc' - WHERE ""c"".""CustomerID"" LIKE 'F%'"); } @@ -525,7 +654,6 @@ public override async Task Update_where_using_property_plus_parameter(bool async UPDATE ""Customers"" AS ""c"" SET ""ContactName"" = COALESCE(""c"".""ContactName"", '') || @__value_0 - WHERE ""c"".""CustomerID"" LIKE 'F%'"); } @@ -536,7 +664,6 @@ public override async Task Update_where_using_property_plus_property(bool async) AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" SET ""ContactName"" = COALESCE(""c"".""ContactName"", '') || ""c"".""CustomerID"" - WHERE ""c"".""CustomerID"" LIKE 'F%'"); } @@ -547,7 +674,6 @@ public override async Task Update_where_constant_using_ef_property(bool async) AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" SET ""ContactName"" = 'Updated' - WHERE ""c"".""CustomerID"" LIKE 'F%'"); } @@ -558,7 +684,6 @@ public override async Task Update_where_null(bool async) AssertExecuteUpdateSql( @"UPDATE ""Customers"" AS ""c"" SET ""ContactName"" = NULL - WHERE ""c"".""CustomerID"" LIKE 'F%'"); } @@ -586,7 +711,6 @@ public override async Task Update_where_multi_property_update(bool async) UPDATE ""Customers"" AS ""c"" SET ""City"" = 'Seattle', ""ContactName"" = @__value_0 - WHERE ""c"".""CustomerID"" LIKE 'F%'"); } diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs index 9a4a9ba6b20..5ff49896ae8 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesSqliteTest.cs @@ -99,7 +99,6 @@ public override async Task Update_where_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Kiwi"" AS ""k"" SET ""Name"" = 'Kiwi' - WHERE ""k"".""CountryId"" = 1 AND ""k"".""Name"" = 'Great spotted kiwi'"); } @@ -110,7 +109,6 @@ public override async Task Update_where_using_hierarchy(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ( @@ -130,7 +128,6 @@ public override async Task Update_where_using_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ( diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs index 0efba9d697c..701c9f8435c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesSqliteTest.cs @@ -99,7 +99,6 @@ public override async Task Update_where_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Kiwi"" AS ""k"" SET ""Name"" = 'Kiwi' - WHERE ""k"".""Name"" = 'Great spotted kiwi'"); } @@ -110,7 +109,6 @@ public override async Task Update_where_using_hierarchy(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ( @@ -130,7 +128,6 @@ public override async Task Update_where_using_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ( diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs index d3ad342b150..162ac651920 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs @@ -97,7 +97,6 @@ public override async Task Update_where_using_hierarchy(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ""Animals"" AS ""a"" @@ -114,7 +113,6 @@ public override async Task Update_where_using_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ""Animals"" AS ""a"" diff --git a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs index e69820f79e0..6c276c2076f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesSqliteTest.cs @@ -85,7 +85,6 @@ public override async Task Update_where_using_hierarchy(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ""Animals"" AS ""a"" @@ -102,7 +101,6 @@ public override async Task Update_where_using_hierarchy_derived(bool async) AssertExecuteUpdateSql( @"UPDATE ""Countries"" AS ""c"" SET ""Name"" = 'Monovia' - WHERE ( SELECT COUNT(*) FROM ""Animals"" AS ""a""