From 46782f0c9c85a92a6f1d715515a5249f72fd4d43 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 23 May 2022 10:58:39 +0300 Subject: [PATCH] Sync EF Core to 7.0.0-preview.5.22260.3 (#2369) * Fix nested set operations with leftmost nulls Fixes #2366 --- Directory.Packages.props | 6 +- NuGet.config | 1 + .../NpgsqlServiceCollectionExtensions.cs | 2 +- .../NpgsqlQueryTranslationPostprocessor.cs | 21 +++ ...sqlQueryTranslationPostprocessorFactory.cs | 26 +++ ...ResolutionCompensatingExpressionVisitor.cs | 152 +++++++++++++++++ .../Internal/NpgsqlRelationalConnection.cs | 1 + .../NpgsqlModificationCommandBatch.cs | 10 +- .../Internal/NpgsqlUpdateSqlGenerator.cs | 6 - .../EFCore.PG.FunctionalTests/BatchingTest.cs | 20 +-- .../ConnectionInterceptionNpgsqlTest.cs | 21 ++- .../NorthwindSetOperationsQueryNpgsqlTest.cs | 154 +++++++++++++++++- .../Query/SimpleQueryNpgsqlTest.cs | 4 + ...TPCFiltersInheritanceQueryNpgsqlFixture.cs | 7 + .../TPCFiltersInheritanceQueryNpgsqlTest.cs | 9 + .../Query/TPCGearsOfWarQueryNpgsqlFixture.cs | 88 ++++++++++ .../Query/TPCGearsOfWarQueryNpgsqlTest.cs | 102 ++++++++++++ .../Query/TPCInheritanceQueryNpgsqlFixture.cs | 9 + .../Query/TPCInheritanceQueryNpgsqlTest.cs | 14 ++ .../TPCManyToManyNoTrackingQueryNpgsqlTest.cs | 14 ++ .../Query/TPCManyToManyQueryNpgsqlFixture.cs | 22 +++ .../Query/TPCManyToManyQueryNpgsqlTest.cs | 14 ++ .../Query/TPCRelationshipsQueryNpgsqlTest.cs | 24 +++ .../FakeRelationalCommandDiagnosticsLogger.cs | 12 ++ ...gsqlModificationCommandBatchFactoryTest.cs | 19 +-- .../NpgsqlModificationCommandBatchTest.cs | 16 +- 26 files changed, 717 insertions(+), 57 deletions(-) create mode 100644 src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs create mode 100644 src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessorFactory.cs create mode 100644 src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlFixture.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlFixture.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCManyToManyNoTrackingQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCManyToManyQueryNpgsqlFixture.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCManyToManyQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/TPCRelationshipsQueryNpgsqlTest.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 679b6278..c795d064 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,8 +1,8 @@ - 7.0.0-preview.4.22229.2 - 7.0.0-preview.4.22229.4 - 7.0.0-preview.4 + 7.0.0-preview.5.22266.3 + 7.0.0-preview.5.22258.4 + 7.0.0-preview.5-ci.20220516T154412 diff --git a/NuGet.config b/NuGet.config index 79b03246..6be8dd11 100644 --- a/NuGet.config +++ b/NuGet.config @@ -9,6 +9,7 @@ + diff --git a/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs b/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs index f5734100..5d0d40b7 100644 --- a/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs +++ b/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs @@ -96,7 +96,6 @@ public static IServiceCollection AddEntityFrameworkNpgsql(this IServiceCollectio .TryAdd() .TryAdd() .TryAdd() - .TryAdd() .TryAdd() .TryAdd() .TryAdd() @@ -111,6 +110,7 @@ public static IServiceCollection AddEntityFrameworkNpgsql(this IServiceCollectio .TryAdd() .TryAdd() .TryAdd() + .TryAdd() .TryAdd() .TryAdd() .TryAdd(p => p.GetRequiredService()) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs new file mode 100644 index 00000000..e0301e91 --- /dev/null +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs @@ -0,0 +1,21 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; + +public class NpgsqlQueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor +{ + public NpgsqlQueryTranslationPostprocessor( + QueryTranslationPostprocessorDependencies dependencies, + RelationalQueryTranslationPostprocessorDependencies relationalDependencies, + QueryCompilationContext queryCompilationContext) + : base(dependencies, relationalDependencies, queryCompilationContext) + { + } + + public override Expression Process(Expression query) + { + var result = base.Process(query); + + result = new NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor().Visit(result); + + return result; + } +} diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessorFactory.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessorFactory.cs new file mode 100644 index 00000000..20f293c3 --- /dev/null +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessorFactory.cs @@ -0,0 +1,26 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; + +public class NpgsqlQueryTranslationPostprocessorFactory : IQueryTranslationPostprocessorFactory +{ + public NpgsqlQueryTranslationPostprocessorFactory( + QueryTranslationPostprocessorDependencies dependencies, + RelationalQueryTranslationPostprocessorDependencies relationalDependencies) + { + Dependencies = dependencies; + RelationalDependencies = relationalDependencies; + } + + /// + /// Dependencies for this service. + /// + protected virtual QueryTranslationPostprocessorDependencies Dependencies { get; } + + /// + /// Relational provider-specific dependencies for this service. + /// + protected virtual RelationalQueryTranslationPostprocessorDependencies RelationalDependencies { get; } + + /// + public virtual QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext) + => new NpgsqlQueryTranslationPostprocessor(Dependencies, RelationalDependencies, queryCompilationContext); +} diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs new file mode 100644 index 00000000..c6ed9fef --- /dev/null +++ b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs @@ -0,0 +1,152 @@ +using Microsoft.EntityFrameworkCore.Query.Internal; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; + +public class NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor : ExpressionVisitor +{ + private State _state; + + protected override Expression VisitExtension(Expression extensionExpression) + => extensionExpression switch + { + ShapedQueryExpression shapedQueryExpression + => shapedQueryExpression.Update( + Visit(shapedQueryExpression.QueryExpression), + Visit(shapedQueryExpression.ShaperExpression)), + SetOperationBase setOperationExpression => VisitSetOperation(setOperationExpression), + SelectExpression selectExpression => VisitSelect(selectExpression), + TpcTablesExpression tpcTablesExpression => VisitTpcTablesExpression(tpcTablesExpression), + _ => base.VisitExtension(extensionExpression) + }; + + private Expression VisitSetOperation(SetOperationBase setOperationExpression) + { + switch (_state) + { + case State.Nothing: + _state = State.InSingleSetOperation; + var visited = base.VisitExtension(setOperationExpression); + _state = State.Nothing; + return visited; + + case State.InSingleSetOperation: + _state = State.InNestedSetOperation; + visited = base.VisitExtension(setOperationExpression); + _state = State.InSingleSetOperation; + return visited; + + default: + return base.VisitExtension(setOperationExpression); + } + } + +#pragma warning disable EF1001 + private Expression VisitTpcTablesExpression(TpcTablesExpression tpcTablesExpression) + { + var parentState = _state; + + if (tpcTablesExpression.SelectExpressions.Count < 3) + { + return base.VisitExtension(tpcTablesExpression); + } + + var changed = false; + var visitedSelectExpressions = new SelectExpression[tpcTablesExpression.SelectExpressions.Count]; + + _state = State.InNestedSetOperation; + visitedSelectExpressions[0] = (SelectExpression)Visit(tpcTablesExpression.SelectExpressions[0]); + changed |= visitedSelectExpressions[0] != tpcTablesExpression.SelectExpressions[0]; + _state = State.AlreadyCompensated; + + for (var i = 1; i < tpcTablesExpression.SelectExpressions.Count; i++) + { + var selectExpression = tpcTablesExpression.SelectExpressions[i]; + var visitedSelectExpression = (SelectExpression)Visit(tpcTablesExpression.SelectExpressions[i]); + visitedSelectExpressions[i] = visitedSelectExpression; + changed |= selectExpression != visitedSelectExpression; + } + + _state = parentState; + + return changed + ? new TpcTablesExpression(tpcTablesExpression.Alias, tpcTablesExpression.EntityType, visitedSelectExpressions) + : tpcTablesExpression; + } +#pragma warning restore EF1001 + + private Expression VisitSelect(SelectExpression selectExpression) + { + var changed = false; + + var tables = new List(); + foreach (var table in selectExpression.Tables) + { + var newTable = (TableExpressionBase)Visit(table); + changed |= newTable != table; + tables.Add(newTable); + } + + // Above we visited the tables, which may contain nested set operations - so we retained our state. + // When visiting the below elements, reset to state to properly handle nested unions inside e.g. the predicate. + var parentState = _state; + _state = State.Nothing; + + var projections = new List(); + foreach (var item in selectExpression.Projection) + { + // Inject an explicit cast node around null literals + var updatedProjection = parentState == State.InNestedSetOperation && item.Expression is SqlConstantExpression { Value : null } + ? item.Update( + new SqlUnaryExpression(ExpressionType.Convert, item.Expression, item.Expression.Type, item.Expression.TypeMapping)) + : (ProjectionExpression)Visit(item); + + projections.Add(updatedProjection); + changed |= updatedProjection != item; + } + + var predicate = (SqlExpression?)Visit(selectExpression.Predicate); + changed |= predicate != selectExpression.Predicate; + + var groupBy = new List(); + foreach (var groupingKey in selectExpression.GroupBy) + { + var newGroupingKey = (SqlExpression)Visit(groupingKey); + changed |= newGroupingKey != groupingKey; + groupBy.Add(newGroupingKey); + } + + var havingExpression = (SqlExpression?)Visit(selectExpression.Having); + changed |= havingExpression != selectExpression.Having; + + var orderings = new List(); + foreach (var ordering in selectExpression.Orderings) + { + var orderingExpression = (SqlExpression)Visit(ordering.Expression); + changed |= orderingExpression != ordering.Expression; + orderings.Add(ordering.Update(orderingExpression)); + } + + var offset = (SqlExpression?)Visit(selectExpression.Offset); + changed |= offset != selectExpression.Offset; + + var limit = (SqlExpression?)Visit(selectExpression.Limit); + changed |= limit != selectExpression.Limit; + + // If we were in the InNestedSetOperation state, we've applied all explicit type mappings when visiting the ProjectionExpressions + // above; change the state to prevent unnecessarily continuing to compensate + _state = parentState == State.InNestedSetOperation ? State.AlreadyCompensated : parentState; + + return changed + ? selectExpression.Update( + projections, tables, predicate, groupBy, havingExpression, orderings, limit, offset) + : selectExpression; + } + + private enum State + { + Nothing, + InSingleSetOperation, + InNestedSetOperation, + AlreadyCompensated + } +} diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs index 2872435a..767c55e3 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs @@ -34,6 +34,7 @@ public NpgsqlRelationalConnection(RelationalConnectionDependencies dependencies) protected override DbConnection CreateDbConnection() { var conn = new NpgsqlConnection(ConnectionString); + if (ProvideClientCertificatesCallback is not null) { conn.ProvideClientCertificatesCallback = ProvideClientCertificatesCallback; diff --git a/src/EFCore.PG/Update/Internal/NpgsqlModificationCommandBatch.cs b/src/EFCore.PG/Update/Internal/NpgsqlModificationCommandBatch.cs index def4fc52..2e167c89 100644 --- a/src/EFCore.PG/Update/Internal/NpgsqlModificationCommandBatch.cs +++ b/src/EFCore.PG/Update/Internal/NpgsqlModificationCommandBatch.cs @@ -86,8 +86,9 @@ protected override void Consume(RelationalDataReader reader) modificationCommand.Entries); } - var valueBufferFactory = CreateValueBufferFactory(modificationCommand.ColumnModifications); - modificationCommand.PropagateResults(valueBufferFactory.Create(npgsqlReader)); + Check.DebugAssert(modificationCommand.RequiresResultPropagation, "RequiresResultPropagation is false"); + + modificationCommand.PropagateResults(reader); npgsqlReader.NextResult(); } @@ -163,8 +164,9 @@ protected override void Consume(RelationalDataReader reader) ); } - var valueBufferFactory = CreateValueBufferFactory(modificationCommand.ColumnModifications); - modificationCommand.PropagateResults(valueBufferFactory.Create(npgsqlReader)); + Check.DebugAssert(modificationCommand.RequiresResultPropagation, "RequiresResultPropagation is false"); + + modificationCommand.PropagateResults(reader); await npgsqlReader.NextResultAsync(cancellationToken).ConfigureAwait(false); } diff --git a/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs b/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs index a14e46ce..1e5e84f6 100644 --- a/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs +++ b/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs @@ -106,10 +106,4 @@ public override void AppendNextSequenceValueOperation(StringBuilder commandStrin SqlGenerationHelper.DelimitIdentifier(commandStringBuilder, Check.NotNull(name, nameof(name)), schema); commandStringBuilder.Append("')"); } - - protected override void AppendIdentityWhereCondition(StringBuilder commandStringBuilder, IColumnModification columnModification) - => throw new NotSupportedException(); - - protected override void AppendRowsAffectedWhereCondition(StringBuilder commandStringBuilder, int expectedRowsAffected) - => throw new NotSupportedException(); } diff --git a/test/EFCore.PG.FunctionalTests/BatchingTest.cs b/test/EFCore.PG.FunctionalTests/BatchingTest.cs index 480cbb9e..159a72b8 100644 --- a/test/EFCore.PG.FunctionalTests/BatchingTest.cs +++ b/test/EFCore.PG.FunctionalTests/BatchingTest.cs @@ -133,8 +133,7 @@ public void Inserts_when_database_type_is_different() } -#if !Test20 - [Theory] + [ConditionalTheory] [InlineData(3)] [InlineData(4)] public void Inserts_are_batched_only_when_necessary(int minBatchSize) @@ -148,13 +147,9 @@ public void Inserts_are_batched_only_when_necessary(int minBatchSize) var owner = new Owner(); context.Owners.Add(owner); - for (var i = 1; i < 4; i++) + for (var i = 1; i < 3; i++) { - var blog = new Blog - { - Id = Guid.NewGuid(), - Owner = owner - }; + var blog = new Blog { Id = Guid.NewGuid(), Owner = owner }; context.Set().Add(blog); expectedBlogs.Add(blog); @@ -166,14 +161,15 @@ public void Inserts_are_batched_only_when_necessary(int minBatchSize) Assert.Contains( minBatchSize == 3 - ? RelationalResources.LogBatchReadyForExecution(new TestLogger()).GenerateMessage(3) - : RelationalResources.LogBatchSmallerThanMinBatchSize(new TestLogger()).GenerateMessage(3, 4), + ? RelationalResources.LogBatchReadyForExecution(new TestLogger()) + .GenerateMessage(3) + : RelationalResources.LogBatchSmallerThanMinBatchSize(new TestLogger()) + .GenerateMessage(3, 4), Fixture.TestSqlLoggerFactory.Log.Select(l => l.Message)); - Assert.Equal(minBatchSize <= 3 ? 2 : 4, Fixture.TestSqlLoggerFactory.SqlStatements.Count); + Assert.Equal(minBatchSize <= 3 ? 1 : 3, Fixture.TestSqlLoggerFactory.SqlStatements.Count); }, context => AssertDatabaseState(context, false, expectedBlogs)); } -#endif private void AssertDatabaseState(DbContext context, bool clientOrder, List expectedBlogs) { diff --git a/test/EFCore.PG.FunctionalTests/ConnectionInterceptionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConnectionInterceptionNpgsqlTest.cs index 4da504ed..8be1781d 100644 --- a/test/EFCore.PG.FunctionalTests/ConnectionInterceptionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConnectionInterceptionNpgsqlTest.cs @@ -11,6 +11,22 @@ protected ConnectionInterceptionNpgsqlTestBase(InterceptionNpgsqlFixtureBase fix { } + [ConditionalTheory(Skip = "#2368")] + public override Task Intercept_connection_creation_passively(bool async) + => base.Intercept_connection_creation_passively(async); + + [ConditionalTheory(Skip = "#2368")] + public override Task Intercept_connection_creation_with_multiple_interceptors(bool async) + => base.Intercept_connection_creation_with_multiple_interceptors(async); + + [ConditionalTheory(Skip = "#2368")] + public override Task Intercept_connection_to_override_connection_after_creation(bool async) + => base.Intercept_connection_to_override_connection_after_creation(async); + + [ConditionalTheory(Skip = "#2368")] + public override Task Intercept_connection_to_override_creation(bool async) + => base.Intercept_connection_to_override_creation(async); + public abstract class InterceptionNpgsqlFixtureBase : InterceptionFixtureBase { protected override string StoreName => "ConnectionInterception"; @@ -22,6 +38,9 @@ public abstract class InterceptionNpgsqlFixtureBase : InterceptionFixtureBase => base.InjectInterceptors(serviceCollection.AddEntityFrameworkNpgsql(), injectedInterceptors); } + protected override DbContextOptionsBuilder ConfigureProvider(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql(); + protected override BadUniverseContext CreateBadUniverse(DbContextOptionsBuilder optionsBuilder) => new(optionsBuilder.UseNpgsql(new FakeDbConnection()).Options); @@ -66,4 +85,4 @@ public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase protected override bool ShouldSubscribeToDiagnosticListener => true; } } -} \ No newline at end of file +} diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindSetOperationsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindSetOperationsQueryNpgsqlTest.cs index 99b90e38..b8d9138c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindSetOperationsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindSetOperationsQueryNpgsqlTest.cs @@ -1,3 +1,5 @@ +using Microsoft.EntityFrameworkCore.TestModels.Northwind; + namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; public class NorthwindSetOperationsQueryNpgsqlTest @@ -9,7 +11,157 @@ public class NorthwindSetOperationsQueryNpgsqlTest : base(fixture) { ClearLog(); - //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + // Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Leftmost_nulls_with_two_unions(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID }) + .Union(ss.Set().Select(o => new { OrderID = (int?)o.OrderID, o.CustomerID }))); + + AssertSql( + @"SELECT NULL AS ""OrderID"", o.""CustomerID"" +FROM ""Orders"" AS o +UNION +SELECT o0.""OrderID"", o0.""CustomerID"" +FROM ""Orders"" AS o0"); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Leftmost_nulls_with_three_unions(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID }) + .Union(ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID })) + .Union(ss.Set().Select(o => new { OrderID = (int?)o.OrderID, o.CustomerID }))); + + AssertSql( + @"SELECT NULL::INT AS ""OrderID"", o.""CustomerID"" +FROM ""Orders"" AS o +UNION +SELECT NULL AS ""OrderID"", o0.""CustomerID"" +FROM ""Orders"" AS o0 +UNION +SELECT o1.""OrderID"", o1.""CustomerID"" +FROM ""Orders"" AS o1"); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Leftmost_nulls_with_four_unions(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID }) + .Union(ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID })) + .Union(ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID })) + .Union(ss.Set().Select(o => new { OrderID = (int?)o.OrderID, o.CustomerID }))); + + AssertSql( + @"SELECT NULL::INT AS ""OrderID"", o.""CustomerID"" +FROM ""Orders"" AS o +UNION +SELECT NULL AS ""OrderID"", o0.""CustomerID"" +FROM ""Orders"" AS o0 +UNION +SELECT NULL AS ""OrderID"", o1.""CustomerID"" +FROM ""Orders"" AS o1 +UNION +SELECT o2.""OrderID"", o2.""CustomerID"" +FROM ""Orders"" AS o2"); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Leftmost_nulls_with_five_unions(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID }) + .Union(ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID })) + .Union(ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID })) + .Union(ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID })) + .Union(ss.Set().Select(o => new { OrderID = (int?)o.OrderID, o.CustomerID }))); + + AssertSql( + @"SELECT NULL::INT AS ""OrderID"", o.""CustomerID"" +FROM ""Orders"" AS o +UNION +SELECT NULL AS ""OrderID"", o0.""CustomerID"" +FROM ""Orders"" AS o0 +UNION +SELECT NULL AS ""OrderID"", o1.""CustomerID"" +FROM ""Orders"" AS o1 +UNION +SELECT NULL AS ""OrderID"", o2.""CustomerID"" +FROM ""Orders"" AS o2 +UNION +SELECT o3.""OrderID"", o3.""CustomerID"" +FROM ""Orders"" AS o3"); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Leftmost_nulls_in_tables_and_predicate(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID }) + .Union(ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID })) + .Union(ss.Set().Select(o => new { OrderID = (int?)o.OrderID, o.CustomerID })) + .Where(o => o.CustomerID == + ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID }) + .Union(ss.Set().Select(o => new { OrderID = (int?)null, o.CustomerID })) + .Union(ss.Set().Select(o => new { OrderID = (int?)o.OrderID, o.CustomerID })) + .OrderBy(o => o.CustomerID) + .First() + .CustomerID)); + + AssertSql( + @"SELECT t0.""OrderID"", t0.""CustomerID"" +FROM ( + SELECT NULL::INT AS ""OrderID"", o.""CustomerID"" + FROM ""Orders"" AS o + UNION + SELECT NULL AS ""OrderID"", o0.""CustomerID"" + FROM ""Orders"" AS o0 + UNION + SELECT o1.""OrderID"", o1.""CustomerID"" + FROM ""Orders"" AS o1 +) AS t0 +WHERE t0.""CustomerID"" = ( + SELECT t1.""CustomerID"" + FROM ( + SELECT NULL::INT AS ""OrderID"", o2.""CustomerID"" + FROM ""Orders"" AS o2 + UNION + SELECT NULL AS ""OrderID"", o3.""CustomerID"" + FROM ""Orders"" AS o3 + UNION + SELECT o4.""OrderID"", o4.""CustomerID"" + FROM ""Orders"" AS o4 + ) AS t1 + ORDER BY t1.""CustomerID"" NULLS FIRST + LIMIT 1) OR ((t0.""CustomerID"" IS NULL) AND (( + SELECT t1.""CustomerID"" + FROM ( + SELECT NULL::INT AS ""OrderID"", o2.""CustomerID"" + FROM ""Orders"" AS o2 + UNION + SELECT NULL AS ""OrderID"", o3.""CustomerID"" + FROM ""Orders"" AS o3 + UNION + SELECT o4.""OrderID"", o4.""CustomerID"" + FROM ""Orders"" AS o4 + ) AS t1 + ORDER BY t1.""CustomerID"" NULLS FIRST + LIMIT 1) IS NULL))"); } public override async Task Client_eval_Union_FirstOrDefault(bool async) diff --git a/test/EFCore.PG.FunctionalTests/Query/SimpleQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SimpleQueryNpgsqlTest.cs index b01ded41..7325e354 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SimpleQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SimpleQueryNpgsqlTest.cs @@ -13,4 +13,8 @@ public override Task SelectMany_where_Select(bool async) // Writes DateTime with Kind=Unspecified to timestamptz public override Task Subquery_first_member_compared_to_null(bool async) => Task.CompletedTask; + + [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/pull/27995/files#r874038747")] + public override Task StoreType_for_UDF_used(bool async) + => base.StoreType_for_UDF_used(async); } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlFixture.cs new file mode 100644 index 00000000..60704d43 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlFixture.cs @@ -0,0 +1,7 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCFiltersInheritanceQueryNpgsqlFixture : TPCInheritanceQueryNpgsqlFixture +{ + protected override bool EnableFilters + => true; +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlTest.cs new file mode 100644 index 00000000..bb782181 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlTest.cs @@ -0,0 +1,9 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCFiltersInheritanceQueryNpgsqlTest : TPCFiltersInheritanceQueryTestBase +{ + public TPCFiltersInheritanceQueryNpgsqlTest(TPCFiltersInheritanceQueryNpgsqlFixture fixture) + : base(fixture) + { + } +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs new file mode 100644 index 00000000..af330e4f --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs @@ -0,0 +1,88 @@ +using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCGearsOfWarQueryNpgsqlFixture : TPCGearsOfWarQueryRelationalFixture +{ + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + modelBuilder.HasPostgresExtension("uuid-ossp"); + + modelBuilder.Entity().Property(c => c.IssueDate).HasColumnType("timestamp without time zone"); + modelBuilder.Entity().Property(g => g.Location).HasColumnType("varchar(100)"); + } + + private GearsOfWarData _expectedData; + + public override ISetSource GetExpectedData() + { + if (_expectedData is null) + { + _expectedData = new GearsOfWarData(); + + // GearsOfWarData contains DateTimeOffsets with various offsets, which we don't support. Change these to UTC. + // Also chop sub-microsecond precision which PostgreSQL does not support. + foreach (var mission in _expectedData.Missions) + { + mission.Timeline = new DateTimeOffset( + mission.Timeline.Ticks - (mission.Timeline.Ticks % (TimeSpan.TicksPerMillisecond / 1000)), TimeSpan.Zero); + } + } + + return _expectedData; + } + + protected override void Seed(GearsOfWarContext context) + { + // GearsOfWarData contains DateTimeOffsets with various offsets, which we don't support. Change these to UTC. + // Also chop sub-microsecond precision which PostgreSQL does not support. + SeedForNpgsql(context); + } + + public static void SeedForNpgsql(GearsOfWarContext context) + { + var squads = GearsOfWarData.CreateSquads(); + var missions = GearsOfWarData.CreateMissions(); + var squadMissions = GearsOfWarData.CreateSquadMissions(); + var cities = GearsOfWarData.CreateCities(); + var weapons = GearsOfWarData.CreateWeapons(); + var tags = GearsOfWarData.CreateTags(); + var gears = GearsOfWarData.CreateGears(); + var locustLeaders = GearsOfWarData.CreateLocustLeaders(); + var factions = GearsOfWarData.CreateFactions(); + var locustHighCommands = GearsOfWarData.CreateHighCommands(); + + foreach (var mission in missions) + { + // var newThing = new DateTimeOffset(orig.Ticks - (orig.Ticks % (TimeSpan.TicksPerMillisecond / 1000)), TimeSpan.Zero); + + mission.Timeline = new DateTimeOffset( + mission.Timeline.Ticks - (mission.Timeline.Ticks % (TimeSpan.TicksPerMillisecond / 1000)), TimeSpan.Zero); + } + + GearsOfWarData.WireUp( + squads, missions, squadMissions, cities, weapons, tags, gears, locustLeaders, factions, locustHighCommands); + + context.Squads.AddRange(squads); + context.Missions.AddRange(missions); + context.SquadMissions.AddRange(squadMissions); + context.Cities.AddRange(cities); + context.Weapons.AddRange(weapons); + context.Tags.AddRange(tags); + context.Gears.AddRange(gears); + context.LocustLeaders.AddRange(locustLeaders); + context.Factions.AddRange(factions); + context.LocustHighCommands.AddRange(locustHighCommands); + context.SaveChanges(); + + GearsOfWarData.WireUp2(locustLeaders, factions); + + context.SaveChanges(); + } +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs new file mode 100644 index 00000000..7c70aff1 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs @@ -0,0 +1,102 @@ +using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCGearsOfWarQueryNpgsqlTest : TPCGearsOfWarQueryRelationalTestBase +{ + public TPCGearsOfWarQueryNpgsqlTest(TPCGearsOfWarQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + // See GearsOfWarQueryNpgsqlTest.Select_null_propagation_negative4 + public override Task Select_null_propagation_negative4(bool async) + => Assert.ThrowsAsync(() => base.Select_null_propagation_negative4(async)); + + [ConditionalTheory(Skip = "https://github.com/npgsql/efcore.pg/issues/2039")] + public override Task Where_DateOnly_Year(bool async) + => base.Where_DateOnly_Year(async); + + // Base implementation uses DateTimeOffset.Now, which we don't translate by design. Use DateTimeOffset.UtcNow instead. + public override async Task Select_datetimeoffset_comparison_in_projection(bool async) + { + await AssertQueryScalar( + async, + ss => ss.Set().Select(m => m.Timeline > DateTimeOffset.UtcNow)); + + AssertSql( + @"SELECT m.""Timeline"" > now() +FROM ""Missions"" AS m"); + } + + public override async Task DateTimeOffset_Contains_Less_than_Greater_than(bool async) + { + var dto = new DateTimeOffset(599898024001234567, new TimeSpan(0)); + var start = dto.AddDays(-1); + var end = dto.AddDays(1); + var dates = new[] { dto }; + + await AssertQuery( + async, + ss => ss.Set().Where( + m => start <= m.Timeline.Date && m.Timeline < end && dates.Contains(m.Timeline))); + + AssertSql( + @"@__start_0='1902-01-01T10:00:00.1234567+00:00' (DbType = DateTime) +@__end_1='1902-01-03T10:00:00.1234567+00:00' (DbType = DateTime) +@__dates_2={ '1902-01-02T10:00:00.1234567+00:00' } (DbType = Object) + +SELECT m.""Id"", m.""CodeName"", m.""Date"", m.""Duration"", m.""Rating"", m.""Time"", m.""Timeline"" +FROM ""Missions"" AS m +WHERE @__start_0 <= date_trunc('day', m.""Timeline"" AT TIME ZONE 'UTC')::timestamptz AND m.""Timeline"" < @__end_1 AND m.""Timeline"" = ANY (@__dates_2)"); + } + + public override async Task DateTimeOffset_Date_returns_datetime(bool async) + { + var dateTimeOffset = new DateTimeOffset(2, 3, 1, 8, 0, 0, new TimeSpan(-5, 0, 0)); + + await AssertQuery( + async, + ss => ss.Set().Where(m => m.Timeline.Date.ToLocalTime() >= dateTimeOffset.Date)); + + AssertSql( + @"@__dateTimeOffset_Date_0='0002-03-01T00:00:00.0000000' + +SELECT m.""Id"", m.""CodeName"", m.""Date"", m.""Duration"", m.""Rating"", m.""Time"", m.""Timeline"" +FROM ""Missions"" AS m +WHERE date_trunc('day', m.""Timeline"" AT TIME ZONE 'UTC')::timestamp >= @__dateTimeOffset_Date_0"); + } + + public override async Task Where_datetimeoffset_date_component(bool async) + { + await AssertQuery( + async, + ss => from m in ss.Set() + where m.Timeline.Date > new DateTime(1, DateTimeKind.Utc) + select m); + + AssertSql( + @"SELECT m.""Id"", m.""CodeName"", m.""Date"", m.""Duration"", m.""Rating"", m.""Time"", m.""Timeline"" +FROM ""Missions"" AS m +WHERE date_trunc('day', m.""Timeline"" AT TIME ZONE 'UTC') > TIMESTAMPTZ '0001-01-01 00:00:00Z'"); + } + + // Not supported by design: we support getting a local DateTime via DateTime.Now (based on PG TimeZone), but there's no way to get a + // non-UTC DateTimeOffset. + public override Task Where_datetimeoffset_now(bool async) + => Task.CompletedTask; + + public override Task Where_datetimeoffset_millisecond_component(bool async) + => Task.CompletedTask; // SQL translation not implemented, too annoying + + // Test runs successfully, but some time difference and precision issues and fail the assertion + public override Task Where_TimeSpan_Hours(bool async) + => Task.CompletedTask; + + public override Task Where_TimeOnly_Millisecond(bool async) + => Task.CompletedTask; // Translation not implemented + + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlFixture.cs new file mode 100644 index 00000000..69bb5d83 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlFixture.cs @@ -0,0 +1,9 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCInheritanceQueryNpgsqlFixture : TPCInheritanceQueryFixture +{ + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlTest.cs new file mode 100644 index 00000000..3b8a670a --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlTest.cs @@ -0,0 +1,14 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCInheritanceQueryNpgsqlTest : TPCInheritanceQueryTestBase +{ + public TPCInheritanceQueryNpgsqlTest(TPCInheritanceQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyNoTrackingQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyNoTrackingQueryNpgsqlTest.cs new file mode 100644 index 00000000..08b0a864 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyNoTrackingQueryNpgsqlTest.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCManyToManyNoTrackingQueryNpgsqlTest : TPCManyToManyNoTrackingQueryRelationalTestBase +{ + public TPCManyToManyNoTrackingQueryNpgsqlTest(TPCManyToManyQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyQueryNpgsqlFixture.cs new file mode 100644 index 00000000..55fd44b1 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyQueryNpgsqlFixture.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCManyToManyQueryNpgsqlFixture : TPCManyToManyQueryRelationalFixture +{ + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + // We default to mapping DateTime to 'timestamp with time zone', but the seeding data has Unspecified DateTimes which aren't + // supported. + modelBuilder.Entity().Property(e => e.Key3).HasColumnType("timestamp without time zone"); + modelBuilder.Entity().Property(e => e.CompositeId3).HasColumnType("timestamp without time zone"); + modelBuilder.Entity().Property(e => e.Payload).HasColumnType("timestamp without time zone"); + modelBuilder.Entity().Property(e => e.CompositeId3).HasColumnType("timestamp without time zone"); + } +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyQueryNpgsqlTest.cs new file mode 100644 index 00000000..6ea49952 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCManyToManyQueryNpgsqlTest.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCManyToManyQueryNpgsqlTest : TPCManyToManyQueryRelationalTestBase +{ + public TPCManyToManyQueryNpgsqlTest(TPCManyToManyQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } +} diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCRelationshipsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCRelationshipsQueryNpgsqlTest.cs new file mode 100644 index 00000000..234c4b03 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/TPCRelationshipsQueryNpgsqlTest.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class TPCRelationshipsQueryNpgsqlTest + : TPCRelationshipsQueryTestBase +{ + public TPCRelationshipsQueryNpgsqlTest( + TPCRelationshipsQueryNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : base(fixture) + { + fixture.TestSqlLoggerFactory.Clear(); + } + + public class TPCRelationshipsQueryNpgsqlFixture : TPCRelationshipsQueryRelationalFixture + { + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + } +} diff --git a/test/EFCore.PG.Tests/TestUtilities/FakeRelationalCommandDiagnosticsLogger.cs b/test/EFCore.PG.Tests/TestUtilities/FakeRelationalCommandDiagnosticsLogger.cs index 00e21261..dca9821d 100644 --- a/test/EFCore.PG.Tests/TestUtilities/FakeRelationalCommandDiagnosticsLogger.cs +++ b/test/EFCore.PG.Tests/TestUtilities/FakeRelationalCommandDiagnosticsLogger.cs @@ -29,6 +29,18 @@ public class FakeRelationalCommandDiagnosticsLogger CommandSource commandSource) => command; + public DbCommand CommandInitialized( + IRelationalConnection connection, + DbCommand command, + DbCommandMethod commandMethod, + DbContext? context, + Guid commandId, + Guid connectionId, + DateTimeOffset startTime, + TimeSpan duration, + CommandSource commandSource) + => command; + public InterceptionResult CommandReaderExecuting( IRelationalConnection connection, DbCommand command, diff --git a/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchFactoryTest.cs b/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchFactoryTest.cs index 546bdccd..5b3b1643 100644 --- a/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchFactoryTest.cs +++ b/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchFactoryTest.cs @@ -37,9 +37,6 @@ public void Uses_MaxBatchSize_specified_in_NpgsqlOptionsExtension() new NpgsqlSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), typeMapper)), - new TypedRelationalValueBufferFactoryFactory( - new RelationalValueBufferFactoryDependencies( - typeMapper, new CoreSingletonOptions())), new CurrentDbContext(new FakeDbContext()), logger), optionsBuilder.Options); @@ -77,9 +74,6 @@ public void MaxBatchSize_is_optional() new NpgsqlSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), typeMapper)), - new TypedRelationalValueBufferFactoryFactory( - new RelationalValueBufferFactoryDependencies( - typeMapper, new CoreSingletonOptions())), new CurrentDbContext(new FakeDbContext()), logger), optionsBuilder.Options); @@ -94,17 +88,10 @@ private class FakeDbContext : DbContext { } - private static IModificationCommand CreateModificationCommand( + private static INonTrackedModificationCommand CreateModificationCommand( string name, string schema, bool sensitiveLoggingEnabled) - { - var modificationCommandParameters = new ModificationCommandParameters( - name, schema, sensitiveLoggingEnabled); - - var modificationCommand = new ModificationCommandFactory().CreateModificationCommand( - modificationCommandParameters); - - return modificationCommand; - } + => new ModificationCommandFactory().CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(name, schema, sensitiveLoggingEnabled)); } diff --git a/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchTest.cs b/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchTest.cs index ee59f3c6..80cd8a27 100644 --- a/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchTest.cs +++ b/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchTest.cs @@ -35,9 +35,6 @@ public void AddCommand_returns_false_when_max_batch_size_is_reached() new NpgsqlSqlGenerationHelper( new RelationalSqlGenerationHelperDependencies()), typeMapper)), - new TypedRelationalValueBufferFactoryFactory( - new RelationalValueBufferFactoryDependencies( - typeMapper, new CoreSingletonOptions())), new CurrentDbContext(new FakeDbContext()), logger), maxBatchSize: 1); @@ -54,17 +51,10 @@ private class FakeDbContext : DbContext { } - private static IModificationCommand CreateModificationCommand( + private static INonTrackedModificationCommand CreateModificationCommand( string name, string schema, bool sensitiveLoggingEnabled) - { - var modificationCommandParameters = new ModificationCommandParameters( - name, schema, sensitiveLoggingEnabled); - - var modificationCommand = new ModificationCommandFactory().CreateModificationCommand( - modificationCommandParameters); - - return modificationCommand; - } + => new ModificationCommandFactory().CreateNonTrackedModificationCommand( + new NonTrackedModificationCommandParameters(name, schema, sensitiveLoggingEnabled)); } -- GitLab