Skip to content

Commit

Permalink
Implement basic ExecuteUpdate (#2467)
Browse files Browse the repository at this point in the history
Sync EF Core to 7.0.0-rc.1.22410.9

Part of #2450
  • Loading branch information
roji committed Aug 11, 2022
1 parent 0f84f7d commit 9313b4d
Show file tree
Hide file tree
Showing 13 changed files with 630 additions and 46 deletions.
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<EFCoreVersion>7.0.0-rc.1.22404.6</EFCoreVersion>
<MicrosoftExtensionsVersion>7.0.0-rc.1.22381.5</MicrosoftExtensionsVersion>
<EFCoreVersion>7.0.0-rc.1.22410.9</EFCoreVersion>
<MicrosoftExtensionsVersion>7.0.0-rc.1.22407.4</MicrosoftExtensionsVersion>
<NpgsqlVersion>7.0.0-preview.7</NpgsqlVersion>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal;
/// Converts the relational <see cref="NonQueryExpression" /> into a PG-specific <see cref="PostgresDeleteExpression" />, which
/// precisely models a DELETE statement in PostgreSQL. This is done to handle the PG-specific USING syntax for table joining.
/// </summary>
public class NonQueryConvertingExpressionVisitor : ExpressionVisitor
public class NpgsqlDeleteConvertingExpressionVisitor : ExpressionVisitor
{
public virtual Expression Process(Expression node)
=> node switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public override Expression Optimize(
{
queryExpression = base.Optimize(queryExpression, parametersValues, out canCache);

queryExpression = new NonQueryConvertingExpressionVisitor().Process(queryExpression);
queryExpression = new NpgsqlDeleteConvertingExpressionVisitor().Process(queryExpression);

return queryExpression;
}
Expand Down
82 changes: 68 additions & 14 deletions src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,20 +207,6 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression binary)
}
}

protected override void GenerateRootCommand(Expression rootExpression)
{
switch (rootExpression)
{
case PostgresDeleteExpression pgDeleteExpression:
VisitPostgresDelete(pgDeleteExpression);
return;

default:
base.GenerateRootCommand(rootExpression);
return;
}
}

// NonQueryConvertingExpressionVisitor converts the relational DeleteExpression to PostgresDeleteExpression, so we should never
// get here
protected override Expression VisitDelete(DeleteExpression deleteExpression)
Expand All @@ -246,6 +232,74 @@ protected virtual Expression VisitPostgresDelete(PostgresDeleteExpression pgDele
return pgDeleteExpression;
}

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 ");
Visit(updateExpression.Table);
Sql.AppendLine();

using (Sql.Indent())
{
Sql.Append("SET ");
GenerateList(updateExpression.SetColumnValues,
e =>
{
Sql
.Append(_sqlGenerationHelper.DelimitIdentifier(e.Column.Name))
.Append(" = ");
Visit(e.Value);
},
joinAction: e => e.AppendLine(","));
}

var first = true;
for (var i = 0; i < selectExpression.Tables.Count; i++)
{
var table = selectExpression.Tables[i];

if (table == updateExpression.Table)
{
continue;
}

if (first)
{
Sql.Append("FROM ");
first = false;
}
else
{
Sql.AppendLine();
}

Visit(table);
}

if (selectExpression.Predicate != null)
{
Sql.AppendLine().Append("WHERE ");
Visit(selectExpression.Predicate);
}

return updateExpression;
}

throw new InvalidOperationException(
RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteUpdate)));
}

protected virtual Expression VisitPostgresNewArray(PostgresNewArrayExpression postgresNewArrayExpression)
{
Debug.Assert(postgresNewArrayExpression.TypeMapping is not null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Npgsql.EntityFrameworkCore.PostgreSQL.Query;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates;

Expand Down Expand Up @@ -67,6 +66,66 @@ public override async Task Delete_where_hierarchy_subquery(bool async)
AssertSql();
}

public override async Task Update_where_hierarchy(bool async)
{
await base.Update_where_hierarchy(async);

AssertExecuteUpdateSql(
@"UPDATE ""Animals"" AS a
SET ""Name"" = 'Animal'
WHERE a.""CountryId"" = 1 AND a.""Name"" = 'Great spotted kiwi'");
}

public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);

AssertExecuteUpdateSql();
}

public override async Task Update_where_hierarchy_derived(bool async)
{
await base.Update_where_hierarchy_derived(async);

AssertExecuteUpdateSql(
@"UPDATE ""Animals"" AS a
SET ""Name"" = 'Kiwi'
WHERE a.""Discriminator"" = 'Kiwi' AND a.""CountryId"" = 1 AND a.""Name"" = 'Great spotted kiwi'");
}

public override async Task Update_where_using_hierarchy(bool async)
{
await base.Update_where_using_hierarchy(async);

AssertExecuteUpdateSql(
@"UPDATE ""Countries"" AS c
SET ""Name"" = 'Monovia'
WHERE (
SELECT count(*)::int
FROM ""Animals"" AS a
WHERE a.""CountryId"" = 1 AND c.""Id"" = a.""CountryId"" AND a.""CountryId"" > 0) > 0");
}

public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);

AssertExecuteUpdateSql(
@"UPDATE ""Countries"" AS c
SET ""Name"" = 'Monovia'
WHERE (
SELECT count(*)::int
FROM ""Animals"" AS a
WHERE a.""CountryId"" = 1 AND c.""Id"" = a.""CountryId"" AND a.""Discriminator"" = 'Kiwi' AND a.""CountryId"" > 0) > 0");
}

public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
{
await base.Update_where_keyless_entity_mapped_to_sql_query(async);

AssertExecuteUpdateSql();
}

[ConditionalFact]
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());
Expand All @@ -75,4 +134,7 @@ public virtual void Check_all_tests_overridden()

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

private void AssertExecuteUpdateSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore.BulkUpdates;
using Microsoft.EntityFrameworkCore.TestModels.InheritanceModel;
using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates;
Expand All @@ -7,4 +8,11 @@ public class InheritanceBulkUpdatesNpgsqlFixture : InheritanceBulkUpdatesRelatio
{
protected override ITestStoreFactory TestStoreFactory
=> NpgsqlTestStoreFactory.Instance;

protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
{
base.OnModelCreating(modelBuilder, context);

modelBuilder.Entity<AnimalQuery>().HasNoKey().ToSqlQuery(@"SELECT * FROM ""Animals""");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,66 @@ public override async Task Delete_where_hierarchy_subquery(bool async)
AssertSql();
}

public override async Task Update_where_hierarchy(bool async)
{
await base.Update_where_hierarchy(async);

AssertExecuteUpdateSql(
@"UPDATE ""Animals"" AS a
SET ""Name"" = 'Animal'
WHERE a.""Name"" = 'Great spotted kiwi'");
}

public override async Task Update_where_hierarchy_subquery(bool async)
{
await base.Update_where_hierarchy_subquery(async);

AssertExecuteUpdateSql();
}

public override async Task Update_where_hierarchy_derived(bool async)
{
await base.Update_where_hierarchy_derived(async);

AssertExecuteUpdateSql(
@"UPDATE ""Animals"" AS a
SET ""Name"" = 'Kiwi'
WHERE a.""Discriminator"" = 'Kiwi' AND a.""Name"" = 'Great spotted kiwi'");
}

public override async Task Update_where_using_hierarchy(bool async)
{
await base.Update_where_using_hierarchy(async);

AssertExecuteUpdateSql(
@"UPDATE ""Countries"" AS c
SET ""Name"" = 'Monovia'
WHERE (
SELECT count(*)::int
FROM ""Animals"" AS a
WHERE c.""Id"" = a.""CountryId"" AND a.""CountryId"" > 0) > 0");
}

public override async Task Update_where_using_hierarchy_derived(bool async)
{
await base.Update_where_using_hierarchy_derived(async);

AssertExecuteUpdateSql(
@"UPDATE ""Countries"" AS c
SET ""Name"" = 'Monovia'
WHERE (
SELECT count(*)::int
FROM ""Animals"" AS a
WHERE c.""Id"" = a.""CountryId"" AND a.""Discriminator"" = 'Kiwi' AND a.""CountryId"" > 0) > 0");
}

public override async Task Update_where_keyless_entity_mapped_to_sql_query(bool async)
{
await base.Update_where_keyless_entity_mapped_to_sql_query(async);

AssertExecuteUpdateSql();
}

[ConditionalFact]
public virtual void Check_all_tests_overridden()
=> TestHelpers.AssertAllMethodsOverridden(GetType());
Expand All @@ -74,4 +134,7 @@ public virtual void Check_all_tests_overridden()

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

private void AssertExecuteUpdateSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true);
}
Loading

0 comments on commit 9313b4d

Please sign in to comment.