From 060b9c61888739c9dcc141f7e72b303b9bb47435 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 21 Nov 2023 01:51:50 +0100 Subject: [PATCH 001/107] Bump version to 9.0.0-preview.1 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index c6c87c2bc..f21f2831b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 8.0.0 + 9.0.0-preview.1 preview true latest From 840203840c9c13c5c1f5ff8a352d69e35a003219 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 26 Nov 2023 15:23:23 +0100 Subject: [PATCH 002/107] Fix operator precedence of AT TIME ZONE (#2987) Fixes #2980 --- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 2 +- .../Query/OperatorsQueryNpgsqlTest.cs | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 42e9684c3..737813673 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -1485,7 +1485,7 @@ protected override bool TryGetOperatorInfo(SqlExpression expression, out int pre PgBinaryExpression => (1000, false), CollateExpression => (1000, false), - AtTimeZoneExpression => (1000, false), + AtTimeZoneExpression => (1100, false), InExpression => (900, false), PgJsonTraversalExpression => (1000, false), PgArrayIndexExpression => (1500, false), diff --git a/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs index ab04dc9df..3bbbe57d7 100644 --- a/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs @@ -125,6 +125,41 @@ LIMIT 2 """); } + [ConditionalFact] + public virtual async Task AtTimeZone_and_addition() + { + var contextFactory = await InitializeAsync( + seed: context => + { + context.Set().AddRange( + new OperatorEntityDateTime { Id = 1, Value = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, + new OperatorEntityDateTime { Id = 2, Value = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc) }); + context.SaveChanges(); + }, + onModelCreating: modelBuilder => modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever()); + + await using var context = contextFactory.CreateContext(); + + var result = await context.Set() + .Where(b => new DateOnly(2020, 1, 15) > DateOnly.FromDateTime(b.Value.AddDays(1))) + .SingleAsync(); + + Assert.Equal(1, result.Id); + + AssertSql( + """ +SELECT o."Id", o."Value" +FROM "OperatorEntityDateTime" AS o +WHERE DATE '2020-01-15' > CAST((o."Value" + INTERVAL '1 days') AT TIME ZONE 'UTC' AS date) +LIMIT 2 +"""); + } + + public class OperatorEntityDateTime : OperatorEntityBase + { + public DateTime Value { get; set; } + } + protected override void Seed(OperatorsContext ctx) { ctx.Set().AddRange(ExpectedData.OperatorEntitiesString); From 70039b6f6a702f9aff9c732a0491234a519cb581 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 28 Nov 2023 12:59:29 +0100 Subject: [PATCH 003/107] Stop testing on PG11 (out of support) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76cf1f8ea..bd3fe1372 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, windows-2022] - pg_major: [16, 15, 14, 13, 12, 11] + pg_major: [16, 15, 14, 13, 12] config: [Release] include: - os: ubuntu-22.04 From 631b330269e6df1f0b7b35379ef108cc1a271e95 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 28 Nov 2023 13:03:11 +0100 Subject: [PATCH 004/107] Add method postfix when rewriting parameters for StartsWith/EndsWith/Contains (#2995) Fixes #2994 --- .../NpgsqlSqlTranslatingExpressionVisitor.cs | 4 ++- .../Query/CitextQueryTest.cs | 12 ++++----- .../Query/FunkyDataQueryNpgsqlTest.cs | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 672095dab..277c50991 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Net; using System.Runtime.CompilerServices; using System.Text; @@ -450,7 +451,8 @@ when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPref QueryCompilationContext.QueryContextParameter); var escapedPatternParameter = - _queryCompilationContext.RegisterRuntimeParameter(patternParameter.Name + "_rewritten", lambda); + _queryCompilationContext.RegisterRuntimeParameter( + $"{patternParameter.Name}_{methodType.ToString().ToLower(CultureInfo.InvariantCulture)}", lambda); translation = _sqlExpressionFactory.Like( translatedInstance, diff --git a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs index 646ee13c7..088c43ec9 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs @@ -44,11 +44,11 @@ public void StartsWith_param_pattern() Assert.Equal(1, result.Id); AssertSql( """ -@__param_0_rewritten='some%' +@__param_0_startswith='some%' SELECT s."Id", s."CaseInsensitiveText" FROM "SomeEntities" AS s -WHERE s."CaseInsensitiveText" LIKE @__param_0_rewritten ESCAPE '\' +WHERE s."CaseInsensitiveText" LIKE @__param_0_startswith ESCAPE '\' LIMIT 2 """); } @@ -98,11 +98,11 @@ public void EndsWith_param_pattern() Assert.Equal(1, result.Id); AssertSql( """ -@__param_0_rewritten='%sometext' +@__param_0_endswith='%sometext' SELECT s."Id", s."CaseInsensitiveText" FROM "SomeEntities" AS s -WHERE s."CaseInsensitiveText" LIKE @__param_0_rewritten ESCAPE '\' +WHERE s."CaseInsensitiveText" LIKE @__param_0_endswith ESCAPE '\' LIMIT 2 """); } @@ -152,11 +152,11 @@ public void Contains_param_pattern() Assert.Equal(1, result.Id); AssertSql( """ -@__param_0_rewritten='%ometex%' +@__param_0_contains='%ometex%' SELECT s."Id", s."CaseInsensitiveText" FROM "SomeEntities" AS s -WHERE s."CaseInsensitiveText" LIKE @__param_0_rewritten ESCAPE '\' +WHERE s."CaseInsensitiveText" LIKE @__param_0_contains ESCAPE '\' LIMIT 2 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs index 8c843a02d..32d7971dc 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs @@ -35,6 +35,33 @@ await AssertQuery( ss => ss.Set().Where(c => c.FirstName != null && c.FirstName.StartsWith(param))); } + [ConditionalTheory] // TODO: Remove, test was introduced upstream + [MemberData(nameof(IsAsyncData))] + public virtual async Task String_Contains_and_StartsWith_with_same_parameter(bool async) + { + var s = "B"; + + await AssertQuery( + async, + ss => ss.Set().Where( + c => c.FirstName.Contains(s) || c.LastName.StartsWith(s)), + ss => ss.Set().Where( + c => c.FirstName.MaybeScalar(f => f.Contains(s)) == true || c.LastName.MaybeScalar(l => l.StartsWith(s)) == true)); + + AssertSql( + """ +@__s_0_contains='%B%' +@__s_0_startswith='B%' + +SELECT f."Id", f."FirstName", f."LastName", f."NullableBool" +FROM "FunkyCustomers" AS f +WHERE f."FirstName" LIKE @__s_0_contains ESCAPE '\' OR f."LastName" LIKE @__s_0_startswith ESCAPE '\' +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + public class FunkyDataQueryNpgsqlFixture : FunkyDataQueryFixtureBase { private FunkyDataData _expectedData; From 91ccdf67bde982ca56e3e05c7e13bc96800621b1 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 4 Dec 2023 15:41:16 +0100 Subject: [PATCH 005/107] Bring back enumerable Concat/Append translations for ExecuteUpdate (#3005) Fixes #3001 --- .../Internal/NpgsqlArrayMethodTranslator.cs | 44 +++++++++++++++++++ .../NonSharedModelBulkUpdatesNpgsqlTest.cs | 35 +++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs index 834dcd949..2dd0c5db3 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs @@ -1,6 +1,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics; +using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal; @@ -33,6 +34,17 @@ public class NpgsqlArrayMethodTranslator : IMethodCallTranslator private static readonly MethodInfo Enumerable_SequenceEqual = typeof(Enumerable).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) .Single(m => m.Name == nameof(Enumerable.SequenceEqual) && m.GetParameters().Length == 2); + + // TODO: Enumerable Append and Concat are only here because primitive collections aren't handled in ExecuteUpdate, + // https://github.com/dotnet/efcore/issues/32494 + private static readonly MethodInfo Enumerable_Append = + typeof(Enumerable).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) + .Single(m => m.Name == nameof(Enumerable.Append) && m.GetParameters().Length == 2); + + private static readonly MethodInfo Enumerable_Concat = + typeof(Enumerable).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) + .Single(m => m.Name == nameof(Enumerable.Concat) && m.GetParameters().Length == 2); + // ReSharper restore InconsistentNaming #endregion Methods @@ -155,6 +167,38 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) _sqlExpressionFactory.Constant(-1)); } + // TODO: Enumerable Append and Concat are only here because primitive collections aren't handled in ExecuteUpdate, + // https://github.com/dotnet/efcore/issues/32494 + if (method.IsClosedFormOf(Enumerable_Append)) + { + var (item, array) = _sqlExpressionFactory.ApplyTypeMappingsOnItemAndArray(arguments[0], arrayOrList); + + return _sqlExpressionFactory.Function( + "array_append", + new[] { array, item }, + nullable: true, + TrueArrays[2], + arrayOrList.Type, + arrayOrList.TypeMapping); + } + + if (method.IsClosedFormOf(Enumerable_Concat)) + { + var inferredMapping = ExpressionExtensions.InferTypeMapping(arrayOrList, arguments[0]); + + return _sqlExpressionFactory.Function( + "array_cat", + new[] + { + _sqlExpressionFactory.ApplyTypeMapping(arrayOrList, inferredMapping), + _sqlExpressionFactory.ApplyTypeMapping(arguments[0], inferredMapping) + }, + nullable: true, + TrueArrays[2], + arrayOrList.Type, + inferredMapping); + } + return null; } } diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs index 943d6c8f9..5c1bda899 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs @@ -151,6 +151,41 @@ SELECT COALESCE(sum(o0."Amount"), 0)::int """); } + [ConditionalTheory] // #3001 + [MemberData(nameof(IsAsyncData))] + public virtual async Task Update_with_primitive_collection_in_value_selector(bool async) + { + var contextFactory = await InitializeAsync( + seed: ctx => + { + ctx.AddRange(new EntityWithPrimitiveCollection { Tags = new List { "tag1", "tag2" }}); + ctx.SaveChanges(); + }); + + await AssertUpdate( + async, + contextFactory.CreateContext, + ss => ss.EntitiesWithPrimitiveCollection, + s => s.SetProperty(x => x.Tags, x => x.Tags.Append("another_tag")), + rowsAffectedCount: 1); + } + + protected class Context3001 : DbContext + { + public Context3001(DbContextOptions options) + : base(options) + { + } + + public DbSet EntitiesWithPrimitiveCollection { get; set; } + } + + protected class EntityWithPrimitiveCollection + { + public int Id { get; set; } + public List Tags { get; set; } + } + private void AssertSql(params string[] expected) => TestSqlLoggerFactory.AssertBaseline(expected); From 0a795e342bfdce70d771d4d553ab16710b7b9724 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 20:24:20 +0100 Subject: [PATCH 006/107] Bump actions/setup-dotnet from 3 to 4 (#3006) --- .github/workflows/build.yml | 6 +++--- .github/workflows/codeql-analysis.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd3fe1372..c924c449d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@v4 - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.dotnet_sdk_version }} @@ -152,7 +152,7 @@ jobs: uses: actions/checkout@v4 - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.dotnet_sdk_version }} @@ -186,7 +186,7 @@ jobs: uses: actions/checkout@v4 - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.dotnet_sdk_version }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 647d3a05b..7e35ba926 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -60,7 +60,7 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v3.0.3 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.dotnet_sdk_version }} From 1f724d3af01b74d8dac62471de38267dba7b5188 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 11 Dec 2023 12:20:40 +0200 Subject: [PATCH 007/107] Fix containment inference of multirange mapping from non-range item (#3013) Fixes #3012 --- .../Query/NpgsqlSqlExpressionFactory.cs | 2 -- .../Mapping/NpgsqlArrayTypeMapping.cs | 2 +- .../Internal/NpgsqlTypeMappingSource.cs | 20 ++++++++++++++ .../PrimitiveCollectionsQueryNpgsqlTest.cs | 26 +++++++++++++++++++ .../TestUtilities/NpgsqlDatabaseCleaner.cs | 8 +++--- 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 6abd9031d..3fd39ac8c 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -871,8 +871,6 @@ NpgsqlMultirangeTypeMapping multirangeTypeMapping // (e.g. IP address containment) containerMapping = _typeMappingSource.FindContainerMapping(container.Type, containeeMapping, Dependencies.Model); - // containerMapping = _typeMappingSource.FindContainerMapping(container.Type, containeeMapping); - // Apply the inferred mapping to the container, or fall back to the default type mapping if (containerMapping is not null) { diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index 3ae21fcec..b986d9122 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -144,7 +144,7 @@ private static RelationalTypeMappingParameters CreateParameters(string storeType if (elementJsonReaderWriter is not null && elementJsonReaderWriter.ValueType != typeof(TElement).UnwrapNullableType()) { throw new InvalidOperationException( - $"When '{elementJsonReaderWriter.ValueType}', '{typeof(TElement).UnwrapNullableType()}' building an array mapping, the JsonValueReaderWriter for element mapping '{elementMapping.GetType().Name}' is incorrect ('{elementMapping.JsonValueReaderWriter?.GetType().Name ?? ""}')."); + $"When building an array mapping over '{typeof(TElement).Name}', the JsonValueReaderWriter for element mapping '{elementMapping.GetType().Name}' is incorrect ('{elementMapping.JsonValueReaderWriter?.GetType().Name ?? ""}' instead of '{typeof(TElement).UnwrapNullableType()}')."); } // If there's no JsonValueReaderWriter on the element, we also don't set one on its array (this is for rare edge cases such as diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index bfd022647..3c9baa486 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -915,6 +915,26 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) return rangeStoreType is null ? null : FindMapping(containerClrType, rangeStoreType); } + // If this containment of a range within a multirange, just flow down to the general collection mapping logic; multiranges are + // handled just like normal collections over ranges (since they can be unnested). + // However, we also support containment of the base type (e.g. int) directly in its multirange (e.g. int4range). A multirange + // is *not* a collection over the base type, so handle that specific case here. + if (containerClrType.IsMultirange() && !containeeTypeMapping.ClrType.IsRange()) + { + var multirangeStoreType = containeeTypeMapping.StoreType switch + { + "int" or "integer" => "int4multirange", + "bigint" => "int8multirange", + "decimal" or "numeric" => "nummultirange", + "date" => "datemultirange", + "timestamp" or "timestamp without time zone" => "tsmultirange", + "timestamptz" or "timestamp with time zone" => "tstzmultirange", + _ => null + }; + + return !_supportsMultiranges || multirangeStoreType is null ? null : FindMapping(containerClrType, multirangeStoreType); + } + // Then, try to find the mapping with the containee mapping as the element type mapping. // This is the standard EF lookup mechanism, and takes care of regular arrays and multiranges, which are supported as full primitive // collections. diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index ef84acab7..7700cb224 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -347,6 +347,32 @@ public override async Task Parameter_collection_null_Contains(bool async) """); } + [ConditionalTheory] // #3012 + [MinimumPostgresVersion(14, 0)] // Multiranges were introduced in PostgreSQL 14 + [MemberData(nameof(IsAsyncData))] + public virtual async Task Parameter_collection_of_ranges_Contains(bool async) + { + var ranges = new NpgsqlRange[] + { + new(5, 15), + new(40, 50) + }; + + await AssertQuery( + async, + ss => ss.Set().Where(e => ranges.Contains(e.Int)), + ss => ss.Set().Where(c => ranges.Any(p => p.LowerBound <= c.Int && p.UpperBound >= c.Int))); + + AssertSql( + """ +@__ranges_0={ '[5,15]', '[40,50]' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE @__ranges_0 @> p."Int" +"""); + } + public override async Task Column_collection_of_ints_Contains(bool async) { await base.Column_collection_of_ints_Contains(async); diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs index a1805342a..f4f5c2071 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs @@ -139,11 +139,13 @@ private void DropCollations(NpgsqlConnection conn) return; } - const string getUserCollations = @"SELECT nspname, collname + const string getUserCollations = + """ +SELECT nspname, collname FROM pg_collation coll JOIN pg_namespace ns ON ns.oid=coll.collnamespace - JOIN pg_authid auth ON auth.oid = coll.collowner WHERE rolname <> 'postgres'; -"; + JOIN pg_authid auth ON auth.oid = coll.collowner WHERE nspname <> 'pg_catalog'; +"""; (string Schema, string Name)[] userDefinedTypes; using (var cmd = new NpgsqlCommand(getUserCollations, conn)) From eda534a6579c474c8f5b4d6163cb0f08466e8fcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Dec 2023 10:15:12 +0200 Subject: [PATCH 008/107] Bump actions/upload-artifact from 3 to 4 (#3022) --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c924c449d..0dcb5e9e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -160,7 +160,7 @@ jobs: run: dotnet pack --configuration Release --property:PackageOutputPath="$PWD/nupkgs" --version-suffix "ci.$(date -u +%Y%m%dT%H%M%S)+sha.${GITHUB_SHA:0:9}" -p:ContinuousIntegrationBuild=true - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: EFCore.PG.CI path: nupkgs @@ -194,7 +194,7 @@ jobs: run: dotnet pack --configuration Release --property:PackageOutputPath="$PWD/nupkgs" -p:ContinuousIntegrationBuild=true - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: EFCore.PG.Release path: nupkgs From a69ba3e7e8f2691739dff8289749aa002abb2150 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Dec 2023 18:32:36 +0200 Subject: [PATCH 009/107] Bump github/codeql-action from 2 to 3 (#3019) --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7e35ba926..6eed22bfd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -51,7 +51,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -84,4 +84,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From f9e34282aceebce97e12b3b19428a35f1d5f6825 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 16 Dec 2023 20:24:02 +0200 Subject: [PATCH 010/107] Cast NodaTime DateInterval.End to date. (#3024) Fixes #3015 Co-authored-by: Daniel Haas --- .../NpgsqlNodaTimeMemberTranslatorPlugin.cs | 7 +++++-- .../NodaTimeQueryNpgsqlTest.cs | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index 649f9afa0..37bffc7c6 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -251,10 +251,13 @@ SqlExpression Upper() if (member == DateInterval_End) { - return + // PostgreSQL creates a result of type 'timestamp without time zone' when subtracting intervals from dates, so add a cast back + // to date. + return _sqlExpressionFactory.Convert( _sqlExpressionFactory.Subtract( Upper(), - _sqlExpressionFactory.Constant(Period.FromDays(1), _periodTypeMapping)); + _sqlExpressionFactory.Constant(Period.FromDays(1), _periodTypeMapping)), typeof(LocalDate), + _typeMappingSource.FindMapping(typeof(LocalDate))); } if (member == DateInterval_Length) diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs index 6886d312c..16ec9a4cf 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs @@ -1300,7 +1300,22 @@ await AssertQuery( """ SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n -WHERE upper(n."DateInterval") - INTERVAL 'P1D' = DATE '2018-04-24' +WHERE CAST(upper(n."DateInterval") - INTERVAL 'P1D' AS date) = DATE '2018-04-24' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_End_Select(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(t => t.DateInterval.End)); + + AssertSql( + """ +SELECT CAST(upper(n."DateInterval") - INTERVAL 'P1D' AS date) +FROM "NodaTimeTypes" AS n """); } From 6731019c1ea3ea796a9f065db0835c743107e2f2 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 17 Dec 2023 07:23:22 +0200 Subject: [PATCH 011/107] Fix index creation when both collation and operators are specified (#3028) Fixes #3027 --- .../NpgsqlMigrationsSqlGenerator.cs | 19 +++---- .../Migrations/MigrationsNpgsqlTest.cs | 53 +++++++++++++++++-- .../NpgsqlMigrationsSqlGeneratorTest.cs | 20 ------- 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 212680376..35b9504f9 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -2051,20 +2051,24 @@ private string DelimitIdentifier(string name, string? schema) private string IndexColumnList(IndexColumn[] columns, string? method) { - var isFirst = true; var builder = new StringBuilder(); for (var i = 0; i < columns.Length; i++) { - if (!isFirst) + var column = columns[i]; + + if (i > 0) { builder.Append(", "); } - var column = columns[i]; - builder.Append(DelimitIdentifier(column.Name)); + if (!string.IsNullOrEmpty(column.Collation)) + { + builder.Append(" COLLATE ").Append(DelimitIdentifier(column.Collation)); + } + if (!string.IsNullOrEmpty(column.Operator)) { var delimitedOperator = TryParseSchema(column.Operator, out var name, out var schema) @@ -2074,11 +2078,6 @@ private string IndexColumnList(IndexColumn[] columns, string? method) builder.Append(" ").Append(delimitedOperator); } - if (!string.IsNullOrEmpty(column.Collation)) - { - builder.Append(" COLLATE ").Append(DelimitIdentifier(column.Collation)); - } - // Of the built-in access methods, only btree (the default) supports // sorting, thus we only want to emit sort options for btree indexes. if (method is null or "btree") @@ -2105,8 +2104,6 @@ private string IndexColumnList(IndexColumn[] columns, string? method) } } } - - isFirst = false; } return builder.ToString(); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 0ffca7ec7..96e931e30 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2235,9 +2235,56 @@ await Test( @"CREATE INDEX ""IX_People_FirstName_LastName"" ON ""People"" (""FirstName"" text_pattern_ops, ""LastName"");"); } - // Index collation: which collations are available on a given PostgreSQL varies (e.g. Linux vs. Windows), - // so we test support for this on the generated SQL only, in NpgsqlMigrationSqlGeneratorTest, and not against - // the database here. + [Fact] + public virtual async Task Create_index_with_collation() + { + await Test( + builder => + { + builder.Entity("People", e => e.Property("Name")); + builder.HasCollation("some_collation", locale: "POSIX", provider: "libc"); + }, + _ => { }, + builder => builder.Entity("People").HasIndex("Name").UseCollation("some_collation"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Equal("some_collation", Assert.Single((IReadOnlyList)index[RelationalAnnotationNames.Collation]!)); + }); + + AssertSql( + """ +CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation); +"""); + } + + [Fact] // #3027 + public virtual async Task Create_index_with_collation_and_operators() + { + await Test( + builder => + { + builder.Entity("People", e => e.Property("Name")); + builder.HasCollation("some_collation", locale: "POSIX", provider: "libc"); + }, + _ => { }, + builder => builder.Entity("People").HasIndex("Name") + .UseCollation("some_collation") + .HasOperators("text_pattern_ops"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Equal("text_pattern_ops", Assert.Single((IReadOnlyList)index[NpgsqlAnnotationNames.IndexOperators]!)); + Assert.Equal("some_collation", Assert.Single((IReadOnlyList)index[RelationalAnnotationNames.Collation]!)); + }); + + AssertSql( + """ +CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation text_pattern_ops); +"""); + } [Fact] public virtual async Task Create_index_with_null_sort_order() diff --git a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs index cfbff2778..19bb19b2f 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs @@ -473,26 +473,6 @@ public override void Sequence_restart_operation(long? startsAt) : """ALTER SEQUENCE dbo."TestRestartSequenceOperation" RESTART;"""); } - // Which index collations are available on a given PostgreSQL varies (e.g. Linux vs. Windows) - // so we test support for this on the generated SQL only, and not against the database in MigrationsNpgsqlTest. - [Fact] - public void CreateIndexOperation_collation() - { - Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Schema = "dbo", - Columns = new[] { "FirstName", "LastName" }, - [RelationalAnnotationNames.Collation] = new[] { null, "de_DE" } - }); - - AssertSql( - @"CREATE INDEX ""IX_People_Name"" ON dbo.""People"" (""FirstName"", ""LastName"" COLLATE ""de_DE""); -"); - } - [Theory] [InlineData(MigrationsSqlGenerationOptions.Default)] [InlineData(MigrationsSqlGenerationOptions.Idempotent)] From 718b939c827d313a75c7f925d04a2b6c109c3fc3 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 17 Dec 2023 07:28:58 +0200 Subject: [PATCH 012/107] Fix PgTableValuedFunctionExpression cloning (#3026) Fixes #3023 --- .../Internal/PgTableValuedFunctionExpression.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs index 360e06da8..19c153129 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs @@ -21,7 +21,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// -public class PgTableValuedFunctionExpression : TableValuedFunctionExpression, IEquatable +public class PgTableValuedFunctionExpression : TableValuedFunctionExpression, + IEquatable, IClonableTableExpressionBase { /// /// The name of the column to be projected out from the unnest call. @@ -80,6 +81,12 @@ public override PgTableValuedFunctionExpression Update(IReadOnlyList + public TableExpressionBase Clone() + => new PgTableValuedFunctionExpression(Alias, Name, Arguments, ColumnInfos, WithOrdinality); + /// protected override void Print(ExpressionPrinter expressionPrinter) { From adff7ad1ba7f381618c21ac16a92eb5f1bcf5b63 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 18 Dec 2023 12:14:59 +0200 Subject: [PATCH 013/107] Make Npgsql-specific JsonValueReaderWriters public for compiled model (#3029) Fixes #2972 --- .../Mapping/NpgsqlBigIntegerTypeMapping.cs | 26 +++++++++- .../Internal/Mapping/NpgsqlCidrTypeMapping.cs | 26 +++++++++- .../Mapping/NpgsqlDateOnlyTypeMapping.cs | 26 +++++++++- .../Mapping/NpgsqlDateTimeDateTypeMapping.cs | 26 +++++++++- .../Internal/Mapping/NpgsqlInetTypeMapping.cs | 52 ++++++++++++++++++- .../Mapping/NpgsqlIntervalTypeMapping.cs | 26 +++++++++- .../Mapping/NpgsqlLTreeTypeMapping.cs | 26 +++++++++- .../Mapping/NpgsqlPgLsnTypeMapping.cs | 26 +++++++++- .../Mapping/NpgsqlTimeTzTypeMapping.cs | 22 ++++++-- .../Mapping/NpgsqlTimestampTypeMapping.cs | 26 +++++++++- .../Mapping/NpgsqlTimestampTzTypeMapping.cs | 52 ++++++++++++++++++- 11 files changed, 319 insertions(+), 15 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs index 2346b5b1c..11a18a0c2 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs @@ -64,14 +64,38 @@ protected override string ProcessStoreType(RelationalTypeMappingParameters param ? $"numeric({parameters.Precision})" : $"numeric({parameters.Precision},{parameters.Scale})"; - private sealed class JsonBigIntegerReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class JsonBigIntegerReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static JsonBigIntegerReaderWriter Instance { get; } = new(); + /// + /// 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. + /// // Other systems handling the JSON very likely won't support arbitrary-length numbers here, we encode as a string public override BigInteger FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => BigInteger.Parse(manager.CurrentReader.GetString()!); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, BigInteger value) => writer.WriteStringValue(value.ToString()); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs index e9e2d0b84..18b4f3526 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs @@ -83,13 +83,37 @@ public override Expression GenerateCodeLiteral(object value) private static readonly ConstructorInfo NpgsqlCidrConstructor = typeof(NpgsqlCidr).GetConstructor(new[] { typeof(IPAddress), typeof(byte) })!; - private sealed class JsonCidrReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class JsonCidrReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static JsonCidrReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override NpgsqlCidr FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => new(manager.CurrentReader.GetString()!); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlCidr value) => writer.WriteStringValue(value.ToString()); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs index 58015300c..55d181bec 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs @@ -87,10 +87,28 @@ private static string Format(DateOnly date) return date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); } - private sealed class NpgsqlJsonDateOnlyReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class NpgsqlJsonDateOnlyReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static NpgsqlJsonDateOnlyReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override DateOnly FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { var s = manager.CurrentReader.GetString()!; @@ -109,6 +127,12 @@ public override DateOnly FromJsonTyped(ref Utf8JsonReaderManager manager, object return DateOnly.Parse(s, CultureInfo.InvariantCulture); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, DateOnly value) => writer.WriteStringValue(Format(value)); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs index 83095faf0..dba554f11 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs @@ -87,10 +87,28 @@ private static string Format(DateTime date) return date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); } - private sealed class NpgsqlJsonDateTimeReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class NpgsqlJsonDateTimeReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static NpgsqlJsonDateTimeReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { var s = manager.CurrentReader.GetString()!; @@ -109,6 +127,12 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object return DateTime.Parse(s, CultureInfo.InvariantCulture); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(Format(value)); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs index ab88eb5fb..59fb0ba15 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs @@ -85,24 +85,72 @@ public override Expression GenerateCodeLiteral(object value) private static readonly MethodInfo IPAddressParseMethod = typeof(IPAddress).GetMethod("Parse", new[] { typeof(string) })!; private static readonly ConstructorInfo NpgsqlInetConstructor = typeof(NpgsqlInet).GetConstructor(new[] { typeof(string) })!; - private sealed class JsonIPAddressReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class JsonIPAddressReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static JsonIPAddressReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override IPAddress FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => IPAddress.Parse(manager.CurrentReader.GetString()!); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, IPAddress value) => writer.WriteStringValue(value.ToString()); } - private sealed class JsonNpgsqlInetReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class JsonNpgsqlInetReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static JsonNpgsqlInetReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override NpgsqlInet FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => new(manager.CurrentReader.GetString()!); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlInet value) => writer.WriteStringValue(value.ToString()); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs index 23f06d5db..6c4706a7f 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs @@ -155,13 +155,37 @@ public static TimeSpan ParseIntervalAsTimeSpan(ReadOnlySpan s) return timeSpan; } - private sealed class NpgsqlJsonTimeSpanReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class NpgsqlJsonTimeSpanReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static NpgsqlJsonTimeSpanReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override TimeSpan FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => ParseIntervalAsTimeSpan(manager.CurrentReader.GetString()!); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, TimeSpan value) => writer.WriteStringValue(FormatTimeSpanAsInterval(value)); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs index d54529bc0..e6a6c4180 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs @@ -68,13 +68,37 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p public override Expression GenerateCodeLiteral(object value) => Expression.New(Constructor, Expression.Constant((string)(LTree)value, typeof(string))); - private sealed class JsonLTreeReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class JsonLTreeReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static JsonLTreeReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override LTree FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => manager.CurrentReader.GetString()!; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, LTree value) => writer.WriteStringValue(value.ToString()); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs index 64345aca4..4faeb5fb5 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs @@ -83,17 +83,41 @@ public override Expression GenerateCodeLiteral(object value) private static readonly ConstructorInfo Constructor = typeof(NpgsqlLogSequenceNumber).GetConstructor(new[] { typeof(ulong) })!; - private sealed class JsonLogSequenceNumberReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class JsonLogSequenceNumberReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static JsonLogSequenceNumberReaderWriter Instance { get; } = new(); private JsonLogSequenceNumberReaderWriter() { } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override NpgsqlLogSequenceNumber FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => NpgsqlLogSequenceNumber.Parse(manager.CurrentReader.GetString()!); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlLogSequenceNumber value) => writer.WriteStringValue(value.ToString()); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs index 1ed301f1e..fba3bf838 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs @@ -77,7 +77,13 @@ protected override string GenerateNonNullSqlLiteral(object value) protected override string GenerateEmbeddedNonNullSqlLiteral(object value) => FormattableString.Invariant(@$"{(DateTimeOffset)value:HH:mm:ss.FFFFFFz}"); - private sealed class JsonTimeTzReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class JsonTimeTzReaderWriter : JsonValueReaderWriter { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -91,11 +97,21 @@ private JsonTimeTzReaderWriter() { } - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override DateTimeOffset FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => DateTimeOffset.Parse(manager.CurrentReader.GetString()!); - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTimeOffset value) => writer.WriteStringValue(value.ToString("HH:mm:ss.FFFFFFz")); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs index 38ebc6aeb..0977641ba 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs @@ -101,10 +101,28 @@ private static string FormatDateTime(DateTime dateTime) : throw new ArgumentException("'timestamp without time zone' literal cannot be generated for a UTC DateTime"); } - private sealed class NpgsqlJsonTimestampReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class NpgsqlJsonTimestampReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static NpgsqlJsonTimestampReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { var s = manager.CurrentReader.GetString()!; @@ -123,6 +141,12 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object return DateTime.Parse(s, CultureInfo.InvariantCulture); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(FormatDateTime(value)); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs index 8a467a511..8f3a09c20 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs @@ -146,10 +146,28 @@ private static string Format(DateTimeOffset dateTimeOffset) return dateTimeOffset.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFzzz", CultureInfo.InvariantCulture); } - private sealed class NpgsqlJsonTimestampTzDateTimeReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class NpgsqlJsonTimestampTzDateTimeReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static NpgsqlJsonTimestampTzDateTimeReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { var s = manager.CurrentReader.GetString()!; @@ -170,14 +188,38 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object return DateTime.Parse(s, CultureInfo.InvariantCulture).ToUniversalTime(); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(Format(value)); } - private sealed class NpgsqlJsonTimestampTzDateTimeOffsetReaderWriter : JsonValueReaderWriter + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class NpgsqlJsonTimestampTzDateTimeOffsetReaderWriter : JsonValueReaderWriter { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static NpgsqlJsonTimestampTzDateTimeOffsetReaderWriter Instance { get; } = new(); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override DateTimeOffset FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { var s = manager.CurrentReader.GetString()!; @@ -196,6 +238,12 @@ public override DateTimeOffset FromJsonTyped(ref Utf8JsonReaderManager manager, return DateTimeOffset.Parse(s, CultureInfo.InvariantCulture); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTimeOffset value) => writer.WriteStringValue(Format(value)); } From 74790424d7c954c43fd9848aa635bb5bd091cbe3 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 18 Dec 2023 13:47:28 +0200 Subject: [PATCH 014/107] Support NpgsqlUIntTypeMapping for the compiled model (#3030) Fixes #2992 --- .../NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs | 1 + .../Storage/Internal/Mapping/NpgsqlUIntTypeMapping.cs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs index 9d26dfce5..daae6db8b 100644 --- a/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs @@ -49,6 +49,7 @@ public override bool Create( var npgsqlDbTypeBasedDefaultInstance = typeMapping switch { NpgsqlStringTypeMapping => NpgsqlStringTypeMapping.Default, + NpgsqlUIntTypeMapping => NpgsqlUIntTypeMapping.Default, NpgsqlULongTypeMapping => NpgsqlULongTypeMapping.Default, // NpgsqlMultirangeTypeMapping => NpgsqlMultirangeTypeMapping.Default, _ => (INpgsqlTypeMapping?)null diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlUIntTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlUIntTypeMapping.cs index c24064b99..6b6028672 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlUIntTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlUIntTypeMapping.cs @@ -10,6 +10,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; /// public class NpgsqlUIntTypeMapping : NpgsqlTypeMapping { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static NpgsqlUIntTypeMapping Default { get; } = new("xid", NpgsqlDbType.Xid); + /// /// 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 From e9f1ded615f2f0d1a0b8f0d23f337149f21beb5d Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 18 Dec 2023 15:30:47 +0100 Subject: [PATCH 015/107] Update NuGet.config for 9 --- NuGet.config | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/NuGet.config b/NuGet.config index 6ee73d258..834023c22 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,11 +1,9 @@ - @@ -20,11 +18,9 @@ - From 10aebeb9049b266b32aeca84caa90b8d99d815fc Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 19 Dec 2023 10:03:24 +0200 Subject: [PATCH 016/107] Translate DateTime.Date without type mapping in legacy mode (#3032) Fixes #3031 --- .../Internal/NpgsqlDateTimeMemberTranslator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs index a709728ee..d83544d38 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs @@ -67,7 +67,7 @@ public NpgsqlDateTimeMemberTranslator(IRelationalTypeMappingSource typeMappingSo switch (instance) { case { TypeMapping: NpgsqlTimestampTypeMapping }: - case { TypeMapping: NpgsqlTimestampTzTypeMapping } when NpgsqlTypeMappingSource.LegacyTimestampBehavior: + case { } when NpgsqlTypeMappingSource.LegacyTimestampBehavior: return _sqlExpressionFactory.Function( "date_trunc", new[] { _sqlExpressionFactory.Constant("day"), instance }, From 1cb8fb5d6380b1005bdfe3c56908d6fdd1f43cc2 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 23 Dec 2023 17:53:47 +0200 Subject: [PATCH 017/107] Fix null semantics for ILikeExpression (#3039) Fixes #3034 --- .../Internal/NpgsqlSqlNullabilityProcessor.cs | 81 ++++++++++++++++--- .../NorthwindDbFunctionsQueryNpgsqlTest.cs | 15 ++++ 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs index fdb885b22..056e73f0c 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs @@ -457,21 +457,73 @@ PgFunctionExpression VisitPostgresFunctionComponents(PgFunctionExpression pgFunc /// protected virtual SqlExpression VisitILike(PgILikeExpression iLikeExpression, bool allowOptimizedExpansion, out bool nullable) { - Check.NotNull(iLikeExpression, nameof(iLikeExpression)); + // Note: this is largely duplicated from relational SqlNullabilityProcessor.VisitLike. + // We unfortunately can't reuse that since it may return arbitrary expression tree structures with LikeExpression embedded, but + // we need ILikeExpression (see #3034). + var match = Visit(iLikeExpression.Match, out var matchNullable); + var pattern = Visit(iLikeExpression.Pattern, out var patternNullable); + var escapeChar = Visit(iLikeExpression.EscapeChar, out var escapeCharNullable); - var like = new LikeExpression( - iLikeExpression.Match, - iLikeExpression.Pattern, - iLikeExpression.EscapeChar, - iLikeExpression.TypeMapping); + SqlExpression result = iLikeExpression.Update(match, pattern, escapeChar); - var visited = base.VisitLike(like, allowOptimizedExpansion, out nullable); + if (UseRelationalNulls) + { + nullable = matchNullable || patternNullable || escapeCharNullable; + + return result; + } + + nullable = false; + + // The null semantics behavior we implement for LIKE is that it only returns true when both sides are non-null and match; any other + // input returns false: + // foo LIKE f% -> true + // foo LIKE null -> false + // null LIKE f% -> false + // null LIKE null -> false - return visited == like - ? iLikeExpression - : visited is LikeExpression visitedLike - ? iLikeExpression.Update(visitedLike.Match, visitedLike.Pattern, visitedLike.EscapeChar) - : visited; + if (IsNull(match) || IsNull(pattern) || IsNull(escapeChar)) + { + return _sqlExpressionFactory.Constant(false, iLikeExpression.TypeMapping); + } + + // A constant match-all pattern (%) returns true for all cases, except where the match string is null: + // nullable_foo LIKE % -> foo IS NOT NULL + // non_nullable_foo LIKE % -> true + if (pattern is SqlConstantExpression { Value: "%" }) + { + return matchNullable + ? _sqlExpressionFactory.IsNotNull(match) + : _sqlExpressionFactory.Constant(true, iLikeExpression.TypeMapping); + } + + if (!allowOptimizedExpansion) + { + if (matchNullable) + { + result = _sqlExpressionFactory.AndAlso(result, GenerateNotNullCheck(match)); + } + + if (patternNullable) + { + result = _sqlExpressionFactory.AndAlso(result, GenerateNotNullCheck(pattern)); + } + + if (escapeChar is not null && escapeCharNullable) + { + result = _sqlExpressionFactory.AndAlso(result, GenerateNotNullCheck(escapeChar)); + } + + SqlExpression GenerateNotNullCheck(SqlExpression operand) + => OptimizeNonNullableNotExpression( + _sqlExpressionFactory.Not( + VisitSqlUnary( + _sqlExpressionFactory.IsNull(operand), + allowOptimizedExpansion: false, + out _))); + } + + return result; } /// @@ -657,4 +709,9 @@ private static bool MayContainNulls(SqlExpression arrayExpression) return true; } + + // Note that we can check parameter values for null since we cache by the parameter nullability; but we cannot do the same for bool. + private bool IsNull(SqlExpression? expression) + => expression is SqlConstantExpression { Value: null } + || expression is SqlParameterExpression { Name: string parameterName } && ParameterValues[parameterName] is null; } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs index 57e6c9f71..46d03dc14 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs @@ -106,6 +106,21 @@ WHERE c."ContactName" ILIKE '!%' ESCAPE '!' """); } + [Fact] + public void String_ILike_negated() + { + using var context = CreateContext(); + var count = context.Customers.Count(c => !EF.Functions.ILike(c.ContactName, "%M%")); + + Assert.Equal(57, count); + AssertSql( + """ +SELECT count(*)::int +FROM "Customers" AS c +WHERE NOT (c."ContactName" ILIKE '%M%') OR c."ContactName" IS NULL +"""); + } + #endregion #region Collation From 159be1984c2d60eeca3831330d2ba180fa15c943 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 4 Jan 2024 02:35:28 +0100 Subject: [PATCH 018/107] Remove incorrect variable declaration in NpgsqlArrayConverter (#3047) Fixes #2976 --- src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs b/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs index 0cbfea720..a052ef292 100644 --- a/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs +++ b/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs @@ -115,7 +115,6 @@ private static Expression> ArrayConversionExpression Date: Fri, 5 Jan 2024 09:59:55 +0100 Subject: [PATCH 019/107] Fix bad array access when an AFTER trigger raises an exception in SaveChanges (#3049) Fixes #3007 --- .../Update/Internal/NpgsqlModificationCommandBatch.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/EFCore.PG/Update/Internal/NpgsqlModificationCommandBatch.cs b/src/EFCore.PG/Update/Internal/NpgsqlModificationCommandBatch.cs index 09778a6bf..2bc592ff9 100644 --- a/src/EFCore.PG/Update/Internal/NpgsqlModificationCommandBatch.cs +++ b/src/EFCore.PG/Update/Internal/NpgsqlModificationCommandBatch.cs @@ -189,6 +189,16 @@ await ThrowAggregateUpdateConcurrencyExceptionAsync(reader, commandIndex, 1, 0, } catch (Exception ex) when (ex is not DbUpdateException and not OperationCanceledException) { + // If the commandIndex points after the last command, attribute the error to the last command. + // This can happen when an AFTER INSERT trigger raises an exception - the insertion itself is successful, and the error comes + // afterwards, as if belonging to the next command. When there's indeed a next command, there's no way to know whether the + // error indeed belongs to it or comes from a trigger on the previous (we assume the former), but when we're the last command, + // at least avoid indexing beyond the end of the array. See #3007. + if (commandIndex == ModificationCommands.Count) + { + commandIndex--; + } + throw new DbUpdateException( RelationalStrings.UpdateStoreException, ex, From 5c744a82fe55111d4d90f67fbf4ccf31c2ac7843 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 6 Jan 2024 07:39:33 +0100 Subject: [PATCH 020/107] Automatic conversion to primary constructors (#3048) --- Directory.Build.props | 2 +- .../NpgsqlNetTopologySuiteOptionsExtension.cs | 7 +- .../NpgsqlNodaTimeOptionsExtension.cs | 7 +- .../Storage/Internal/DateTimeZoneMapping.cs | 24 +--- .../Internal/NpgsqlOptionsExtension.cs | 7 +- .../NpgsqlMigrationsSqlGenerator.cs | 21 +--- .../NpgsqlCompiledQueryCacheKeyGenerator.cs | 16 +-- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 10 +- ...yableMethodTranslatingExpressionVisitor.cs | 10 +- .../Internal/NpgsqlDatabaseModelFactory.cs | 9 +- .../Mapping/NpgsqlHstoreTypeMapping.cs | 13 +- src/Shared/CodeAnnotations.cs | 85 ++++---------- .../EFCore.PG.FunctionalTests/BatchingTest.cs | 16 +-- .../ComplexTypeBulkUpdatesNpgsqlTest.cs | 11 +- .../NonSharedModelBulkUpdatesNpgsqlTest.cs | 7 +- .../NorthwindBulkUpdatesNpgsqlTest.cs | 12 +- ...FiltersInheritanceBulkUpdatesNpgsqlTest.cs | 13 +- .../TPCInheritanceBulkUpdatesNpgsqlTest.cs | 13 +- ...FiltersInheritanceBulkUpdatesNpgsqlTest.cs | 13 +- .../TPTInheritanceBulkUpdatesNpgsqlTest.cs | 12 +- .../CommandInterceptionNpgsqlTest.cs | 27 ++--- .../CompositeKeyEndToEndTest.cs | 8 +- .../ComputedColumnTest.cs | 34 ++---- .../ConferencePlannerNpgsqlTest.cs | 8 +- .../ConnectionInterceptionNpgsqlTest.cs | 28 ++--- .../ConnectionSpecificationTest.cs | 56 ++------- .../CustomConvertersNpgsqlTest.cs | 8 +- .../DataAnnotationNpgsqlTest.cs | 8 +- .../DataBindingNpgsqlTest.cs | 8 +- .../DefaultValuesTest.cs | 12 +- .../DesignTimeNpgsqlTest.cs | 8 +- .../EntitySplittingNpgsqlTest.cs | 7 +- .../ExecutionStrategyTest.cs | 7 +- .../ExistingConnectionTest.cs | 12 +- .../F1NpgsqlFixture.cs | 14 +-- .../FieldMappingNpgsqlTest.cs | 9 +- .../FindNpgsqlTest.cs | 21 +--- .../KeysWithConvertersNpgsqlTest.cs | 10 +- .../ManyToManyFieldsLoadNpgsqlTest.cs | 9 +- .../ManyToManyLoadNpgsqlTest.cs | 8 +- .../ManyToManyTrackingNpgsqlTest.cs | 10 +- .../MaterializationInterceptionNpgsqlTest.cs | 18 +-- .../MigrationsInfrastructureNpgsqlTest.cs | 16 +-- .../NpgsqlMigrationsSqlGeneratorTest.cs | 19 ++- .../MonsterFixupChangedChangingNpgsqlTest.cs | 9 +- .../MusicStoreNpgsqlTest.cs | 8 +- .../NotificationEntitiesNpgsqlTest.cs | 9 +- .../NpgsqlApiConsistencyTest.cs | 8 +- .../NpgsqlDatabaseCreatorTest.cs | 33 ++---- .../NpgsqlServiceCollectionExtensionsTest.cs | 8 +- .../NpgsqlValueGenerationScenariosTest.cs | 111 +++--------------- .../OptimisticConcurrencyNpgsqlTest.cs | 26 +--- .../OverzealousInitializationNpgsqlTest.cs | 9 +- .../PropertyValuesNpgsqlTest.cs | 8 +- .../Query/ArrayArrayQueryTest.cs | 8 +- .../Query/BigIntegerQueryTest.cs | 14 +-- .../Query/CitextQueryTest.cs | 7 +- .../Query/CompatibilityQueryNpgsqlTest.cs | 7 +- .../Query/EnumQueryTest.cs | 7 +- .../Query/FieldsOnlyLoadNpgsqlTest.cs | 8 +- .../Query/FromSqlQueryNpgsqlTest.cs | 8 +- ...InheritanceRelationshipsQueryNpgsqlTest.cs | 8 +- .../Query/JsonDomQueryTest.cs | 7 +- .../Query/JsonPocoQueryTest.cs | 7 +- .../Query/JsonQueryAdHocNpgsqlTest.cs | 15 +-- .../Query/JsonStringQueryTest.cs | 7 +- .../Query/LTreeQueryTest.cs | 7 +- .../Query/LegacyTimestampQueryTest.cs | 11 +- .../Query/MultirangeQueryNpgsqlTest.cs | 7 +- .../Query/NavigationTest.cs | 20 +--- .../Query/NullKeysNpgsqlTest.cs | 8 +- .../Query/OwnedQueryNpgsqlTest.cs | 8 +- .../Query/QueryBugTest.cs | 7 +- .../Query/QueryNoClientEvalNpgsqlFixture.cs | 4 +- .../Query/QueryNoClientEvalNpgsqlTest.cs | 9 +- .../Query/RangeQueryNpgsqlTest.cs | 7 +- .../Query/SqlExecutorNpgsqlTest.cs | 8 +- .../TPCFiltersInheritanceQueryNpgsqlTest.cs | 9 +- .../Query/TPCInheritanceQueryNpgsqlTest.cs | 8 +- .../Query/TPHInheritanceQueryNpgsqlTest.cs | 9 +- .../Query/TPTInheritanceQueryNpgsqlTest.cs | 10 +- .../Query/TimestampQueryTest.cs | 14 +-- .../Query/UdfDbFunctionNpgsqlTests.cs | 7 +- .../Query/WarningsNpgsqlTest.cs | 8 +- ...eryExpressionInterceptionNpgsqlTestBase.cs | 28 ++--- .../SaveChangesInterceptionNpgsqlTest.cs | 27 ++--- .../SeedingNpgsqlTest.cs | 7 +- .../SequenceEndToEndTest.cs | 42 ++----- .../SerializationNpgsqlTest.cs | 8 +- .../SpatialNpgsqlTest.cs | 7 +- .../StoreGeneratedFixupNpgsqlTest.cs | 9 +- .../StoreGeneratedNpgsqlTest.cs | 9 +- .../SystemColumnTest.cs | 7 +- .../TPTTableSplittingNpgsqlTest.cs | 7 +- .../TableSplittingNpgsqlTest.cs | 7 +- .../TestModels/Array/ArrayQueryContext.cs | 7 +- .../TestModels/Array/ArrayQueryData.cs | 9 +- .../Northwind/NorthwindNpgsqlContext.cs | 7 +- .../MinimumPostgresVersionAttribute.cs | 9 +- .../TestUtilities/NpgsqlDatabaseCleaner.cs | 7 +- .../TestUtilities/TestNpgsqlConnection.cs | 8 +- .../TestRelationalCommandBuilderFactory.cs | 36 ++---- .../TestRelationalTransaction.cs | 30 ++--- .../TransactionInterceptionNpgsqlTest.cs | 28 ++--- .../TransactionNpgsqlTest.cs | 8 +- .../TwoDatabasesNpgsqlTest.cs | 7 +- .../ValueConvertersEndToEndNpgsqlTest.cs | 18 +-- .../WithConstructorsNpgsqlTest.cs | 8 +- .../LegacyNpgsqlNodaTimeTypeMappingTest.cs | 4 +- .../NodaTimeQueryNpgsqlTest.cs | 14 +-- .../NpgsqlMetadataBuilderExtensionsTest.cs | 4 +- .../Migrations/NpgsqlHistoryRepositoryTest.cs | 7 +- .../NpgsqlDatabaseFacadeTest.cs | 14 +-- .../NpgsqlDbContextOptionsExtensionsTest.cs | 8 +- ...pgsqlNetTopologySuiteApiConsistencyTest.cs | 10 +- .../NpgsqlNodaTimeApiConsistencyTest.cs | 8 +- .../Storage/LegacyNpgsqlTypeMappingTest.cs | 4 +- .../Storage/NpgsqlTypeMappingSourceTest.cs | 8 +- .../FakeRelationalOptionsExtension.cs | 7 +- ...gsqlModificationCommandBatchFactoryTest.cs | 4 +- .../NpgsqlModificationCommandBatchTest.cs | 4 +- 121 files changed, 360 insertions(+), 1242 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f21f2831b..c27355bf6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ 9.0.0-preview.1 - preview + latest true latest NU5105 diff --git a/src/EFCore.PG.NTS/Infrastructure/Internal/NpgsqlNetTopologySuiteOptionsExtension.cs b/src/EFCore.PG.NTS/Infrastructure/Internal/NpgsqlNetTopologySuiteOptionsExtension.cs index 9de048388..3d0205650 100644 --- a/src/EFCore.PG.NTS/Infrastructure/Internal/NpgsqlNetTopologySuiteOptionsExtension.cs +++ b/src/EFCore.PG.NTS/Infrastructure/Internal/NpgsqlNetTopologySuiteOptionsExtension.cs @@ -109,15 +109,10 @@ public virtual void Validate(IDbContextOptions options) } } - private sealed class ExtensionInfo : DbContextOptionsExtensionInfo + private sealed class ExtensionInfo(IDbContextOptionsExtension extension) : DbContextOptionsExtensionInfo(extension) { private string? _logFragment; - public ExtensionInfo(IDbContextOptionsExtension extension) - : base(extension) - { - } - private new NpgsqlNetTopologySuiteOptionsExtension Extension => (NpgsqlNetTopologySuiteOptionsExtension)base.Extension; diff --git a/src/EFCore.PG.NodaTime/Infrastructure/Internal/NpgsqlNodaTimeOptionsExtension.cs b/src/EFCore.PG.NodaTime/Infrastructure/Internal/NpgsqlNodaTimeOptionsExtension.cs index 732411214..0c070cf11 100644 --- a/src/EFCore.PG.NodaTime/Infrastructure/Internal/NpgsqlNodaTimeOptionsExtension.cs +++ b/src/EFCore.PG.NodaTime/Infrastructure/Internal/NpgsqlNodaTimeOptionsExtension.cs @@ -55,13 +55,8 @@ public virtual void Validate(IDbContextOptions options) } } - private sealed class ExtensionInfo : DbContextOptionsExtensionInfo + private sealed class ExtensionInfo(IDbContextOptionsExtension extension) : DbContextOptionsExtensionInfo(extension) { - public ExtensionInfo(IDbContextOptionsExtension extension) - : base(extension) - { - } - private new NpgsqlNodaTimeOptionsExtension Extension => (NpgsqlNodaTimeOptionsExtension)base.Extension; diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateTimeZoneMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateTimeZoneMapping.cs index 6dc203ffd..0959d6ef9 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateTimeZoneMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateTimeZoneMapping.cs @@ -55,23 +55,11 @@ public override Expression GenerateCodeLiteral(object value) typeof(IDateTimeZoneProvider).GetMethod(nameof(IDateTimeZoneProvider.GetZoneOrNull), new[] { typeof(string) })!, Expression.Constant(((DateTimeZone)value).Id)); - private sealed class DateTimeZoneConverter : ValueConverter - { - public DateTimeZoneConverter() - : base( - tz => tz.Id, - id => DateTimeZoneProviders.Tzdb[id]) - { - } - } + private sealed class DateTimeZoneConverter() : ValueConverter( + tz => tz.Id, + id => DateTimeZoneProviders.Tzdb[id]); - private sealed class DateTimeZoneComparer : ValueComparer - { - public DateTimeZoneComparer() - : base( - (tz1, tz2) => tz1 == null ? tz2 == null : tz2 != null && tz1.Id == tz2.Id, - tz => tz.GetHashCode()) - { - } - } + private sealed class DateTimeZoneComparer() : ValueComparer( + (tz1, tz2) => tz1 == null ? tz2 == null : tz2 != null && tz1.Id == tz2.Id, + tz => tz.GetHashCode()); } diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index 7ba3f5fec..0d4fd224e 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -333,16 +333,11 @@ public override void ApplyServices(IServiceCollection services) public override DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); - private sealed class ExtensionInfo : RelationalExtensionInfo + private sealed class ExtensionInfo(IDbContextOptionsExtension extension) : RelationalExtensionInfo(extension) { private int? _serviceProviderHash; private string? _logFragment; - public ExtensionInfo(IDbContextOptionsExtension extension) - : base(extension) - { - } - private new NpgsqlOptionsExtension Extension => (NpgsqlOptionsExtension)base.Extension; diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 35b9504f9..2442078b9 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -2217,22 +2217,13 @@ private static IndexColumn[] GetIndexColumns(CreateIndexOperation operation) return columns; } - private readonly struct IndexColumn + private readonly struct IndexColumn(string name, string? @operator, string? collation, bool isDescending, NullSortOrder nullSortOrder) { - public IndexColumn(string name, string? @operator, string? collation, bool isDescending, NullSortOrder nullSortOrder) - { - Name = name; - Operator = @operator; - Collation = collation; - IsDescending = isDescending; - NullSortOrder = nullSortOrder; - } - - public string Name { get; } - public string? Operator { get; } - public string? Collation { get; } - public bool IsDescending { get; } - public NullSortOrder NullSortOrder { get; } + public string Name { get; } = name; + public string? Operator { get; } = @operator; + public string? Collation { get; } = collation; + public bool IsDescending { get; } = isDescending; + public NullSortOrder NullSortOrder { get; } = nullSortOrder; } #endregion diff --git a/src/EFCore.PG/Query/Internal/NpgsqlCompiledQueryCacheKeyGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlCompiledQueryCacheKeyGenerator.cs index c8d826357..2b5731698 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlCompiledQueryCacheKeyGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlCompiledQueryCacheKeyGenerator.cs @@ -34,18 +34,12 @@ public override object GenerateCacheKey(Expression query, bool async) GenerateCacheKeyCore(query, async), RelationalDependencies.ContextOptions.FindExtension()?.ReverseNullOrdering ?? false); - private struct NpgsqlCompiledQueryCacheKey + private struct NpgsqlCompiledQueryCacheKey( + RelationalCompiledQueryCacheKey relationalCompiledQueryCacheKey, + bool reverseNullOrdering) { - private readonly RelationalCompiledQueryCacheKey _relationalCompiledQueryCacheKey; - private readonly bool _reverseNullOrdering; - - public NpgsqlCompiledQueryCacheKey( - RelationalCompiledQueryCacheKey relationalCompiledQueryCacheKey, - bool reverseNullOrdering) - { - _relationalCompiledQueryCacheKey = relationalCompiledQueryCacheKey; - _reverseNullOrdering = reverseNullOrdering; - } + private readonly RelationalCompiledQueryCacheKey _relationalCompiledQueryCacheKey = relationalCompiledQueryCacheKey; + private readonly bool _reverseNullOrdering = reverseNullOrdering; public override bool Equals(object? obj) => !(obj is null) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 737813673..b71d138c3 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -1516,16 +1516,10 @@ private void GenerateList( } } - private sealed class OuterReferenceFindingExpressionVisitor : ExpressionVisitor + private sealed class OuterReferenceFindingExpressionVisitor(TableExpression mainTable) : ExpressionVisitor { - private readonly TableExpression _mainTable; private bool _containsReference; - public OuterReferenceFindingExpressionVisitor(TableExpression mainTable) - { - _mainTable = mainTable; - } - public bool ContainsReferenceToMainTable(SqlExpression sqlExpression) { _containsReference = false; @@ -1544,7 +1538,7 @@ public bool ContainsReferenceToMainTable(SqlExpression sqlExpression) } if (expression is ColumnExpression columnExpression - && columnExpression.Table == _mainTable) + && columnExpression.Table == mainTable) { _containsReference = true; diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index d4d8cd7b0..4cde6c0cd 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -1258,16 +1258,10 @@ private SqlExpression GetArray(TableExpressionBase tableExpression) } } - private sealed class OuterReferenceFindingExpressionVisitor : ExpressionVisitor + private sealed class OuterReferenceFindingExpressionVisitor(TableExpression mainTable) : ExpressionVisitor { - private readonly TableExpression _mainTable; private bool _containsReference; - public OuterReferenceFindingExpressionVisitor(TableExpression mainTable) - { - _mainTable = mainTable; - } - public bool ContainsReferenceToMainTable(TableExpressionBase tableExpression) { _containsReference = false; @@ -1286,7 +1280,7 @@ public bool ContainsReferenceToMainTable(TableExpressionBase tableExpression) } if (expression is ColumnExpression columnExpression - && columnExpression.Table == _mainTable) + && columnExpression.Table == mainTable) { _containsReference = true; diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 44580c911..68bf6b898 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -1297,14 +1297,9 @@ private static SequenceInfo ReadSequenceInfo(DbDataRecord record, Version postgr }; } - private sealed class SequenceInfo + private sealed class SequenceInfo(string storeType) { - public SequenceInfo(string storeType) - { - StoreType = storeType; - } - - public string StoreType { get; } + public string StoreType { get; } = storeType; public long? StartValue { get; set; } public long? MinValue { get; set; } public long? MaxValue { get; set; } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs index d48c3e121..3d6eef2f0 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlHstoreTypeMapping.cs @@ -109,16 +109,11 @@ protected override string GenerateNonNullSqlLiteral(object value) $"CLR type must be {nameof(Dictionary)} or {nameof(ImmutableDictionary)}"); } - private sealed class HstoreMutableComparer : ValueComparer> + private sealed class HstoreMutableComparer() : ValueComparer>( + (a, b) => Compare(a, b), + o => o.GetHashCode(), + o => new Dictionary(o)) { - public HstoreMutableComparer() - : base( - (a, b) => Compare(a, b), - o => o.GetHashCode(), - o => new Dictionary(o)) - { - } - private static bool Compare(Dictionary? a, Dictionary? b) { if (a is null) diff --git a/src/Shared/CodeAnnotations.cs b/src/Shared/CodeAnnotations.cs index 417866a62..32ec3e251 100644 --- a/src/Shared/CodeAnnotations.cs +++ b/src/Shared/CodeAnnotations.cs @@ -91,14 +91,9 @@ public StringFormatMethodAttribute(string formatParameterName) [AttributeUsage( AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] -internal sealed class ValueProviderAttribute : Attribute +internal sealed class ValueProviderAttribute(string name) : Attribute { - public ValueProviderAttribute(string name) - { - Name = name; - } - - public string Name { get; } + public string Name { get; } = name; } /// @@ -164,9 +159,7 @@ public ValueRangeAttribute(ulong value) | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Delegate)] -internal sealed class NonNegativeValueAttribute : Attribute -{ -} +internal sealed class NonNegativeValueAttribute : Attribute; /// /// Indicates that the function argument should be a string literal and match one @@ -180,9 +173,7 @@ internal sealed class NonNegativeValueAttribute : Attribute /// } /// [AttributeUsage(AttributeTargets.Parameter)] -internal sealed class InvokerParameterNameAttribute : Attribute -{ -} +internal sealed class InvokerParameterNameAttribute : Attribute; /// /// Describes dependency between method input and output. @@ -229,22 +220,16 @@ internal sealed class InvokerParameterNameAttribute : Attribute /// /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -internal sealed class ContractAnnotationAttribute : Attribute +internal sealed class ContractAnnotationAttribute(string contract, bool forceFullStates) : Attribute { public ContractAnnotationAttribute(string contract) : this(contract, false) { } - public ContractAnnotationAttribute(string contract, bool forceFullStates) - { - Contract = contract; - ForceFullStates = forceFullStates; - } + public string Contract { get; } = contract; - public string Contract { get; } - - public bool ForceFullStates { get; } + public bool ForceFullStates { get; } = forceFullStates; } /// @@ -257,19 +242,14 @@ public ContractAnnotationAttribute(string contract, bool forceFullStates) /// } /// [AttributeUsage(AttributeTargets.All)] -internal sealed class LocalizationRequiredAttribute : Attribute +internal sealed class LocalizationRequiredAttribute(bool required) : Attribute { public LocalizationRequiredAttribute() : this(true) { } - public LocalizationRequiredAttribute(bool required) - { - Required = required; - } - - public bool Required { get; } + public bool Required { get; } = required; } /// @@ -293,9 +273,7 @@ public LocalizationRequiredAttribute(bool required) /// } /// [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] -internal sealed class CannotApplyEqualityOperatorAttribute : Attribute -{ -} +internal sealed class CannotApplyEqualityOperatorAttribute : Attribute; /// /// When applied to a target attribute, specifies a requirement for any type marked @@ -310,14 +288,9 @@ internal sealed class CannotApplyEqualityOperatorAttribute : Attribute /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [BaseTypeRequired(typeof(Attribute))] -internal sealed class BaseTypeRequiredAttribute : Attribute +internal sealed class BaseTypeRequiredAttribute(Type baseType) : Attribute { - public BaseTypeRequiredAttribute(Type baseType) - { - BaseType = baseType; - } - - public Type BaseType { get; } + public Type BaseType { get; } = baseType; } /// @@ -325,7 +298,8 @@ public BaseTypeRequiredAttribute(Type baseType) /// so this symbol will not be reported as unused (as well as by other usage inspections). /// [AttributeUsage(AttributeTargets.All)] -internal sealed class UsedImplicitlyAttribute : Attribute +internal sealed class UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + : Attribute { public UsedImplicitlyAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) @@ -342,15 +316,9 @@ public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) { } - public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - public ImplicitUseKindFlags UseKindFlags { get; } + public ImplicitUseKindFlags UseKindFlags { get; } = useKindFlags; - public ImplicitUseTargetFlags TargetFlags { get; } + public ImplicitUseTargetFlags TargetFlags { get; } = targetFlags; } /// @@ -360,7 +328,8 @@ public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTar /// is used implicitly. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter | AttributeTargets.Parameter)] -internal sealed class MeansImplicitUseAttribute : Attribute +internal sealed class MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + : Attribute { public MeansImplicitUseAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) @@ -377,15 +346,9 @@ public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) { } - public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; } + [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; } = useKindFlags; - [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; } + [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; } = targetFlags; } /// @@ -450,14 +413,10 @@ internal enum ImplicitUseTargetFlags /// } /// [AttributeUsage(AttributeTargets.Parameter)] -internal sealed class NoEnumerationAttribute : Attribute -{ -} +internal sealed class NoEnumerationAttribute : Attribute; /// /// Indicates that the marked parameter is a regular expression pattern. /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] -internal sealed class RegexPatternAttribute : Attribute -{ -} \ No newline at end of file +internal sealed class RegexPatternAttribute : Attribute; \ No newline at end of file diff --git a/test/EFCore.PG.FunctionalTests/BatchingTest.cs b/test/EFCore.PG.FunctionalTests/BatchingTest.cs index 2ae6bafb5..a8cd3516f 100644 --- a/test/EFCore.PG.FunctionalTests/BatchingTest.cs +++ b/test/EFCore.PG.FunctionalTests/BatchingTest.cs @@ -7,14 +7,9 @@ // ReSharper disable InconsistentNaming namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class BatchingTest : IClassFixture +public class BatchingTest(BatchingTest.BatchingTestFixture fixture) : IClassFixture { - public BatchingTest(BatchingTestFixture fixture) - { - Fixture = fixture; - } - - protected BatchingTestFixture Fixture { get; } + protected BatchingTestFixture Fixture { get; } = fixture; [Theory] [InlineData(true, true, true)] @@ -201,13 +196,8 @@ private void ExecuteWithStrategyInTransaction( protected void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); - private class BloggingContext : PoolableDbContext + private class BloggingContext(DbContextOptions options) : PoolableDbContext(options) { - public BloggingContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity( diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs index bfe7cf2a0..58acbafea 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs @@ -3,14 +3,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Update; -public class ComplexTypeBulkUpdatesNpgsqlTest : ComplexTypeBulkUpdatesTestBase< - ComplexTypeBulkUpdatesNpgsqlTest.ComplexTypeBulkUpdatesNpgsqlFixture> +public class ComplexTypeBulkUpdatesNpgsqlTest( + ComplexTypeBulkUpdatesNpgsqlTest.ComplexTypeBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : ComplexTypeBulkUpdatesTestBase(fixture, testOutputHelper) { - public ComplexTypeBulkUpdatesNpgsqlTest(ComplexTypeBulkUpdatesNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - public override async Task Delete_entity_type_with_complex_type(bool async) { await base.Delete_entity_type_with_complex_type(async); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs index 5c1bda899..bf9f677e6 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs @@ -170,13 +170,8 @@ await AssertUpdate( rowsAffectedCount: 1); } - protected class Context3001 : DbContext + protected class Context3001(DbContextOptions options) : DbContext(options) { - public Context3001(DbContextOptions options) - : base(options) - { - } - public DbSet EntitiesWithPrimitiveCollection { get; set; } } diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs index 098e57b2e..8bbb02ee0 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs @@ -3,15 +3,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class NorthwindBulkUpdatesNpgsqlTest : NorthwindBulkUpdatesTestBase> +public class NorthwindBulkUpdatesNpgsqlTest( + NorthwindBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : NorthwindBulkUpdatesTestBase>(fixture, testOutputHelper) { - public NorthwindBulkUpdatesNpgsqlTest( - NorthwindBulkUpdatesNpgsqlFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - public override async Task Delete_Where_TagWith(bool async) { await base.Delete_Where_TagWith(async); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesNpgsqlTest.cs index 1d19a430f..79a758d0c 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,16 +2,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPCFiltersInheritanceBulkUpdatesNpgsqlTest - : TPCFiltersInheritanceBulkUpdatesTestBase +public class TPCFiltersInheritanceBulkUpdatesNpgsqlTest( + TPCFiltersInheritanceBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : TPCFiltersInheritanceBulkUpdatesTestBase(fixture, testOutputHelper) { - public TPCFiltersInheritanceBulkUpdatesNpgsqlTest( - TPCFiltersInheritanceBulkUpdatesNpgsqlFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - public override async Task Delete_where_hierarchy(bool async) { await base.Delete_where_hierarchy(async); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesNpgsqlTest.cs index 90f2a52d9..6b68719e9 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,16 +2,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPCInheritanceBulkUpdatesNpgsqlTest - : TPCInheritanceBulkUpdatesTestBase +public class TPCInheritanceBulkUpdatesNpgsqlTest( + TPCInheritanceBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : TPCInheritanceBulkUpdatesTestBase(fixture, testOutputHelper) { - public TPCInheritanceBulkUpdatesNpgsqlTest( - TPCInheritanceBulkUpdatesNpgsqlFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - public override async Task Delete_where_hierarchy(bool async) { await base.Delete_where_hierarchy(async); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesNpgsqlTest.cs index b0590845f..9e9a1477e 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,16 +2,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPTFiltersInheritanceBulkUpdatesSqlServerTest - : TPTFiltersInheritanceBulkUpdatesTestBase +public class TPTFiltersInheritanceBulkUpdatesSqlServerTest( + TPTFiltersInheritanceBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : TPTFiltersInheritanceBulkUpdatesTestBase(fixture, testOutputHelper) { - public TPTFiltersInheritanceBulkUpdatesSqlServerTest( - TPTFiltersInheritanceBulkUpdatesNpgsqlFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - public override async Task Delete_where_hierarchy(bool async) { await base.Delete_where_hierarchy(async); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesNpgsqlTest.cs index 503c0ae55..d2e87b654 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,15 +2,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPTInheritanceBulkUpdatesNpgsqlTest : TPTInheritanceBulkUpdatesTestBase +public class TPTInheritanceBulkUpdatesNpgsqlTest( + TPTInheritanceBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : TPTInheritanceBulkUpdatesTestBase(fixture, testOutputHelper) { - public TPTInheritanceBulkUpdatesNpgsqlTest( - TPTInheritanceBulkUpdatesNpgsqlFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - public override async Task Delete_where_hierarchy(bool async) { await base.Delete_where_hierarchy(async); diff --git a/test/EFCore.PG.FunctionalTests/CommandInterceptionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/CommandInterceptionNpgsqlTest.cs index ced8872ae..26799628e 100644 --- a/test/EFCore.PG.FunctionalTests/CommandInterceptionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/CommandInterceptionNpgsqlTest.cs @@ -4,13 +4,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public abstract class CommandInterceptionNpgsqlTestBase : CommandInterceptionTestBase +public abstract class CommandInterceptionNpgsqlTestBase(CommandInterceptionNpgsqlTestBase.InterceptionNpgsqlFixtureBase fixture) + : CommandInterceptionTestBase(fixture) { - public CommandInterceptionNpgsqlTestBase(InterceptionNpgsqlFixtureBase fixture) - : base(fixture) - { - } - public abstract class InterceptionNpgsqlFixtureBase : InterceptionFixtureBase { protected override string StoreName @@ -25,14 +21,9 @@ protected override IServiceCollection InjectInterceptors( => base.InjectInterceptors(serviceCollection.AddEntityFrameworkNpgsql(), injectedInterceptors); } - public class CommandInterceptionNpgsqlTest - : CommandInterceptionNpgsqlTestBase, IClassFixture + public class CommandInterceptionNpgsqlTest(CommandInterceptionNpgsqlTest.InterceptionNpgsqlFixture fixture) + : CommandInterceptionNpgsqlTestBase(fixture), IClassFixture { - public CommandInterceptionNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override bool ShouldSubscribeToDiagnosticListener @@ -47,14 +38,10 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build } } - public class CommandInterceptionWithDiagnosticsNpgsqlTest - : CommandInterceptionNpgsqlTestBase, IClassFixture + public class CommandInterceptionWithDiagnosticsNpgsqlTest( + CommandInterceptionWithDiagnosticsNpgsqlTest.InterceptionNpgsqlFixture fixture) + : CommandInterceptionNpgsqlTestBase(fixture), IClassFixture { - public CommandInterceptionWithDiagnosticsNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override bool ShouldSubscribeToDiagnosticListener diff --git a/test/EFCore.PG.FunctionalTests/CompositeKeyEndToEndTest.cs b/test/EFCore.PG.FunctionalTests/CompositeKeyEndToEndTest.cs index 6e66276b3..3770aaf73 100644 --- a/test/EFCore.PG.FunctionalTests/CompositeKeyEndToEndTest.cs +++ b/test/EFCore.PG.FunctionalTests/CompositeKeyEndToEndTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class CompositeKeyEndToEndNpgsqlTest : CompositeKeyEndToEndTestBase +public class CompositeKeyEndToEndNpgsqlTest(CompositeKeyEndToEndNpgsqlTest.CompositeKeyEndToEndNpgsqlFixture fixture) + : CompositeKeyEndToEndTestBase(fixture) { - public CompositeKeyEndToEndNpgsqlTest(CompositeKeyEndToEndNpgsqlFixture fixture) - : base(fixture) - { - } - public class CompositeKeyEndToEndNpgsqlFixture : CompositeKeyEndToEndFixtureBase { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs index 4565752b1..17340262f 100644 --- a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs +++ b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs @@ -47,16 +47,10 @@ public void Can_use_computed_columns_with_null_values() Assert.Null(entity.P5); } - private class Context : DbContext + private class Context(IServiceProvider serviceProvider, string databaseName) : DbContext { - private readonly IServiceProvider _serviceProvider; - private readonly string _databaseName; - - public Context(IServiceProvider serviceProvider, string databaseName) - { - _serviceProvider = serviceProvider; - _databaseName = databaseName; - } + private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly string _databaseName = databaseName; public DbSet Entities { get; set; } @@ -103,23 +97,14 @@ public class EnumItem public FlagEnum? CalculatedFlagEnum { get; set; } } - private class NullableContext : DbContext + private class NullableContext(IServiceProvider serviceProvider, string databaseName) : DbContext { - private readonly IServiceProvider _serviceProvider; - private readonly string _databaseName; - - public NullableContext(IServiceProvider serviceProvider, string databaseName) - { - _serviceProvider = serviceProvider; - _databaseName = databaseName; - } - public DbSet EnumItems { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder - .UseNpgsql(NpgsqlTestStore.CreateConnectionString(_databaseName), b => b.ApplyConfiguration()) - .UseInternalServiceProvider(_serviceProvider); + .UseNpgsql(NpgsqlTestStore.CreateConnectionString(databaseName), b => b.ApplyConfiguration()) + .UseInternalServiceProvider(serviceProvider); protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity() @@ -143,12 +128,7 @@ public void Can_use_computed_columns_with_nullable_enum() Assert.Equal(FlagEnum.AValue | FlagEnum.BValue, entity.CalculatedFlagEnum); } - public ComputedColumnTest() - { - TestStore = NpgsqlTestStore.CreateInitialized("ComputedColumnTest"); - } - - protected NpgsqlTestStore TestStore { get; } + protected NpgsqlTestStore TestStore { get; } = NpgsqlTestStore.CreateInitialized("ComputedColumnTest"); public virtual void Dispose() => TestStore.Dispose(); diff --git a/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs index 4a9611f0a..c7619c547 100644 --- a/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs @@ -4,13 +4,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class ConferencePlannerNpgsqlTest : ConferencePlannerTestBase +public class ConferencePlannerNpgsqlTest(ConferencePlannerNpgsqlTest.ConferencePlannerNpgsqlFixture fixture) + : ConferencePlannerTestBase(fixture) { - public ConferencePlannerNpgsqlTest(ConferencePlannerNpgsqlFixture fixture) - : base(fixture) - { - } - // Overridden to use UTC DateTimeOffsets public override async Task SessionsController_Post() => await ExecuteWithStrategyInTransactionAsync( diff --git a/test/EFCore.PG.FunctionalTests/ConnectionInterceptionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConnectionInterceptionNpgsqlTest.cs index e8c47ed71..2d8f44e13 100644 --- a/test/EFCore.PG.FunctionalTests/ConnectionInterceptionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConnectionInterceptionNpgsqlTest.cs @@ -4,13 +4,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public abstract class ConnectionInterceptionNpgsqlTestBase : ConnectionInterceptionTestBase +public abstract class ConnectionInterceptionNpgsqlTestBase(ConnectionInterceptionNpgsqlTestBase.InterceptionNpgsqlFixtureBase fixture) + : ConnectionInterceptionTestBase(fixture) { - protected ConnectionInterceptionNpgsqlTestBase(InterceptionNpgsqlFixtureBase fixture) - : base(fixture) - { - } - [ConditionalTheory(Skip = "#2368")] public override Task Intercept_connection_creation_passively(bool async) => base.Intercept_connection_creation_passively(async); @@ -79,14 +75,9 @@ protected override DbCommand CreateDbCommand() => throw new NotImplementedException(); } - public class ConnectionInterceptionNpgsqlTest - : ConnectionInterceptionNpgsqlTestBase, IClassFixture + public class ConnectionInterceptionNpgsqlTest(ConnectionInterceptionNpgsqlTest.InterceptionNpgsqlFixture fixture) + : ConnectionInterceptionNpgsqlTestBase(fixture), IClassFixture { - public ConnectionInterceptionNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override bool ShouldSubscribeToDiagnosticListener @@ -94,14 +85,11 @@ protected override bool ShouldSubscribeToDiagnosticListener } } - public class ConnectionInterceptionWithDiagnosticsNpgsqlTest - : ConnectionInterceptionNpgsqlTestBase, IClassFixture + public class ConnectionInterceptionWithDiagnosticsNpgsqlTest( + ConnectionInterceptionWithDiagnosticsNpgsqlTest.InterceptionNpgsqlFixture fixture) + : ConnectionInterceptionNpgsqlTestBase(fixture), + IClassFixture { - public ConnectionInterceptionWithDiagnosticsNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override bool ShouldSubscribeToDiagnosticListener diff --git a/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs b/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs index 539d70731..152082d8b 100644 --- a/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs @@ -56,21 +56,14 @@ public void Can_specify_connection_in_OnConfiguring_with_default_service_provide Assert.True(context.Customers.Any()); } - private class ConnectionInOnConfiguringContext : NorthwindContextBase + private class ConnectionInOnConfiguringContext(NpgsqlConnection connection) : NorthwindContextBase { - private readonly NpgsqlConnection _connection; - - public ConnectionInOnConfiguringContext(NpgsqlConnection connection) - { - _connection = connection; - } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseNpgsql(_connection, b => b.ApplyConfiguration()); + => optionsBuilder.UseNpgsql(connection, b => b.ApplyConfiguration()); public override void Dispose() { - _connection.Dispose(); + connection.Dispose(); base.Dispose(); } } @@ -108,9 +101,7 @@ public void Throws_if_no_config_without_UseNpgsql() } // ReSharper disable once ClassNeverInstantiated.Local - private class NoUseNpgsqlContext : NorthwindContextBase - { - } + private class NoUseNpgsqlContext : NorthwindContextBase; [Fact] public void Can_depend_on_DbContextOptions() @@ -137,30 +128,21 @@ public void Can_depend_on_DbContextOptions_with_default_service_provider() Assert.True(context.Customers.Any()); } - private class OptionsContext : NorthwindContextBase + private class OptionsContext(DbContextOptions options, NpgsqlConnection connection) + : NorthwindContextBase(options) { - private readonly NpgsqlConnection _connection; - private readonly DbContextOptions _options; - - public OptionsContext(DbContextOptions options, NpgsqlConnection connection) - : base(options) - { - _options = options; - _connection = connection; - } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - Assert.Same(_options, optionsBuilder.Options); + Assert.Same(options, optionsBuilder.Options); - optionsBuilder.UseNpgsql(_connection, b => b.ApplyConfiguration()); + optionsBuilder.UseNpgsql(connection, b => b.ApplyConfiguration()); - Assert.NotSame(_options, optionsBuilder.Options); + Assert.NotSame(options, optionsBuilder.Options); } public override void Dispose() { - _connection.Dispose(); + connection.Dispose(); base.Dispose(); } } @@ -187,15 +169,9 @@ public void Can_depend_on_non_generic_options_when_only_one_context_with_default Assert.True(context.Customers.Any()); } - private class NonGenericOptionsContext : NorthwindContextBase + private class NonGenericOptionsContext(DbContextOptions options) : NorthwindContextBase(options) { - private readonly DbContextOptions _options; - - public NonGenericOptionsContext(DbContextOptions options) - : base(options) - { - _options = options; - } + private readonly DbContextOptions _options = options; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -301,13 +277,7 @@ public void Can_create_admin_connection_with_connection() adminConnection.Open(); } - private class GeneralOptionsContext : NorthwindContextBase - { - public GeneralOptionsContext(DbContextOptions options) - : base(options) - { - } - } + private class GeneralOptionsContext(DbContextOptions options) : NorthwindContextBase(options); #endregion } diff --git a/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs index 085f560c4..10f0cc9d9 100644 --- a/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class CustomConvertersNpgsqlTest : CustomConvertersTestBase +public class CustomConvertersNpgsqlTest(CustomConvertersNpgsqlTest.CustomConvertersNpgsqlFixture fixture) + : CustomConvertersTestBase(fixture) { - public CustomConvertersNpgsqlTest(CustomConvertersNpgsqlFixture fixture) - : base(fixture) - { - } - // Disabled: PostgreSQL is case-sensitive public override void Can_insert_and_read_back_with_case_insensitive_string_key() { } diff --git a/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs index cc2b9f362..634a16cf2 100644 --- a/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class DataAnnotationNpgsqlTest : DataAnnotationRelationalTestBase +public class DataAnnotationNpgsqlTest(DataAnnotationNpgsqlTest.DataAnnotationNpgsqlFixture fixture) + : DataAnnotationRelationalTestBase(fixture) { - public DataAnnotationNpgsqlTest(DataAnnotationNpgsqlFixture fixture) - : base(fixture) - { - } - protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); diff --git a/test/EFCore.PG.FunctionalTests/DataBindingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/DataBindingNpgsqlTest.cs index 308ef08e7..9e53df97a 100644 --- a/test/EFCore.PG.FunctionalTests/DataBindingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/DataBindingNpgsqlTest.cs @@ -1,9 +1,3 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class DataBindingNpgsqlTest : DataBindingTestBase -{ - public DataBindingNpgsqlTest(F1BytesNpgsqlFixture fixture) - : base(fixture) - { - } -} +public class DataBindingNpgsqlTest(F1BytesNpgsqlFixture fixture) : DataBindingTestBase(fixture); diff --git a/test/EFCore.PG.FunctionalTests/DefaultValuesTest.cs b/test/EFCore.PG.FunctionalTests/DefaultValuesTest.cs index b3677ed03..611655a01 100644 --- a/test/EFCore.PG.FunctionalTests/DefaultValuesTest.cs +++ b/test/EFCore.PG.FunctionalTests/DefaultValuesTest.cs @@ -43,16 +43,10 @@ public void Dispose() context.Database.EnsureDeleted(); } - private class ChipsContext : DbContext + private class ChipsContext(IServiceProvider serviceProvider, string databaseName) : DbContext { - private readonly IServiceProvider _serviceProvider; - private readonly string _databaseName; - - public ChipsContext(IServiceProvider serviceProvider, string databaseName) - { - _serviceProvider = serviceProvider; - _databaseName = databaseName; - } + private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly string _databaseName = databaseName; // ReSharper disable once UnusedAutoPropertyAccessor.Local public DbSet Chips { get; set; } diff --git a/test/EFCore.PG.FunctionalTests/DesignTimeNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/DesignTimeNpgsqlTest.cs index 88e113ac7..3bfffa890 100644 --- a/test/EFCore.PG.FunctionalTests/DesignTimeNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/DesignTimeNpgsqlTest.cs @@ -3,13 +3,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class DesignTimeNpgsqlTest : DesignTimeTestBase +public class DesignTimeNpgsqlTest(DesignTimeNpgsqlTest.DesignTimeNpgsqlFixture fixture) + : DesignTimeTestBase(fixture) { - public DesignTimeNpgsqlTest(DesignTimeNpgsqlFixture fixture) - : base(fixture) - { - } - protected override Assembly ProviderAssembly => typeof(NpgsqlDesignTimeServices).Assembly; diff --git a/test/EFCore.PG.FunctionalTests/EntitySplittingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/EntitySplittingNpgsqlTest.cs index dccba770b..8552d88f9 100644 --- a/test/EFCore.PG.FunctionalTests/EntitySplittingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/EntitySplittingNpgsqlTest.cs @@ -2,13 +2,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class EntitySplittingNpgsqlTest : EntitySplittingTestBase +public class EntitySplittingNpgsqlTest(ITestOutputHelper testOutputHelper) : EntitySplittingTestBase(testOutputHelper) { - public EntitySplittingNpgsqlTest(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; } diff --git a/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs b/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs index 1d82ef469..f66ac9433 100644 --- a/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs +++ b/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs @@ -607,13 +607,8 @@ public void Verification_is_retried_using_same_retry_limit() } } - protected class ExecutionStrategyContext : DbContext + protected class ExecutionStrategyContext(DbContextOptions options) : DbContext(options) { - public ExecutionStrategyContext(DbContextOptions options) - : base(options) - { - } - public DbSet Products { get; set; } } diff --git a/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs b/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs index cebec6272..2ef271f86 100644 --- a/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs +++ b/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs @@ -67,16 +67,10 @@ private static async Task Can_use_an_existing_closed_connection_test(bool openCo } } - private class NorthwindContext : DbContext + private class NorthwindContext(IServiceProvider serviceProvider, NpgsqlConnection connection) : DbContext { - private readonly IServiceProvider _serviceProvider; - private readonly NpgsqlConnection _connection; - - public NorthwindContext(IServiceProvider serviceProvider, NpgsqlConnection connection) - { - _serviceProvider = serviceProvider; - _connection = connection; - } + private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly NpgsqlConnection _connection = connection; // ReSharper disable once UnusedAutoPropertyAccessor.Local public DbSet Customers { get; set; } diff --git a/test/EFCore.PG.FunctionalTests/F1NpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/F1NpgsqlFixture.cs index 193474eed..91be0a0e5 100644 --- a/test/EFCore.PG.FunctionalTests/F1NpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/F1NpgsqlFixture.cs @@ -21,16 +21,10 @@ protected override void BuildModelExternal(ModelBuilder modelBuilder) }); } - private class BytesToUIntConverter : ValueConverter - { - public BytesToUIntConverter() - : base( - bytes => BitConverter.ToUInt32(bytes), - num => BitConverter.GetBytes(num), - mappingHints: null) - { - } - } + private class BytesToUIntConverter() : ValueConverter( + bytes => BitConverter.ToUInt32(bytes), + num => BitConverter.GetBytes(num), + mappingHints: null); } public class F1NpgsqlFixture : F1NpgsqlFixtureBase diff --git a/test/EFCore.PG.FunctionalTests/FieldMappingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/FieldMappingNpgsqlTest.cs index dd19464ba..c725a2f93 100644 --- a/test/EFCore.PG.FunctionalTests/FieldMappingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/FieldMappingNpgsqlTest.cs @@ -2,14 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class FieldMappingNpgsqlTest - : FieldMappingTestBase +public class FieldMappingNpgsqlTest(FieldMappingNpgsqlTest.FieldMappingNpgsqlFixture fixture) + : FieldMappingTestBase(fixture) { - public FieldMappingNpgsqlTest(FieldMappingNpgsqlFixture fixture) - : base(fixture) - { - } - protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); diff --git a/test/EFCore.PG.FunctionalTests/FindNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/FindNpgsqlTest.cs index 071c2e844..a76837511 100644 --- a/test/EFCore.PG.FunctionalTests/FindNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/FindNpgsqlTest.cs @@ -10,33 +10,18 @@ protected FindNpgsqlTest(FindNpgsqlFixture fixture) fixture.TestSqlLoggerFactory.Clear(); } - public class FindNpgsqlTestSet : FindNpgsqlTest + public class FindNpgsqlTestSet(FindNpgsqlFixture fixture) : FindNpgsqlTest(fixture) { - public FindNpgsqlTestSet(FindNpgsqlFixture fixture) - : base(fixture) - { - } - protected override TestFinder Finder { get; } = new FindViaSetFinder(); } - public class FindNpgsqlTestContext : FindNpgsqlTest + public class FindNpgsqlTestContext(FindNpgsqlFixture fixture) : FindNpgsqlTest(fixture) { - public FindNpgsqlTestContext(FindNpgsqlFixture fixture) - : base(fixture) - { - } - protected override TestFinder Finder { get; } = new FindViaContextFinder(); } - public class FindNpgsqlTestNonGeneric : FindNpgsqlTest + public class FindNpgsqlTestNonGeneric(FindNpgsqlFixture fixture) : FindNpgsqlTest(fixture) { - public FindNpgsqlTestNonGeneric(FindNpgsqlFixture fixture) - : base(fixture) - { - } - protected override TestFinder Finder { get; } = new FindViaNonGenericContextFinder(); } diff --git a/test/EFCore.PG.FunctionalTests/KeysWithConvertersNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/KeysWithConvertersNpgsqlTest.cs index 1bc6bf9af..e83af54b8 100644 --- a/test/EFCore.PG.FunctionalTests/KeysWithConvertersNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/KeysWithConvertersNpgsqlTest.cs @@ -2,14 +2,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class KeysWithConvertersNpgsqlTest : KeysWithConvertersTestBase< - KeysWithConvertersNpgsqlTest.KeysWithConvertersNpgsqlFixture> +public class KeysWithConvertersNpgsqlTest(KeysWithConvertersNpgsqlTest.KeysWithConvertersNpgsqlFixture fixture) + : KeysWithConvertersTestBase< + KeysWithConvertersNpgsqlTest.KeysWithConvertersNpgsqlFixture>(fixture) { - public KeysWithConvertersNpgsqlTest(KeysWithConvertersNpgsqlFixture fixture) - : base(fixture) - { - } - public class KeysWithConvertersNpgsqlFixture : KeysWithConvertersFixtureBase { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs index 9f4b69706..7edbcc6ab 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs @@ -3,14 +3,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class ManyToManyFieldsLoadNpgsqlTest - : ManyToManyFieldsLoadTestBase +public class ManyToManyFieldsLoadNpgsqlTest(ManyToManyFieldsLoadNpgsqlTest.ManyToManyFieldsLoadNpgsqlFixture fixture) + : ManyToManyFieldsLoadTestBase(fixture) { - public ManyToManyFieldsLoadNpgsqlTest(ManyToManyFieldsLoadNpgsqlFixture fixture) - : base(fixture) - { - } - public class ManyToManyFieldsLoadNpgsqlFixture : ManyToManyFieldsLoadFixtureBase { public TestSqlLoggerFactory TestSqlLoggerFactory diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs index 0d7eaddd4..eaa5fea6a 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs @@ -3,13 +3,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class ManyToManyLoadNpgsqlTest : ManyToManyLoadTestBase +public class ManyToManyLoadNpgsqlTest(ManyToManyLoadNpgsqlTest.ManyToManyLoadNpgsqlFixture fixture) + : ManyToManyLoadTestBase(fixture) { - public ManyToManyLoadNpgsqlTest(ManyToManyLoadNpgsqlFixture fixture) - : base(fixture) - { - } - public class ManyToManyLoadNpgsqlFixture : ManyToManyLoadFixtureBase { public TestSqlLoggerFactory TestSqlLoggerFactory diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs index 3ef930e16..b7a906ffd 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs @@ -3,14 +3,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class ManyToManyTrackingNpgsqlTest : ManyToManyTrackingRelationalTestBase< - ManyToManyTrackingNpgsqlTest.ManyToManyTrackingNpgsqlFixture> +public class ManyToManyTrackingNpgsqlTest(ManyToManyTrackingNpgsqlTest.ManyToManyTrackingNpgsqlFixture fixture) + : ManyToManyTrackingRelationalTestBase< + ManyToManyTrackingNpgsqlTest.ManyToManyTrackingNpgsqlFixture>(fixture) { - public ManyToManyTrackingNpgsqlTest(ManyToManyTrackingNpgsqlFixture fixture) - : base(fixture) - { - } - protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); diff --git a/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs index dc8e30903..b571ed37c 100644 --- a/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs @@ -6,22 +6,12 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class MaterializationInterceptionNpgsqlTest : - MaterializationInterceptionTestBase, - IClassFixture +public class MaterializationInterceptionNpgsqlTest(MaterializationInterceptionNpgsqlTest.MaterializationInterceptionNpgsqlFixture fixture) + : MaterializationInterceptionTestBase(fixture), + IClassFixture { - public MaterializationInterceptionNpgsqlTest(MaterializationInterceptionNpgsqlFixture fixture) - : base(fixture) + public class SqlServerLibraryContext(DbContextOptions options) : LibraryContext(options) { - } - - public class SqlServerLibraryContext : LibraryContext - { - public SqlServerLibraryContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs index 6336588d7..43139f7bb 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs @@ -4,14 +4,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations { - public class MigrationsInfrastructureNpgsqlTest - : MigrationsInfrastructureTestBase + public class MigrationsInfrastructureNpgsqlTest(MigrationsInfrastructureNpgsqlTest.MigrationsInfrastructureNpgsqlFixture fixture) + : MigrationsInfrastructureTestBase(fixture) { - public MigrationsInfrastructureNpgsqlTest(MigrationsInfrastructureNpgsqlFixture fixture) - : base(fixture) - { - } - public override void Can_get_active_provider() { base.Can_get_active_provider(); @@ -34,13 +29,8 @@ public async Task Empty_Migration_Creates_Database() Assert.True(creator.Exists()); } - private class BloggingContext : DbContext + private class BloggingContext(DbContextOptions options) : DbContext(options) { - public BloggingContext(DbContextOptions options) - : base(options) - { - } - // ReSharper disable once UnusedMember.Local public DbSet Blogs { get; set; } diff --git a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs index 19bb19b2f..a9e96d2c5 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs @@ -7,7 +7,13 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations; -public class NpgsqlMigrationsSqlGeneratorTest : MigrationsSqlGeneratorTestBase +public class NpgsqlMigrationsSqlGeneratorTest() : MigrationsSqlGeneratorTestBase( + NpgsqlTestHelpers.Instance, + new ServiceCollection().AddEntityFrameworkNpgsqlNetTopologySuite(), + NpgsqlTestHelpers.Instance.AddProviderOptions( + ((IRelationalDbContextOptionsBuilderInfrastructure) + new NpgsqlDbContextOptionsBuilder(new DbContextOptionsBuilder()).UseNetTopologySuite()) + .OptionsBuilder).Options) { #region Database @@ -613,17 +619,6 @@ public override void InsertDataOperation_throws_for_unsupported_column_types() #pragma warning restore 618 - public NpgsqlMigrationsSqlGeneratorTest() - : base( - NpgsqlTestHelpers.Instance, - new ServiceCollection().AddEntityFrameworkNpgsqlNetTopologySuite(), - NpgsqlTestHelpers.Instance.AddProviderOptions( - ((IRelationalDbContextOptionsBuilderInfrastructure) - new NpgsqlDbContextOptionsBuilder(new DbContextOptionsBuilder()).UseNetTopologySuite()) - .OptionsBuilder).Options) - { - } - protected override string GetGeometryCollectionStoreType() => "GEOMETRY(GEOMETRYCOLLECTION)"; } diff --git a/test/EFCore.PG.FunctionalTests/MonsterFixupChangedChangingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/MonsterFixupChangedChangingNpgsqlTest.cs index 7f7272073..433677564 100644 --- a/test/EFCore.PG.FunctionalTests/MonsterFixupChangedChangingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/MonsterFixupChangedChangingNpgsqlTest.cs @@ -2,14 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class MonsterFixupChangedChangingNpgsqlTest : - MonsterFixupTestBase +public class MonsterFixupChangedChangingNpgsqlTest(MonsterFixupChangedChangingNpgsqlTest.MonsterFixupChangedChangingNpgsqlFixture fixture) + : MonsterFixupTestBase(fixture) { - public MonsterFixupChangedChangingNpgsqlTest(MonsterFixupChangedChangingNpgsqlFixture fixture) - : base(fixture) - { - } - public class MonsterFixupChangedChangingNpgsqlFixture : MonsterFixupChangedChangingFixtureBase { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/MusicStoreNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/MusicStoreNpgsqlTest.cs index a9c448cd9..45ca33212 100644 --- a/test/EFCore.PG.FunctionalTests/MusicStoreNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/MusicStoreNpgsqlTest.cs @@ -3,13 +3,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class MusicStoreNpgsqlTest : MusicStoreTestBase +public class MusicStoreNpgsqlTest(MusicStoreNpgsqlTest.MusicStoreNpgsqlFixture fixture) + : MusicStoreTestBase(fixture) { - public MusicStoreNpgsqlTest(MusicStoreNpgsqlFixture fixture) - : base(fixture) - { - } - public class MusicStoreNpgsqlFixture : MusicStoreFixtureBase { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/NotificationEntitiesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/NotificationEntitiesNpgsqlTest.cs index 326a7d58b..5adf93326 100644 --- a/test/EFCore.PG.FunctionalTests/NotificationEntitiesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/NotificationEntitiesNpgsqlTest.cs @@ -2,14 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class NotificationEntitiesNpgsqlTest - : NotificationEntitiesTestBase +public class NotificationEntitiesNpgsqlTest(NotificationEntitiesNpgsqlTest.NotificationEntitiesNpgsqlFixture fixture) + : NotificationEntitiesTestBase(fixture) { - public NotificationEntitiesNpgsqlTest(NotificationEntitiesNpgsqlFixture fixture) - : base(fixture) - { - } - public class NotificationEntitiesNpgsqlFixture : NotificationEntitiesFixtureBase { protected override string StoreName { get; } = "NotificationEntities"; diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlApiConsistencyTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlApiConsistencyTest.cs index 7aff5b4ca..c5c596b2c 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlApiConsistencyTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlApiConsistencyTest.cs @@ -3,13 +3,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class NpgsqlApiConsistencyTest : ApiConsistencyTestBase +public class NpgsqlApiConsistencyTest(NpgsqlApiConsistencyTest.NpgsqlApiConsistencyFixture fixture) + : ApiConsistencyTestBase(fixture) { - public NpgsqlApiConsistencyTest(NpgsqlApiConsistencyFixture fixture) - : base(fixture) - { - } - protected override void AddServices(ServiceCollection serviceCollection) => serviceCollection.AddEntityFrameworkNpgsql(); diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs index 3e2e54569..0a2ff9dcb 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs @@ -571,13 +571,9 @@ protected static IExecutionStrategy GetExecutionStrategy(NpgsqlTestStore testSto => new BloggingContext(testStore).GetService().Create(); // ReSharper disable once ClassNeverInstantiated.Local - private class TestNpgsqlExecutionStrategyFactory : NpgsqlExecutionStrategyFactory + private class TestNpgsqlExecutionStrategyFactory(ExecutionStrategyDependencies dependencies) + : NpgsqlExecutionStrategyFactory(dependencies) { - public TestNpgsqlExecutionStrategyFactory(ExecutionStrategyDependencies dependencies) - : base(dependencies) - { - } - protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies) => new NonRetryingExecutionStrategy(dependencies); } @@ -589,24 +585,17 @@ private static IServiceProvider CreateServiceProvider() .AddScoped() .BuildServiceProvider(); - protected class BloggingContext : DbContext + protected class BloggingContext(string connectionString) : DbContext { - private readonly string _connectionString; - public BloggingContext(NpgsqlTestStore testStore) : this(testStore.ConnectionString) { } - public BloggingContext(string connectionString) - { - _connectionString = connectionString; - } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .UseNpgsql( - _connectionString, b => b + connectionString, b => b .ApplyConfiguration() .SetPostgresVersion(TestEnvironment.PostgresVersion)) .UseInternalServiceProvider(CreateServiceProvider()); @@ -641,16 +630,12 @@ public class Blog public byte[] AndRow { get; set; } } - public class TestDatabaseCreator : NpgsqlDatabaseCreator + public class TestDatabaseCreator( + RelationalDatabaseCreatorDependencies dependencies, + INpgsqlRelationalConnection connection, + IRawSqlCommandBuilder rawSqlCommandBuilder) + : NpgsqlDatabaseCreator(dependencies, connection, rawSqlCommandBuilder) { - public TestDatabaseCreator( - RelationalDatabaseCreatorDependencies dependencies, - INpgsqlRelationalConnection connection, - IRawSqlCommandBuilder rawSqlCommandBuilder) - : base(dependencies, connection, rawSqlCommandBuilder) - { - } - public bool HasTablesBase() => HasTables(); diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlServiceCollectionExtensionsTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlServiceCollectionExtensionsTest.cs index efbc1e333..47173ca15 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlServiceCollectionExtensionsTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlServiceCollectionExtensionsTest.cs @@ -2,10 +2,4 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class NpgsqlServiceCollectionExtensionsTest : RelationalServiceCollectionExtensionsTestBase -{ - public NpgsqlServiceCollectionExtensionsTest() - : base(NpgsqlTestHelpers.Instance) - { - } -} +public class NpgsqlServiceCollectionExtensionsTest() : RelationalServiceCollectionExtensionsTestBase(NpgsqlTestHelpers.Instance); diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs index b7de014d9..6f663d7d9 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs @@ -30,13 +30,7 @@ public void Insert_with_sequence_id() } } - public class BlogContextSequence : ContextBase - { - public BlogContextSequence(string databaseName) - : base(databaseName) - { - } - } + public class BlogContextSequence(string databaseName) : ContextBase(databaseName); [Fact] public void Insert_with_sequence_HiLo() @@ -61,13 +55,8 @@ public void Insert_with_sequence_HiLo() } } - public class BlogContextHiLo : ContextBase + public class BlogContextHiLo(string databaseName) : ContextBase(databaseName) { - public BlogContextHiLo(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -117,13 +106,8 @@ public void Insert_with_default_value_from_sequence() } } - public class BlogContextDefaultValue : ContextBase + public class BlogContextDefaultValue(string databaseName) : ContextBase(databaseName) { - public BlogContextDefaultValue(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -140,13 +124,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } - public class BlogContextDefaultValueNoMigrations : ContextBase + public class BlogContextDefaultValueNoMigrations(string databaseName) : ContextBase(databaseName) { - public BlogContextDefaultValueNoMigrations(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -187,13 +166,8 @@ public void Insert_with_key_default_value_from_sequence() } } - public class BlogContextKeyColumnWithDefaultValue : ContextBase + public class BlogContextKeyColumnWithDefaultValue(string databaseName) : ContextBase(databaseName) { - public BlogContextKeyColumnWithDefaultValue(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -233,13 +207,8 @@ public void Insert_uint_to_Identity_column_using_value_converter() } } - public class BlogContextUIntToIdentityUsingValueConverter : ContextBase + public class BlogContextUIntToIdentityUsingValueConverter(string databaseName) : ContextBase(databaseName) { - public BlogContextUIntToIdentityUsingValueConverter(string databaseName) - : base(databaseName) - { - } - public DbSet UnsignedBlogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -282,13 +251,8 @@ public void Insert_string_to_Identity_column_using_value_converter() } } - public class BlogContextStringToIdentityUsingValueConverter : ContextBase + public class BlogContextStringToIdentityUsingValueConverter(string databaseName) : ContextBase(databaseName) { - public BlogContextStringToIdentityUsingValueConverter(string databaseName) - : base(databaseName) - { - } - public DbSet StringyBlogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -332,13 +296,8 @@ public void Insert_with_explicit_non_default_keys() } } - public class BlogContextNoKeyGeneration : ContextBase + public class BlogContextNoKeyGeneration(string databaseName) : ContextBase(databaseName) { - public BlogContextNoKeyGeneration(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -373,13 +332,8 @@ public void Insert_with_explicit_with_default_keys() } } - public class BlogContextNoKeyGenerationNullableKey : ContextBase + public class BlogContextNoKeyGenerationNullableKey(string databaseName) : ContextBase(databaseName) { - public BlogContextNoKeyGenerationNullableKey(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -431,13 +385,8 @@ public void Insert_with_non_key_default_value() } } - public class BlogContextNonKeyDefaultValue : ContextBase + public class BlogContextNonKeyDefaultValue(string databaseName) : ContextBase(databaseName) { - public BlogContextNonKeyDefaultValue(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -490,13 +439,8 @@ public void Insert_with_non_key_default_value_readonly() } } - public class BlogContextNonKeyReadOnlyDefaultValue : ContextBase + public class BlogContextNonKeyReadOnlyDefaultValue(string databaseName) : ContextBase(databaseName) { - public BlogContextNonKeyReadOnlyDefaultValue(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -533,13 +477,8 @@ public void Insert_with_serial_non_id() } } - public class BlogContextSequenceNonId : ContextBase + public class BlogContextSequenceNonId(string databaseName) : ContextBase(databaseName) { - public BlogContextSequenceNonId(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -574,13 +513,7 @@ public void Insert_with_client_generated_GUID_key() } } - public class BlogContext : ContextBase - { - public BlogContext(string databaseName) - : base(databaseName) - { - } - } + public class BlogContext(string databaseName) : ContextBase(databaseName); [Fact] public void Insert_with_server_generated_GUID_key() @@ -617,13 +550,8 @@ public void Insert_with_server_generated_GUID_key() } } - public class BlogContextServerGuidKey : ContextBase + public class BlogContextServerGuidKey(string databaseName) : ContextBase(databaseName) { - public BlogContextServerGuidKey(string databaseName) - : base(databaseName) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasPostgresExtension("uuid-ossp"); @@ -676,15 +604,8 @@ public class ConcurrentBlog public byte[] Timestamp { get; set; } } - public abstract class ContextBase : DbContext + public abstract class ContextBase(string databaseName) : DbContext { - private readonly string _databaseName; - - protected ContextBase(string databaseName) - { - _databaseName = databaseName; - } - public DbSet Blogs { get; set; } public DbSet NullableKeyBlogs { get; set; } public DbSet FullNameBlogs { get; set; } @@ -695,7 +616,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .EnableServiceProviderCaching(false) .UseNpgsql( - NpgsqlTestStore.CreateConnectionString(_databaseName), + NpgsqlTestStore.CreateConnectionString(databaseName), b => b.ApplyConfiguration()); protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/test/EFCore.PG.FunctionalTests/OptimisticConcurrencyNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/OptimisticConcurrencyNpgsqlTest.cs index fbccf7ceb..e68095f3c 100644 --- a/test/EFCore.PG.FunctionalTests/OptimisticConcurrencyNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/OptimisticConcurrencyNpgsqlTest.cs @@ -2,32 +2,16 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class OptimisticConcurrencyBytesNpgsqlTest : OptimisticConcurrencyNpgsqlTestBase -{ - public OptimisticConcurrencyBytesNpgsqlTest(F1BytesNpgsqlFixture fixture) - : base(fixture) - { - } -} +public class OptimisticConcurrencyBytesNpgsqlTest(F1BytesNpgsqlFixture fixture) + : OptimisticConcurrencyNpgsqlTestBase(fixture); // uint maps directly to xid, which is the PG type of the xmin column that we use as a row version. -public class OptimisticConcurrencyNpgsqlTest : OptimisticConcurrencyNpgsqlTestBase -{ - public OptimisticConcurrencyNpgsqlTest(F1NpgsqlFixture fixture) - : base(fixture) - { - } -} +public class OptimisticConcurrencyNpgsqlTest(F1NpgsqlFixture fixture) : OptimisticConcurrencyNpgsqlTestBase(fixture); -public abstract class OptimisticConcurrencyNpgsqlTestBase - : OptimisticConcurrencyRelationalTestBase +public abstract class OptimisticConcurrencyNpgsqlTestBase(TFixture fixture) + : OptimisticConcurrencyRelationalTestBase(fixture) where TFixture : F1RelationalFixture, new() { - protected OptimisticConcurrencyNpgsqlTestBase(TFixture fixture) - : base(fixture) - { - } - [ConditionalFact] public async Task Modifying_concurrency_token_only_is_noop() { diff --git a/test/EFCore.PG.FunctionalTests/OverzealousInitializationNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/OverzealousInitializationNpgsqlTest.cs index 51650b7bd..103eea732 100644 --- a/test/EFCore.PG.FunctionalTests/OverzealousInitializationNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/OverzealousInitializationNpgsqlTest.cs @@ -2,14 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class OverzealousInitializationNpgsqlTest - : OverzealousInitializationTestBase +public class OverzealousInitializationNpgsqlTest(OverzealousInitializationNpgsqlTest.OverzealousInitializationNpgsqlFixture fixture) + : OverzealousInitializationTestBase(fixture) { - public OverzealousInitializationNpgsqlTest(OverzealousInitializationNpgsqlFixture fixture) - : base(fixture) - { - } - public class OverzealousInitializationNpgsqlFixture : OverzealousInitializationFixtureBase { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/PropertyValuesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/PropertyValuesNpgsqlTest.cs index 7473cba9d..2f9d6bfed 100644 --- a/test/EFCore.PG.FunctionalTests/PropertyValuesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/PropertyValuesNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class PropertyValuesNpgsqlTest : PropertyValuesTestBase +public class PropertyValuesNpgsqlTest(PropertyValuesNpgsqlTest.PropertyValuesNpgsqlFixture fixture) + : PropertyValuesTestBase(fixture) { - public PropertyValuesNpgsqlTest(PropertyValuesNpgsqlFixture fixture) - : base(fixture) - { - } - public class PropertyValuesNpgsqlFixture : PropertyValuesFixtureBase { protected override string StoreName { get; } = "PropertyValues"; diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index 5703c1eeb..629e474f5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class ArrayArrayQueryTest : ArrayQueryTest +public class ArrayArrayQueryTest(ArrayArrayQueryTest.ArrayArrayQueryFixture fixture, ITestOutputHelper testOutputHelper) + : ArrayQueryTest(fixture, testOutputHelper) { - public ArrayArrayQueryTest(ArrayArrayQueryFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - #region Indexers public override async Task Index_with_constant(bool async) diff --git a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs index e48fcd9bd..44b42b2b9 100644 --- a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs @@ -128,15 +128,10 @@ await AssertQuery( private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class BigIntegerQueryContext : PoolableDbContext + public class BigIntegerQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet Entities { get; set; } - public BigIntegerQueryContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(BigIntegerQueryContext context) { context.Entities.AddRange(BigIntegerData.CreateEntities()); @@ -198,12 +193,7 @@ public IReadOnlyDictionary EntityAsserters protected class BigIntegerData : ISetSource { - public IReadOnlyList Entities { get; } - - public BigIntegerData() - { - Entities = CreateEntities(); - } + public IReadOnlyList Entities { get; } = CreateEntities(); public IQueryable Set() where TEntity : class diff --git a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs index 088c43ec9..20d48e02e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs @@ -294,15 +294,10 @@ protected CitextQueryContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class CitextQueryContext : PoolableDbContext + public class CitextQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet SomeEntities { get; set; } - public CitextQueryContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(CitextQueryContext context) { context.SomeEntities.AddRange( diff --git a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs index ccf797ecd..19f73cf39 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs @@ -95,15 +95,10 @@ public class CompatibilityTestEntity public int SomeInt { get; set; } } - public class CompatibilityContext : DbContext + public class CompatibilityContext(DbContextOptions options) : DbContext(options) { public DbSet TestEntities { get; set; } - public CompatibilityContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(CompatibilityContext context) { context.TestEntities.AddRange( diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index b68ebbb89..5676f51a1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -217,16 +217,11 @@ protected EnumContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class EnumContext : PoolableDbContext + public class EnumContext(DbContextOptions options) : PoolableDbContext(options) { // ReSharper disable once UnusedAutoPropertyAccessor.Global public DbSet SomeEntities { get; set; } - public EnumContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) => builder .HasPostgresEnum("mapped_enum", new[] { "happy", "sad" }) diff --git a/test/EFCore.PG.FunctionalTests/Query/FieldsOnlyLoadNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FieldsOnlyLoadNpgsqlTest.cs index 76b581831..9e75660b0 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FieldsOnlyLoadNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FieldsOnlyLoadNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class FieldsOnlyLoadNpgsqlTest : FieldsOnlyLoadTestBase +public class FieldsOnlyLoadNpgsqlTest(FieldsOnlyLoadNpgsqlTest.FieldsOnlyLoadNpgsqlFixture fixture) + : FieldsOnlyLoadTestBase(fixture) { - public FieldsOnlyLoadNpgsqlTest(FieldsOnlyLoadNpgsqlFixture fixture) - : base(fixture) - { - } - public class FieldsOnlyLoadNpgsqlFixture : FieldsOnlyLoadFixtureBase { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs index c94935591..06fd4c016 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs @@ -3,13 +3,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class FromSqlQueryNpgsqlTest : FromSqlQueryTestBase> +public class FromSqlQueryNpgsqlTest(NorthwindQueryNpgsqlFixture fixture) + : FromSqlQueryTestBase>(fixture) { - public FromSqlQueryNpgsqlTest(NorthwindQueryNpgsqlFixture fixture) - : base(fixture) - { - } - [ConditionalTheory(Skip = "https://github.com/aspnet/EntityFramework/issues/{6563,20364}")] public override Task Bad_data_error_handling_invalid_cast(bool async) => base.Bad_data_error_handling_invalid_cast(async); diff --git a/test/EFCore.PG.FunctionalTests/Query/InheritanceRelationshipsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/InheritanceRelationshipsQueryNpgsqlTest.cs index 2df5c0cb7..6fbb0e034 100644 --- a/test/EFCore.PG.FunctionalTests/Query/InheritanceRelationshipsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/InheritanceRelationshipsQueryNpgsqlTest.cs @@ -1,11 +1,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class InheritanceRelationshipsQueryNpgsqlTest : InheritanceRelationshipsQueryTestBase +public class InheritanceRelationshipsQueryNpgsqlTest(InheritanceRelationshipsQueryNpgsqlFixture fixture) + : InheritanceRelationshipsQueryTestBase(fixture) { - public InheritanceRelationshipsQueryNpgsqlTest(InheritanceRelationshipsQueryNpgsqlFixture fixture) - : base(fixture) - { - } - protected override void ClearLog() { } } diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs index a0f3e9980..b8bc094a2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs @@ -593,16 +593,11 @@ protected JsonDomQueryContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class JsonDomQueryContext : PoolableDbContext + public class JsonDomQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet JsonbEntities { get; set; } public DbSet JsonEntities { get; set; } - public JsonDomQueryContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(JsonDomQueryContext context) { var (customer1, customer2, customer3) = (CreateCustomer1(), CreateCustomer2(), CreateCustomer3()); diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs index f804c6040..3bb952e3e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs @@ -704,16 +704,11 @@ protected JsonPocoQueryContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class JsonPocoQueryContext : PoolableDbContext + public class JsonPocoQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet JsonbEntities { get; set; } public DbSet JsonEntities { get; set; } - public JsonPocoQueryContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(JsonPocoQueryContext context) { context.JsonbEntities.AddRange( diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs index 5a9004d92..c44c70060 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs @@ -81,13 +81,8 @@ LIMIT 2 } } - protected class TypesDbContext : DbContext + protected class TypesDbContext(DbContextOptions options) : DbContext(options) { - public TypesDbContext(DbContextOptions options) - : base(options) - { - } - public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -387,13 +382,9 @@ private void SeedEnumLegacyValues(MyContextEnumLegacyValues ctx) 'e1') """); - private class MyContextEnumLegacyValues : DbContext + private class MyContextEnumLegacyValues(DbContextOptions options) : DbContext( + (new DbContextOptionsBuilder(options)).ConfigureWarnings(b => b.Log(CoreEventId.StringEnumValueInJson)).Options) { - public MyContextEnumLegacyValues(DbContextOptions options) - : base((new DbContextOptionsBuilder(options)).ConfigureWarnings(b => b.Log(CoreEventId.StringEnumValueInJson)).Options) - { - } - // ReSharper disable once UnusedAutoPropertyAccessor.Local public DbSet Entities { get; set; } diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs index 5f3d7bc0e..403b2be92 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs @@ -247,15 +247,10 @@ protected JsonStringQueryContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class JsonStringQueryContext : PoolableDbContext + public class JsonStringQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet JsonEntities { get; set; } - public JsonStringQueryContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(JsonStringQueryContext context) { const string customer1 = @" diff --git a/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs index 1e0777b51..67a8ecc95 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs @@ -502,15 +502,10 @@ protected LTreeQueryContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class LTreeQueryContext : PoolableDbContext + public class LTreeQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet LTreeEntities { get; set; } - public LTreeQueryContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(LTreeQueryContext context) { var ltreeEntities = new LTreeEntity[] diff --git a/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs index cb13429ec..02ec1ffab 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs @@ -79,14 +79,9 @@ private TimestampQueryContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class TimestampQueryContext : PoolableDbContext + public class TimestampQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet Entities { get; set; } - - public TimestampQueryContext(DbContextOptions options) - : base(options) - { - } } public class Entity @@ -149,9 +144,7 @@ protected override void Seed(TimestampQueryContext context) } [CollectionDefinition("LegacyTimestampQueryTest", DisableParallelization = true)] - public class EventSourceTestCollection - { - } + public class EventSourceTestCollection; } #endif diff --git a/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs index 0c6ad1b1c..f88be7e10 100644 --- a/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs @@ -749,15 +749,10 @@ public class MultirangeTestEntity private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class MultirangeContext : PoolableDbContext + public class MultirangeContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet TestEntities { get; set; } - public MultirangeContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(MultirangeContext context) { context.TestEntities.AddRange( diff --git a/test/EFCore.PG.FunctionalTests/Query/NavigationTest.cs b/test/EFCore.PG.FunctionalTests/Query/NavigationTest.cs index d5f412c76..bb9e565e5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NavigationTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NavigationTest.cs @@ -2,12 +2,12 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class NavigationTest : IClassFixture +public class NavigationTest(NavigationTestFixture fixture) : IClassFixture { [Fact] public void Duplicate_entries_are_not_created_for_navigations_to_principal() { - using var context = _fixture.CreateContext(); + using var context = fixture.CreateContext(); context.ConfigAction = modelBuilder => { @@ -31,7 +31,7 @@ public void Duplicate_entries_are_not_created_for_navigations_to_principal() [Fact] public void Duplicate_entries_are_not_created_for_navigations_to_dependant() { - using var context = _fixture.CreateContext(); + using var context = fixture.CreateContext(); context.ConfigAction = modelBuilder => { @@ -51,13 +51,6 @@ public void Duplicate_entries_are_not_created_for_navigations_to_dependant() "ForeignKey: GoTPerson {'SiblingReverseId'} -> GoTPerson {'Id'} ClientSetNull ToDependent: Siblings ToPrincipal: SiblingReverse", entityType.GetForeignKeys().Skip(1).First().ToString()); } - - private readonly NavigationTestFixture _fixture; - - public NavigationTest(NavigationTestFixture fixture) - { - _fixture = fixture; - } } public class GoTPerson @@ -71,13 +64,8 @@ public class GoTPerson public GoTPerson SiblingReverse { get; set; } } -public class GoTContext : DbContext +public class GoTContext(DbContextOptions options) : DbContext(options) { - public GoTContext(DbContextOptions options) - : base(options) - { - } - public DbSet People { get; set; } public Func ConfigAction { get; set; } diff --git a/test/EFCore.PG.FunctionalTests/Query/NullKeysNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NullKeysNpgsqlTest.cs index 8c465a89b..5a7e3dadf 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NullKeysNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NullKeysNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class NullKeysNpgsqlTest : NullKeysTestBase +public class NullKeysNpgsqlTest(NullKeysNpgsqlTest.NullKeysNpgsqlFixture fixture) + : NullKeysTestBase(fixture) { - public NullKeysNpgsqlTest(NullKeysNpgsqlFixture fixture) - : base(fixture) - { - } - public class NullKeysNpgsqlFixture : NullKeysFixtureBase { protected override string StoreName { get; } = "StringsContext"; diff --git a/test/EFCore.PG.FunctionalTests/Query/OwnedQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/OwnedQueryNpgsqlTest.cs index e5a0429a7..fd15a3621 100644 --- a/test/EFCore.PG.FunctionalTests/Query/OwnedQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/OwnedQueryNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class OwnedQueryNpgsqlTest : OwnedQueryRelationalTestBase +public class OwnedQueryNpgsqlTest(OwnedQueryNpgsqlTest.OwnedQueryNpgsqlFixture fixture) + : OwnedQueryRelationalTestBase(fixture) { - public OwnedQueryNpgsqlTest(OwnedQueryNpgsqlFixture fixture) - : base(fixture) - { - } - public class OwnedQueryNpgsqlFixture : RelationalOwnedQueryFixture { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs b/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs index 861b233b8..8b626b5c9 100644 --- a/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs @@ -41,13 +41,8 @@ public class Bug920Entity public Bug920Enum Enum { get; set; } } - private class Bug920Context : DbContext + private class Bug920Context(DbContextOptions options) : DbContext(options) { - public Bug920Context(DbContextOptions options) - : base(options) - { - } - public DbSet Entities { get; set; } } diff --git a/test/EFCore.PG.FunctionalTests/Query/QueryNoClientEvalNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/QueryNoClientEvalNpgsqlFixture.cs index ddfec4bfe..1e31adfbd 100644 --- a/test/EFCore.PG.FunctionalTests/Query/QueryNoClientEvalNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/QueryNoClientEvalNpgsqlFixture.cs @@ -1,5 +1,3 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class QueryNoClientEvalNpgsqlFixture : NorthwindQueryNpgsqlFixture -{ -} +public class QueryNoClientEvalNpgsqlFixture : NorthwindQueryNpgsqlFixture; diff --git a/test/EFCore.PG.FunctionalTests/Query/QueryNoClientEvalNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/QueryNoClientEvalNpgsqlTest.cs index 15816405a..d33862cdc 100644 --- a/test/EFCore.PG.FunctionalTests/Query/QueryNoClientEvalNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/QueryNoClientEvalNpgsqlTest.cs @@ -1,9 +1,4 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class QueryNoClientEvalNpgsqlTest : QueryNoClientEvalTestBase -{ - public QueryNoClientEvalNpgsqlTest(QueryNoClientEvalNpgsqlFixture fixture) - : base(fixture) - { - } -} +public class QueryNoClientEvalNpgsqlTest(QueryNoClientEvalNpgsqlFixture fixture) + : QueryNoClientEvalTestBase(fixture); diff --git a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs index 3e7000cf9..880ade010 100644 --- a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs @@ -654,15 +654,10 @@ public class RangeTestEntity public NpgsqlRange UserDefinedRangeWithSchema { get; set; } } - public class RangeContext : PoolableDbContext + public class RangeContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet RangeTestEntities { get; set; } - public RangeContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder builder) => builder.HasPostgresRange("doublerange", "double precision") .HasPostgresRange("test", "Schema_Range", "real"); diff --git a/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs index 7bd5ec4d1..fac71b515 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class SqlExecutorNpgsqlTest : SqlExecutorTestBase> +public class SqlExecutorNpgsqlTest(NorthwindQueryNpgsqlFixture fixture) + : SqlExecutorTestBase>(fixture) { - public SqlExecutorNpgsqlTest(NorthwindQueryNpgsqlFixture fixture) - : base(fixture) - { - } - protected override DbParameter CreateDbParameter(string name, object value) => new NpgsqlParameter { ParameterName = name, Value = value }; diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlTest.cs index bb7821813..ac1e24fcb 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCFiltersInheritanceQueryNpgsqlTest.cs @@ -1,9 +1,4 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class TPCFiltersInheritanceQueryNpgsqlTest : TPCFiltersInheritanceQueryTestBase -{ - public TPCFiltersInheritanceQueryNpgsqlTest(TPCFiltersInheritanceQueryNpgsqlFixture fixture) - : base(fixture) - { - } -} +public class TPCFiltersInheritanceQueryNpgsqlTest(TPCFiltersInheritanceQueryNpgsqlFixture fixture) + : TPCFiltersInheritanceQueryTestBase(fixture); diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlTest.cs index 3ce9b2085..6804a75a8 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCInheritanceQueryNpgsqlTest.cs @@ -1,12 +1,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class TPCInheritanceQueryNpgsqlTest : TPCInheritanceQueryTestBase +public class TPCInheritanceQueryNpgsqlTest(TPCInheritanceQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : TPCInheritanceQueryTestBase(fixture, testOutputHelper) { - public TPCInheritanceQueryNpgsqlTest(TPCInheritanceQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPHInheritanceQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPHInheritanceQueryNpgsqlTest.cs index 066e3e549..1eb085607 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPHInheritanceQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPHInheritanceQueryNpgsqlTest.cs @@ -1,9 +1,4 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class TPHInheritanceQueryNpgsqlTest : TPHInheritanceQueryTestBase -{ - public TPHInheritanceQueryNpgsqlTest(TPHInheritanceQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } -} +public class TPHInheritanceQueryNpgsqlTest(TPHInheritanceQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : TPHInheritanceQueryTestBase(fixture, testOutputHelper); diff --git a/test/EFCore.PG.FunctionalTests/Query/TPTInheritanceQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPTInheritanceQueryNpgsqlTest.cs index 5359e75f7..8ddc46103 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPTInheritanceQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPTInheritanceQueryNpgsqlTest.cs @@ -1,10 +1,4 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class TPTInheritanceQueryNpgsqlTest : TPTInheritanceQueryTestBase -{ - // ReSharper disable once UnusedParameter.Local - public TPTInheritanceQueryNpgsqlTest(TPTInheritanceQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } -} +public class TPTInheritanceQueryNpgsqlTest(TPTInheritanceQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : TPTInheritanceQueryTestBase(fixture, testOutputHelper); diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index 28ab41a48..b30d4e609 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -825,15 +825,10 @@ private TimestampQueryContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class TimestampQueryContext : PoolableDbContext + public class TimestampQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet Entities { get; set; } - public TimestampQueryContext(DbContextOptions options) - : base(options) - { - } - public static void Seed(TimestampQueryContext context) { context.Entities.AddRange(TimestampData.CreateEntities()); @@ -926,12 +921,7 @@ public IReadOnlyDictionary EntityAsserters protected class TimestampData : ISetSource { - public IReadOnlyList Entities { get; } - - public TimestampData() - { - Entities = CreateEntities(); - } + public IReadOnlyList Entities { get; } = CreateEntities(); public IQueryable Set() where TEntity : class diff --git a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs index fc1144d10..ec24c062d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs +++ b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs @@ -588,13 +588,8 @@ LIMIT 2 public override void Scalar_Function_with_nullable_value_return_type_throws() {} #endif - protected class NpgsqlUDFSqlContext : UDFSqlContext + protected class NpgsqlUDFSqlContext(DbContextOptions options) : UDFSqlContext(options) { - public NpgsqlUDFSqlContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/test/EFCore.PG.FunctionalTests/Query/WarningsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/WarningsNpgsqlTest.cs index 3211f7d17..2e10b22e0 100644 --- a/test/EFCore.PG.FunctionalTests/Query/WarningsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/WarningsNpgsqlTest.cs @@ -1,9 +1,3 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class WarningsNpgsqlTest : WarningsTestBase -{ - public WarningsNpgsqlTest(QueryNoClientEvalNpgsqlFixture fixture) - : base(fixture) - { - } -} +public class WarningsNpgsqlTest(QueryNoClientEvalNpgsqlFixture fixture) : WarningsTestBase(fixture); diff --git a/test/EFCore.PG.FunctionalTests/QueryExpressionInterceptionNpgsqlTestBase.cs b/test/EFCore.PG.FunctionalTests/QueryExpressionInterceptionNpgsqlTestBase.cs index 320f4b63b..8ce7a886d 100644 --- a/test/EFCore.PG.FunctionalTests/QueryExpressionInterceptionNpgsqlTestBase.cs +++ b/test/EFCore.PG.FunctionalTests/QueryExpressionInterceptionNpgsqlTestBase.cs @@ -4,13 +4,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public abstract class QueryExpressionInterceptionNpgsqlTestBase : QueryExpressionInterceptionTestBase +public abstract class QueryExpressionInterceptionNpgsqlTestBase( + QueryExpressionInterceptionNpgsqlTestBase.InterceptionNpgsqlFixtureBase fixture) + : QueryExpressionInterceptionTestBase(fixture) { - protected QueryExpressionInterceptionNpgsqlTestBase(InterceptionNpgsqlFixtureBase fixture) - : base(fixture) - { - } - public abstract class InterceptionNpgsqlFixtureBase : InterceptionFixtureBase { protected override ITestStoreFactory TestStoreFactory @@ -29,14 +26,9 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build } } - public class QueryExpressionInterceptionNpgsqlTest - : QueryExpressionInterceptionNpgsqlTestBase, IClassFixture + public class QueryExpressionInterceptionNpgsqlTest(QueryExpressionInterceptionNpgsqlTest.InterceptionNpgsqlFixture fixture) + : QueryExpressionInterceptionNpgsqlTestBase(fixture), IClassFixture { - public QueryExpressionInterceptionNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override string StoreName @@ -47,15 +39,11 @@ protected override bool ShouldSubscribeToDiagnosticListener } } - public class QueryExpressionInterceptionWithDiagnosticsNpgsqlTest - : QueryExpressionInterceptionNpgsqlTestBase, + public class QueryExpressionInterceptionWithDiagnosticsNpgsqlTest( + QueryExpressionInterceptionWithDiagnosticsNpgsqlTest.InterceptionNpgsqlFixture fixture) + : QueryExpressionInterceptionNpgsqlTestBase(fixture), IClassFixture { - public QueryExpressionInterceptionWithDiagnosticsNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override string StoreName diff --git a/test/EFCore.PG.FunctionalTests/SaveChangesInterceptionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/SaveChangesInterceptionNpgsqlTest.cs index b0ff63af5..c62c896b9 100644 --- a/test/EFCore.PG.FunctionalTests/SaveChangesInterceptionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/SaveChangesInterceptionNpgsqlTest.cs @@ -4,13 +4,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public abstract class SaveChangesInterceptionNpgsqlTestBase : SaveChangesInterceptionTestBase +public abstract class SaveChangesInterceptionNpgsqlTestBase(SaveChangesInterceptionNpgsqlTestBase.InterceptionNpgsqlFixtureBase fixture) + : SaveChangesInterceptionTestBase(fixture) { - protected SaveChangesInterceptionNpgsqlTestBase(InterceptionNpgsqlFixtureBase fixture) - : base(fixture) - { - } - public abstract class InterceptionNpgsqlFixtureBase : InterceptionFixtureBase { protected override ITestStoreFactory TestStoreFactory @@ -29,14 +25,9 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build } } - public class SaveChangesInterceptionNpgsqlTest - : SaveChangesInterceptionNpgsqlTestBase, IClassFixture + public class SaveChangesInterceptionNpgsqlTest(SaveChangesInterceptionNpgsqlTest.InterceptionNpgsqlFixture fixture) + : SaveChangesInterceptionNpgsqlTestBase(fixture), IClassFixture { - public SaveChangesInterceptionNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override string StoreName @@ -54,15 +45,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build } } - public class SaveChangesInterceptionWithDiagnosticsNpgsqlTest - : SaveChangesInterceptionNpgsqlTestBase, + public class SaveChangesInterceptionWithDiagnosticsNpgsqlTest( + SaveChangesInterceptionWithDiagnosticsNpgsqlTest.InterceptionNpgsqlFixture fixture) + : SaveChangesInterceptionNpgsqlTestBase(fixture), IClassFixture { - public SaveChangesInterceptionWithDiagnosticsNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override string StoreName diff --git a/test/EFCore.PG.FunctionalTests/SeedingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/SeedingNpgsqlTest.cs index 8844da5a8..90df831ea 100644 --- a/test/EFCore.PG.FunctionalTests/SeedingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/SeedingNpgsqlTest.cs @@ -10,13 +10,8 @@ protected override TestStore TestStore protected override SeedingContext CreateContextWithEmptyDatabase(string testId) => new SeedingNpgsqlContext(testId); - protected class SeedingNpgsqlContext : SeedingContext + protected class SeedingNpgsqlContext(string testId) : SeedingContext(testId) { - public SeedingNpgsqlContext(string testId) - : base(testId) - { - } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseNpgsql(NpgsqlTestStore.CreateConnectionString($"Seeds{TestId}")); } diff --git a/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs b/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs index 51d9e76a3..9b4d29b8a 100644 --- a/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs +++ b/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs @@ -263,23 +263,14 @@ private static void AddEntitiesWithIds(IServiceProvider serviceProvider, int idO context.SaveChanges(); } - private class BronieContext : DbContext + private class BronieContext(IServiceProvider serviceProvider, string databaseName) : DbContext { - private readonly IServiceProvider _serviceProvider; - private readonly string _databaseName; - - public BronieContext(IServiceProvider serviceProvider, string databaseName) - { - _serviceProvider = serviceProvider; - _databaseName = databaseName; - } - public DbSet Pegasuses { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder - .UseInternalServiceProvider(_serviceProvider) - .UseNpgsql(NpgsqlTestStore.CreateConnectionString(_databaseName), b => b.ApplyConfiguration()); + .UseInternalServiceProvider(serviceProvider) + .UseNpgsql(NpgsqlTestStore.CreateConnectionString(databaseName), b => b.ApplyConfiguration()); protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity( @@ -366,32 +357,22 @@ private static void AddEntitiesNullable(IServiceProvider serviceProvider, string context.SaveChanges(); } - private class NullableBronieContext : DbContext + private class NullableBronieContext(IServiceProvider serviceProvider, string databaseName, bool useSequence) + : DbContext { - private readonly IServiceProvider _serviceProvider; - private readonly string _databaseName; - private readonly bool _useSequence; - - public NullableBronieContext(IServiceProvider serviceProvider, string databaseName, bool useSequence) - { - _serviceProvider = serviceProvider; - _databaseName = databaseName; - _useSequence = useSequence; - } - public DbSet Unicons { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder - .UseInternalServiceProvider(_serviceProvider) - .UseNpgsql(NpgsqlTestStore.CreateConnectionString(_databaseName), b => b.ApplyConfiguration()); + .UseInternalServiceProvider(serviceProvider) + .UseNpgsql(NpgsqlTestStore.CreateConnectionString(databaseName), b => b.ApplyConfiguration()); protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity( b => { b.HasKey(e => e.Identifier); - if (_useSequence) + if (useSequence) { b.Property(e => e.Identifier).UseHiLo(); } @@ -408,12 +389,7 @@ private class Unicon public string Name { get; set; } } - public SequenceEndToEndTest() - { - TestStore = NpgsqlTestStore.CreateInitialized("SequenceEndToEndTest"); - } - - protected NpgsqlTestStore TestStore { get; } + protected NpgsqlTestStore TestStore { get; } = NpgsqlTestStore.CreateInitialized("SequenceEndToEndTest"); public void Dispose() => TestStore.Dispose(); diff --git a/test/EFCore.PG.FunctionalTests/SerializationNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/SerializationNpgsqlTest.cs index 8fb43e9e4..c0f16c72e 100644 --- a/test/EFCore.PG.FunctionalTests/SerializationNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/SerializationNpgsqlTest.cs @@ -1,9 +1,3 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class SerializationNpgsqlTest : SerializationTestBase -{ - public SerializationNpgsqlTest(F1BytesNpgsqlFixture fixture) - : base(fixture) - { - } -} +public class SerializationNpgsqlTest(F1BytesNpgsqlFixture fixture) : SerializationTestBase(fixture); diff --git a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs index 17772cfb0..8f610757f 100644 --- a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs @@ -3,13 +3,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; [RequiresPostgis] -public class SpatialNpgsqlTest : SpatialTestBase +public class SpatialNpgsqlTest(SpatialNpgsqlFixture fixture) : SpatialTestBase(fixture) { - public SpatialNpgsqlTest(SpatialNpgsqlFixture fixture) - : base(fixture) - { - } - protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); } diff --git a/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs index 1b967b6e2..1133cb7f3 100644 --- a/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs @@ -2,14 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class StoreGeneratedFixupNpgsqlTest : StoreGeneratedFixupRelationalTestBase< - StoreGeneratedFixupNpgsqlTest.StoreGeneratedFixupNpgsqlFixture> +public class StoreGeneratedFixupNpgsqlTest(StoreGeneratedFixupNpgsqlTest.StoreGeneratedFixupNpgsqlFixture fixture) + : StoreGeneratedFixupRelationalTestBase(fixture) { - public StoreGeneratedFixupNpgsqlTest(StoreGeneratedFixupNpgsqlFixture fixture) - : base(fixture) - { - } - [Fact] public void Temp_values_are_replaced_on_save() => ExecuteWithStrategyInTransaction( diff --git a/test/EFCore.PG.FunctionalTests/StoreGeneratedNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/StoreGeneratedNpgsqlTest.cs index 4765fe20e..bef3d8b23 100644 --- a/test/EFCore.PG.FunctionalTests/StoreGeneratedNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/StoreGeneratedNpgsqlTest.cs @@ -2,14 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class StoreGeneratedNpgsqlTest - : StoreGeneratedTestBase +public class StoreGeneratedNpgsqlTest(StoreGeneratedNpgsqlTest.StoreGeneratedNpgsqlFixture fixture) + : StoreGeneratedTestBase(fixture) { - public StoreGeneratedNpgsqlTest(StoreGeneratedNpgsqlFixture fixture) - : base(fixture) - { - } - protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); diff --git a/test/EFCore.PG.FunctionalTests/SystemColumnTest.cs b/test/EFCore.PG.FunctionalTests/SystemColumnTest.cs index 44f5c3500..cd7150436 100644 --- a/test/EFCore.PG.FunctionalTests/SystemColumnTest.cs +++ b/test/EFCore.PG.FunctionalTests/SystemColumnTest.cs @@ -30,13 +30,8 @@ public void Xmin() Assert.NotEqual(firstVersion, secondVersion); } - public class SystemColumnContext : PoolableDbContext + public class SystemColumnContext(DbContextOptions options) : PoolableDbContext(options) { - public SystemColumnContext(DbContextOptions options) - : base(options) - { - } - // ReSharper disable once UnusedAutoPropertyAccessor.Local public DbSet Entities { get; set; } diff --git a/test/EFCore.PG.FunctionalTests/TPTTableSplittingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/TPTTableSplittingNpgsqlTest.cs index 941ce5c47..c89e21ae6 100644 --- a/test/EFCore.PG.FunctionalTests/TPTTableSplittingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/TPTTableSplittingNpgsqlTest.cs @@ -2,13 +2,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class TPTTableSplittingNpgsqlTest : TPTTableSplittingTestBase +public class TPTTableSplittingNpgsqlTest(ITestOutputHelper testOutputHelper) : TPTTableSplittingTestBase(testOutputHelper) { - public TPTTableSplittingNpgsqlTest(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - public override Task Can_insert_dependent_with_just_one_parent() // This scenario is not valid for TPT => Task.CompletedTask; diff --git a/test/EFCore.PG.FunctionalTests/TableSplittingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/TableSplittingNpgsqlTest.cs index 74c511879..d47776766 100644 --- a/test/EFCore.PG.FunctionalTests/TableSplittingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/TableSplittingNpgsqlTest.cs @@ -4,13 +4,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; [MinimumPostgresVersion(12, 0)] // Test suite uses computed columns -public class TableSplittingNpgsqlTest : TableSplittingTestBase +public class TableSplittingNpgsqlTest(ITestOutputHelper testOutputHelper) : TableSplittingTestBase(testOutputHelper) { - public TableSplittingNpgsqlTest(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs index 5b8cb32b0..ea7ef0197 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs @@ -1,15 +1,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Array; -public class ArrayQueryContext : PoolableDbContext +public class ArrayQueryContext(DbContextOptions options) : PoolableDbContext(options) { public DbSet SomeEntities { get; set; } public DbSet SomeEntityContainers { get; set; } - public ArrayQueryContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity( e => diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs index 7b056188e..55dd61c1c 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs @@ -2,13 +2,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Array; public class ArrayQueryData : ISetSource { - public IReadOnlyList ArrayEntities { get; } - public IReadOnlyList ContainerEntities { get; } - - public ArrayQueryData() - { - (ArrayEntities, ContainerEntities) = (CreateArrayEntities(), CreateContainerEntities()); - } + public IReadOnlyList ArrayEntities { get; } = CreateArrayEntities(); + public IReadOnlyList ContainerEntities { get; } = CreateContainerEntities(); public IQueryable Set() where TEntity : class diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Northwind/NorthwindNpgsqlContext.cs b/test/EFCore.PG.FunctionalTests/TestModels/Northwind/NorthwindNpgsqlContext.cs index b0a47a54f..fd80fe107 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Northwind/NorthwindNpgsqlContext.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Northwind/NorthwindNpgsqlContext.cs @@ -2,13 +2,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Northwind; -public class NorthwindNpgsqlContext : NorthwindRelationalContext +public class NorthwindNpgsqlContext(DbContextOptions options) : NorthwindRelationalContext(options) { - public NorthwindNpgsqlContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/MinimumPostgresVersionAttribute.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/MinimumPostgresVersionAttribute.cs index e7d1f56f7..86e224e7d 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/MinimumPostgresVersionAttribute.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/MinimumPostgresVersionAttribute.cs @@ -1,14 +1,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] -public sealed class MinimumPostgresVersionAttribute : Attribute, ITestCondition +public sealed class MinimumPostgresVersionAttribute(int major, int minor) : Attribute, ITestCondition { - private readonly Version _version; - - public MinimumPostgresVersionAttribute(int major, int minor) - { - _version = new Version(major, minor); - } + private readonly Version _version = new(major, minor); public ValueTask IsMetAsync() => new(TestEnvironment.PostgresVersion >= _version); diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs index f4f5c2071..d75de6be2 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs @@ -9,12 +9,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; public class NpgsqlDatabaseCleaner : RelationalDatabaseCleaner { - private readonly NpgsqlSqlGenerationHelper _sqlGenerationHelper; - - public NpgsqlDatabaseCleaner() - { - _sqlGenerationHelper = new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()); - } + private readonly NpgsqlSqlGenerationHelper _sqlGenerationHelper = new(new RelationalSqlGenerationHelperDependencies()); protected override IDatabaseModelFactory CreateDatabaseModelFactory(ILoggerFactory loggerFactory) => new NpgsqlDatabaseModelFactory( diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/TestNpgsqlConnection.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/TestNpgsqlConnection.cs index 5a2a46057..15ef578c7 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/TestNpgsqlConnection.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/TestNpgsqlConnection.cs @@ -4,13 +4,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; -public class TestNpgsqlConnection : NpgsqlRelationalConnection +public class TestNpgsqlConnection(RelationalConnectionDependencies dependencies, DbDataSource dataSource = null) + : NpgsqlRelationalConnection(dependencies, dataSource) { - public TestNpgsqlConnection(RelationalConnectionDependencies dependencies, DbDataSource dataSource = null) - : base(dependencies, dataSource) - { - } - public string ErrorCode { get; set; } = "XX000"; public Queue OpenFailures { get; } = new(); public int OpenCount { get; set; } diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs index 1746cd7dd..35e7dd1a4 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs @@ -2,32 +2,20 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; -public class TestRelationalCommandBuilderFactory : IRelationalCommandBuilderFactory +public class TestRelationalCommandBuilderFactory(RelationalCommandBuilderDependencies dependencies) : IRelationalCommandBuilderFactory { - public TestRelationalCommandBuilderFactory( - RelationalCommandBuilderDependencies dependencies) - { - Dependencies = dependencies; - } - - public RelationalCommandBuilderDependencies Dependencies { get; } + public RelationalCommandBuilderDependencies Dependencies { get; } = dependencies; public virtual IRelationalCommandBuilder Create() => new TestRelationalCommandBuilder(Dependencies); - private class TestRelationalCommandBuilder : IRelationalCommandBuilder + private class TestRelationalCommandBuilder(RelationalCommandBuilderDependencies dependencies) : IRelationalCommandBuilder { private readonly List _parameters = new(); - public TestRelationalCommandBuilder( - RelationalCommandBuilderDependencies dependencies) - { - Dependencies = dependencies; - } - public IndentedStringBuilder Instance { get; } = new(); - public RelationalCommandBuilderDependencies Dependencies { get; } + public RelationalCommandBuilderDependencies Dependencies { get; } = dependencies; public IReadOnlyList Parameters => _parameters; @@ -88,17 +76,13 @@ public int CommandTextLength => Instance.Length; } - private class TestRelationalCommand : IRelationalCommand + private class TestRelationalCommand( + RelationalCommandBuilderDependencies dependencies, + string commandText, + IReadOnlyList parameters) + : IRelationalCommand { - private readonly RelationalCommand _realRelationalCommand; - - public TestRelationalCommand( - RelationalCommandBuilderDependencies dependencies, - string commandText, - IReadOnlyList parameters) - { - _realRelationalCommand = new RelationalCommand(dependencies, commandText, parameters); - } + private readonly RelationalCommand _realRelationalCommand = new(dependencies, commandText, parameters); public string CommandText => _realRelationalCommand.CommandText; diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalTransaction.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalTransaction.cs index 9cfe34475..89cfb1b93 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalTransaction.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalTransaction.cs @@ -2,14 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; -public class TestRelationalTransactionFactory : IRelationalTransactionFactory +public class TestRelationalTransactionFactory(RelationalTransactionFactoryDependencies dependencies) : IRelationalTransactionFactory { - public TestRelationalTransactionFactory(RelationalTransactionFactoryDependencies dependencies) - { - Dependencies = dependencies; - } - - protected virtual RelationalTransactionFactoryDependencies Dependencies { get; } + protected virtual RelationalTransactionFactoryDependencies Dependencies { get; } = dependencies; public RelationalTransaction Create( IRelationalConnection connection, @@ -20,20 +15,15 @@ public RelationalTransaction Create( => new TestRelationalTransaction(connection, transaction, logger, transactionOwned, Dependencies.SqlGenerationHelper); } -public class TestRelationalTransaction : RelationalTransaction +public class TestRelationalTransaction( + IRelationalConnection connection, + DbTransaction transaction, + IDiagnosticsLogger logger, + bool transactionOwned, + ISqlGenerationHelper sqlGenerationHelper) + : RelationalTransaction(connection, transaction, new Guid(), logger, transactionOwned, sqlGenerationHelper) { - private readonly TestNpgsqlConnection _testConnection; - - public TestRelationalTransaction( - IRelationalConnection connection, - DbTransaction transaction, - IDiagnosticsLogger logger, - bool transactionOwned, - ISqlGenerationHelper sqlGenerationHelper) - : base(connection, transaction, new Guid(), logger, transactionOwned, sqlGenerationHelper) - { - _testConnection = (TestNpgsqlConnection)connection; - } + private readonly TestNpgsqlConnection _testConnection = (TestNpgsqlConnection)connection; public override void Commit() { diff --git a/test/EFCore.PG.FunctionalTests/TransactionInterceptionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/TransactionInterceptionNpgsqlTest.cs index 05b57061c..7fe19ba50 100644 --- a/test/EFCore.PG.FunctionalTests/TransactionInterceptionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/TransactionInterceptionNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public abstract class TransactionInterceptionNpgsqlTestBase : TransactionInterceptionTestBase +public abstract class TransactionInterceptionNpgsqlTestBase(TransactionInterceptionNpgsqlTestBase.InterceptionNpgsqlFixtureBase fixture) + : TransactionInterceptionTestBase(fixture) { - protected TransactionInterceptionNpgsqlTestBase(InterceptionNpgsqlFixtureBase fixture) - : base(fixture) - { - } - public abstract class InterceptionNpgsqlFixtureBase : InterceptionFixtureBase { protected override string StoreName @@ -23,14 +19,9 @@ protected override IServiceCollection InjectInterceptors( => base.InjectInterceptors(serviceCollection.AddEntityFrameworkNpgsql(), injectedInterceptors); } - public class TransactionInterceptionNpgsqlTest - : TransactionInterceptionNpgsqlTestBase, IClassFixture + public class TransactionInterceptionNpgsqlTest(TransactionInterceptionNpgsqlTest.InterceptionNpgsqlFixture fixture) + : TransactionInterceptionNpgsqlTestBase(fixture), IClassFixture { - public TransactionInterceptionNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override bool ShouldSubscribeToDiagnosticListener @@ -38,14 +29,11 @@ protected override bool ShouldSubscribeToDiagnosticListener } } - public class TransactionInterceptionWithDiagnosticsNpgsqlTest - : TransactionInterceptionNpgsqlTestBase, IClassFixture + public class TransactionInterceptionWithDiagnosticsNpgsqlTest( + TransactionInterceptionWithDiagnosticsNpgsqlTest.InterceptionNpgsqlFixture fixture) + : TransactionInterceptionNpgsqlTestBase(fixture), + IClassFixture { - public TransactionInterceptionWithDiagnosticsNpgsqlTest(InterceptionNpgsqlFixture fixture) - : base(fixture) - { - } - public class InterceptionNpgsqlFixture : InterceptionNpgsqlFixtureBase { protected override bool ShouldSubscribeToDiagnosticListener diff --git a/test/EFCore.PG.FunctionalTests/TransactionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/TransactionNpgsqlTest.cs index a80111f5b..0eb3c9bc3 100644 --- a/test/EFCore.PG.FunctionalTests/TransactionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/TransactionNpgsqlTest.cs @@ -4,13 +4,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class TransactionNpgsqlTest : TransactionTestBase +public class TransactionNpgsqlTest(TransactionNpgsqlTest.TransactionNpgsqlFixture fixture) + : TransactionTestBase(fixture) { - public TransactionNpgsqlTest(TransactionNpgsqlFixture fixture) - : base(fixture) - { - } - public override Task SaveChanges_can_be_used_with_AutoTransactionBehavior_Never(bool async) // Npgsql batches the inserts, creating an implicit transaction which fails the test // (see https://github.com/npgsql/npgsql/issues/1307) diff --git a/test/EFCore.PG.FunctionalTests/TwoDatabasesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/TwoDatabasesNpgsqlTest.cs index 96ced3bd3..9a6ab976a 100644 --- a/test/EFCore.PG.FunctionalTests/TwoDatabasesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/TwoDatabasesNpgsqlTest.cs @@ -2,13 +2,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class TwoDatabasesNpgsqlTest : TwoDatabasesTestBase, IClassFixture +public class TwoDatabasesNpgsqlTest(NpgsqlFixture fixture) : TwoDatabasesTestBase(fixture), IClassFixture { - public TwoDatabasesNpgsqlTest(NpgsqlFixture fixture) - : base(fixture) - { - } - protected new NpgsqlFixture Fixture => (NpgsqlFixture)base.Fixture; diff --git a/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs index 51734f48a..5cebb5470 100644 --- a/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs @@ -2,14 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class ValueConvertersEndToEndNpgsqlTest - : ValueConvertersEndToEndTestBase +public class ValueConvertersEndToEndNpgsqlTest(ValueConvertersEndToEndNpgsqlTest.ValueConvertersEndToEndNpgsqlFixture fixture) + : ValueConvertersEndToEndTestBase(fixture) { - public ValueConvertersEndToEndNpgsqlTest(ValueConvertersEndToEndNpgsqlFixture fixture) - : base(fixture) - { - } - [ConditionalTheory(Skip = "DateTime and DateTimeOffset, https://github.com/dotnet/efcore/issues/26068")] public override void Can_insert_and_read_back_with_conversions(int[] valueOrder) => base.Can_insert_and_read_back_with_conversions(valueOrder); @@ -211,14 +206,9 @@ public class ValueConvertedArrayEntity public IntWrapper[] Values { get; set; } = null!; } - public class IntWrapper : IEquatable + public class IntWrapper(int value) : IEquatable { - public int Value { get; } - - public IntWrapper(int value) - { - Value = value; - } + public int Value { get; } = value; public bool Equals(IntWrapper? other) => other is not null && Value == other.Value; diff --git a/test/EFCore.PG.FunctionalTests/WithConstructorsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/WithConstructorsNpgsqlTest.cs index d82eec61b..e1337b284 100644 --- a/test/EFCore.PG.FunctionalTests/WithConstructorsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/WithConstructorsNpgsqlTest.cs @@ -2,13 +2,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class WithConstructorsNpgsqlTest : WithConstructorsTestBase +public class WithConstructorsNpgsqlTest(WithConstructorsNpgsqlTest.WithConstructorsNpgsqlFixture fixture) + : WithConstructorsTestBase(fixture) { - public WithConstructorsNpgsqlTest(WithConstructorsNpgsqlFixture fixture) - : base(fixture) - { - } - protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/LegacyNpgsqlNodaTimeTypeMappingTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/LegacyNpgsqlNodaTimeTypeMappingTest.cs index 4c25a7e8f..080a54bf3 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/LegacyNpgsqlNodaTimeTypeMappingTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/LegacyNpgsqlNodaTimeTypeMappingTest.cs @@ -98,9 +98,7 @@ public void Dispose() } [CollectionDefinition("LegacyNodaTimeTest", DisableParallelization = true)] - public class EventSourceTestCollection - { - } + public class EventSourceTestCollection; } #endif diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs index 16ec9a4cf..dabd1dba4 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs @@ -1854,13 +1854,8 @@ private NodaTimeContext CreateContext() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class NodaTimeContext : PoolableDbContext + public class NodaTimeContext(DbContextOptions options) : PoolableDbContext(options) { - public NodaTimeContext(DbContextOptions options) - : base(options) - { - } - // ReSharper disable once MemberHidesStaticFromOuterClass // ReSharper disable once UnusedAutoPropertyAccessor.Global public DbSet NodaTimeTypes { get; set; } @@ -1983,12 +1978,7 @@ public IReadOnlyDictionary EntityAsserters private class NodaTimeData : ISetSource { - private IReadOnlyList NodaTimeTypes { get; } - - public NodaTimeData() - { - NodaTimeTypes = CreateNodaTimeTypes(); - } + private IReadOnlyList NodaTimeTypes { get; } = CreateNodaTimeTypes(); public IQueryable Set() where TEntity : class diff --git a/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs b/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs index 9e8288852..287bbcd18 100644 --- a/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs +++ b/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs @@ -138,7 +138,5 @@ public void Can_access_relationship() a => a.Name.StartsWith(RelationalAnnotationNames.Prefix, StringComparison.Ordinal))); } - private class Splot - { - } + private class Splot; } diff --git a/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs b/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs index 38cee7e59..f7a4c5571 100644 --- a/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs +++ b/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs @@ -157,13 +157,8 @@ private static IHistoryRepository CreateHistoryRepository(string schema = null) .Options) .GetService(); - private class TestDbContext : DbContext + private class TestDbContext(DbContextOptions options) : DbContext(options) { - public TestDbContext(DbContextOptions options) - : base(options) - { - } - public DbSet Blogs { get; set; } [DbFunction("TableFunction")] diff --git a/test/EFCore.PG.Tests/NpgsqlDatabaseFacadeTest.cs b/test/EFCore.PG.Tests/NpgsqlDatabaseFacadeTest.cs index db82de596..e8e29ad87 100644 --- a/test/EFCore.PG.Tests/NpgsqlDatabaseFacadeTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlDatabaseFacadeTest.cs @@ -141,13 +141,8 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) } } - private class ProviderOnModelContext : ProviderContext + private class ProviderOnModelContext(DbContextOptions options) : ProviderContext(options) { - public ProviderOnModelContext(DbContextOptions options) - : base(options) - { - } - protected override void OnModelCreating(ModelBuilder modelBuilder) => IsNpgsqlSet = Database.IsNpgsql(); } @@ -162,13 +157,8 @@ public ProviderConstructorContext(DbContextOptions options) } } - private class ProviderUseInOnConfiguringContext : ProviderContext + private class ProviderUseInOnConfiguringContext(DbContextOptions options) : ProviderContext(options) { - public ProviderUseInOnConfiguringContext(DbContextOptions options) - : base(options) - { - } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => IsNpgsqlSet = Database.IsNpgsql(); } diff --git a/test/EFCore.PG.Tests/NpgsqlDbContextOptionsExtensionsTest.cs b/test/EFCore.PG.Tests/NpgsqlDbContextOptionsExtensionsTest.cs index 2b4cfb7aa..516229e48 100644 --- a/test/EFCore.PG.Tests/NpgsqlDbContextOptionsExtensionsTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlDbContextOptionsExtensionsTest.cs @@ -116,11 +116,5 @@ public void Service_collection_extension_method_can_configure_sqlserver_options( } } - private class ApplicationDbContext : DbContext - { - public ApplicationDbContext(DbContextOptions options) - : base(options) - { - } - } + private class ApplicationDbContext(DbContextOptions options) : DbContext(options); } diff --git a/test/EFCore.PG.Tests/NpgsqlNetTopologySuiteApiConsistencyTest.cs b/test/EFCore.PG.Tests/NpgsqlNetTopologySuiteApiConsistencyTest.cs index 5febe0b86..5f52f567d 100644 --- a/test/EFCore.PG.Tests/NpgsqlNetTopologySuiteApiConsistencyTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlNetTopologySuiteApiConsistencyTest.cs @@ -1,13 +1,9 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class NpgsqlNetTopologySuiteApiConsistencyTest : ApiConsistencyTestBase< - NpgsqlNetTopologySuiteApiConsistencyTest.NpgsqlNetTopologySuiteApiConsistencyFixture> +public class NpgsqlNetTopologySuiteApiConsistencyTest( + NpgsqlNetTopologySuiteApiConsistencyTest.NpgsqlNetTopologySuiteApiConsistencyFixture fixture) + : ApiConsistencyTestBase(fixture) { - public NpgsqlNetTopologySuiteApiConsistencyTest(NpgsqlNetTopologySuiteApiConsistencyFixture fixture) - : base(fixture) - { - } - protected override void AddServices(ServiceCollection serviceCollection) => serviceCollection.AddEntityFrameworkNpgsqlNetTopologySuite(); diff --git a/test/EFCore.PG.Tests/NpgsqlNodaTimeApiConsistencyTest.cs b/test/EFCore.PG.Tests/NpgsqlNodaTimeApiConsistencyTest.cs index 9a76028f4..ff79bc1ce 100644 --- a/test/EFCore.PG.Tests/NpgsqlNodaTimeApiConsistencyTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlNodaTimeApiConsistencyTest.cs @@ -1,12 +1,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class NpgsqlNodaTimeApiConsistencyTest : ApiConsistencyTestBase +public class NpgsqlNodaTimeApiConsistencyTest(NpgsqlNodaTimeApiConsistencyTest.NpgsqlNodaTimeApiConsistencyFixture fixture) + : ApiConsistencyTestBase(fixture) { - public NpgsqlNodaTimeApiConsistencyTest(NpgsqlNodaTimeApiConsistencyFixture fixture) - : base(fixture) - { - } - protected override void AddServices(ServiceCollection serviceCollection) => serviceCollection.AddEntityFrameworkNpgsqlNodaTime(); diff --git a/test/EFCore.PG.Tests/Storage/LegacyNpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/LegacyNpgsqlTypeMappingTest.cs index 4c880d6ba..64400b9c4 100644 --- a/test/EFCore.PG.Tests/Storage/LegacyNpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/LegacyNpgsqlTypeMappingTest.cs @@ -76,8 +76,6 @@ public void Dispose() } [CollectionDefinition("LegacyDateTimeTest", DisableParallelization = true)] -public class EventSourceTestCollection -{ -} +public class EventSourceTestCollection; #endif diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs index cafb9d493..a1dca3a49 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -353,13 +353,9 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p } } - private class DummyType - { - } + private class DummyType; - private class UnknownType - { - } + private class UnknownType; protected IModel CreateEmptyModel() => CreateModelBuilder().Model.FinalizeModel(); diff --git a/test/EFCore.PG.Tests/TestUtilities/FakeProvider/FakeRelationalOptionsExtension.cs b/test/EFCore.PG.Tests/TestUtilities/FakeProvider/FakeRelationalOptionsExtension.cs index edf36f126..61b17e88e 100644 --- a/test/EFCore.PG.Tests/TestUtilities/FakeProvider/FakeRelationalOptionsExtension.cs +++ b/test/EFCore.PG.Tests/TestUtilities/FakeProvider/FakeRelationalOptionsExtension.cs @@ -32,13 +32,8 @@ public static IServiceCollection AddEntityFrameworkRelationalDatabase(IServiceCo return serviceCollection; } - private sealed class ExtensionInfo : RelationalExtensionInfo + private sealed class ExtensionInfo(IDbContextOptionsExtension extension) : RelationalExtensionInfo(extension) { - public ExtensionInfo(IDbContextOptionsExtension extension) - : base(extension) - { - } - public override void PopulateDebugInfo(IDictionary debugInfo) { } diff --git a/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchFactoryTest.cs b/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchFactoryTest.cs index fd1b15b1a..86fadf80f 100644 --- a/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchFactoryTest.cs +++ b/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchFactoryTest.cs @@ -82,9 +82,7 @@ public void MaxBatchSize_is_optional() Assert.True(batch.TryAddCommand(CreateModificationCommand("T1", null, false))); } - private class FakeDbContext : DbContext - { - } + private class FakeDbContext : DbContext; private static INonTrackedModificationCommand CreateModificationCommand( string name, diff --git a/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchTest.cs b/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchTest.cs index 1fcd77b26..c242266e0 100644 --- a/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchTest.cs +++ b/test/EFCore.PG.Tests/Update/NpgsqlModificationCommandBatchTest.cs @@ -46,9 +46,7 @@ public void AddCommand_returns_false_when_max_batch_size_is_reached() CreateModificationCommand("T1", null, false))); } - private class FakeDbContext : DbContext - { - } + private class FakeDbContext : DbContext; private static INonTrackedModificationCommand CreateModificationCommand( string name, From e6d545ace2294a14140744cd32c33adf68e4e3c7 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 6 Jan 2024 18:27:57 +0100 Subject: [PATCH 021/107] Automatic usage of collection initializers (#3051) --- EFCore.PG.sln | 1 + ...uiteAggregateMethodCallTranslatorPlugin.cs | 8 +- ...lNetTopologySuiteMemberTranslatorPlugin.cs | 58 +++---- ...TopologySuiteMethodCallTranslatorPlugin.cs | 60 +++---- ...TimeAggregateMethodCallTranslatorPlugin.cs | 2 +- ...daTimeEvaluatableExpressionFilterPlugin.cs | 2 +- .../NpgsqlNodaTimeMemberTranslatorPlugin.cs | 2 +- ...pgsqlNodaTimeMethodCallTranslatorPlugin.cs | 48 +++--- .../Internal/DateIntervalRangeMapping.cs | 4 +- .../Storage/Internal/DateMapping.cs | 2 +- .../Storage/Internal/DateTimeZoneMapping.cs | 2 +- .../Internal/DurationIntervalMapping.cs | 10 +- .../Storage/Internal/IntervalRangeMapping.cs | 4 +- .../NpgsqlNodaTimeTypeMappingSourcePlugin.cs | 27 ++-- .../Storage/Internal/PeriodIntervalMapping.cs | 18 +-- .../Storage/Internal/TimeMapping.cs | 6 +- .../Storage/Internal/TimeTzMapping.cs | 12 +- .../Internal/TimestampLocalDateTimeMapping.cs | 6 +- .../Internal/TimestampTzInstantMapping.cs | 2 +- .../TimestampTzOffsetDateTimeMapping.cs | 6 +- .../TimestampTzZonedDateTimeMapping.cs | 4 +- .../Internal/NpgsqlAnnotationCodeGenerator.cs | 16 +- .../Internal/NpgsqlOptionsExtension.cs | 4 +- src/EFCore.PG/Internal/EnumerableMethods.cs | 12 +- .../Internal/NpgsqlSingletonOptions.cs | 2 +- .../Internal/NpgsqlAnnotationProvider.cs | 2 +- .../NpgsqlMigrationsSqlGenerator.cs | 2 +- .../Internal/NpgsqlArrayMethodTranslator.cs | 2 +- .../Internal/NpgsqlConvertTranslator.cs | 6 +- .../NpgsqlDateTimeMemberTranslator.cs | 2 +- .../NpgsqlDateTimeMethodTranslator.cs | 62 ++++---- .../NpgsqlFuzzyStringMatchMethodTranslator.cs | 18 +-- .../Internal/NpgsqlJsonDomTranslator.cs | 6 +- .../Internal/NpgsqlLTreeTranslator.cs | 4 +- .../Internal/NpgsqlLikeTranslator.cs | 8 +- .../Internal/NpgsqlMathTranslator.cs | 148 +++++++++--------- .../NpgsqlMiscAggregateMethodTranslator.cs | 4 +- .../Internal/NpgsqlNetworkTranslator.cs | 36 ++--- .../Internal/NpgsqlNewGuidTranslator.cs | 4 +- .../NpgsqlObjectToStringTranslator.cs | 8 +- .../Internal/NpgsqlRandomTranslator.cs | 6 +- .../Internal/NpgsqlRegexIsMatchTranslator.cs | 4 +- .../Internal/NpgsqlRowValueTranslator.cs | 2 +- .../Internal/NpgsqlStringMethodTranslator.cs | 52 +++--- .../NpgsqlTimeSpanMemberTranslator.cs | 2 +- .../NpgsqlTrigramsMethodTranslator.cs | 2 +- .../Internal/PgFunctionExpression.cs | 8 +- .../Internal/PgNewArrayExpression.cs | 2 +- .../NpgsqlEvaluatableExpressionFilter.cs | 4 +- ...yableMethodTranslatingExpressionVisitor.cs | 6 +- .../NpgsqlSqlTranslatingExpressionVisitor.cs | 14 +- .../Query/NpgsqlSqlExpressionFactory.cs | 6 +- .../Internal/NpgsqlCodeGenerator.cs | 4 +- .../Internal/NpgsqlDatabaseModelFactory.cs | 8 +- .../Internal/Mapping/NpgsqlBitTypeMapping.cs | 2 +- .../Internal/Mapping/NpgsqlCidrTypeMapping.cs | 4 +- .../Mapping/NpgsqlGeometricTypeMapping.cs | 18 +-- .../Internal/Mapping/NpgsqlInetTypeMapping.cs | 4 +- .../Internal/Mapping/NpgsqlJsonTypeMapping.cs | 2 +- .../Mapping/NpgsqlLTreeTypeMapping.cs | 2 +- .../Mapping/NpgsqlMacaddr8TypeMapping.cs | 2 +- .../Mapping/NpgsqlMacaddrTypeMapping.cs | 2 +- .../Mapping/NpgsqlOwnedJsonTypeMapping.cs | 6 +- .../Mapping/NpgsqlPgLsnTypeMapping.cs | 2 +- .../Mapping/NpgsqlRangeTypeMapping.cs | 6 +- .../Internal/Mapping/NpgsqlTidTypeMapping.cs | 2 +- .../Mapping/NpgsqlVarbitTypeMapping.cs | 2 +- .../Internal/NpgsqlSqlGenerationHelper.cs | 2 +- .../Internal/NpgsqlTypeMappingSource.cs | 136 ++++++++-------- .../ValueConversion/NpgsqlArrayConverter.cs | 6 +- .../Internal/NpgsqlUpdateSqlGenerator.cs | 2 +- src/EFCore.PG/Utilities/Util.cs | 24 +-- src/Shared/SharedTypeExtensions.cs | 4 +- .../BuiltInDataTypesNpgsqlTest.cs | 20 +-- .../NonSharedModelBulkUpdatesNpgsqlTest.cs | 2 +- .../JsonTypesNpgsqlTest.cs | 56 +++---- .../Migrations/MigrationsNpgsqlTest.cs | 32 ++-- .../NpgsqlMigrationsSqlGeneratorTest.cs | 15 +- .../NpgsqlApiConsistencyTest.cs | 22 +-- .../NpgsqlDatabaseCreatorTest.cs | 5 +- .../Query/ArrayListQueryTest.cs | 2 +- .../Query/ArrayQueryTest.cs | 17 +- .../Query/EnumQueryTest.cs | 2 +- .../Query/JsonPocoQueryTest.cs | 28 ++-- .../Query/JsonQueryAdHocNpgsqlTest.cs | 50 +++--- .../Query/LTreeQueryTest.cs | 2 +- .../Query/MultirangeQueryNpgsqlTest.cs | 40 +++-- .../QueryFilterFuncletizationNpgsqlTest.cs | 6 +- .../Query/RangeQueryNpgsqlTest.cs | 2 +- .../Query/SpatialQueryNpgsqlGeographyTest.cs | 2 +- .../Query/SpatialQueryNpgsqlGeometryTest.cs | 2 +- .../Query/TimestampQueryTest.cs | 4 +- .../NpgsqlDatabaseModelFactoryTest.cs | 56 +++---- .../TestModels/Array/ArrayQueryData.cs | 82 +++------- .../TestNpgsqlRetryingExecutionStrategy.cs | 2 +- .../TestRelationalCommandBuilderFactory.cs | 2 +- .../ValueConvertersEndToEndNpgsqlTest.cs | 2 +- .../LegacyNpgsqlNodaTimeTypeMappingTest.cs | 2 +- .../NodaTimeQueryNpgsqlTest.cs | 4 +- .../NpgsqlNodaTimeTypeMappingTest.cs | 2 +- .../NpgsqlAnnotationCodeGeneratorTest.cs | 4 +- .../Metadata/NpgsqlBuilderExtensionsTest.cs | 2 +- ...pgsqlNetTopologySuiteApiConsistencyTest.cs | 6 +- .../NpgsqlNodaTimeApiConsistencyTest.cs | 6 +- .../Scaffolding/NpgsqlCodeGeneratorTest.cs | 2 +- .../Storage/LegacyNpgsqlTypeMappingTest.cs | 4 +- .../Storage/NpgsqlArrayValueConverterTest.cs | 24 +-- .../Storage/NpgsqlTypeMappingSourceTest.cs | 2 +- .../Storage/NpgsqlTypeMappingTest.cs | 10 +- 109 files changed, 720 insertions(+), 779 deletions(-) diff --git a/EFCore.PG.sln b/EFCore.PG.sln index d1e61fc97..dd965ca0d 100644 --- a/EFCore.PG.sln +++ b/EFCore.PG.sln @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md NuGet.config = NuGet.config global.json = global.json + .editorconfig = .editorconfig EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8537E50E-CF7F-49CB-B4EF-3E2A1B11F050}" diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs index 6843da9df..c3a57fffa 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs @@ -52,16 +52,16 @@ public NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin( public class NpgsqlNetTopologySuiteAggregateMethodTranslator : IAggregateMethodCallTranslator { private static readonly MethodInfo GeometryCombineMethod - = typeof(GeometryCombiner).GetRuntimeMethod(nameof(GeometryCombiner.Combine), new[] { typeof(IEnumerable) })!; + = typeof(GeometryCombiner).GetRuntimeMethod(nameof(GeometryCombiner.Combine), [typeof(IEnumerable)])!; private static readonly MethodInfo ConvexHullMethod - = typeof(ConvexHull).GetRuntimeMethod(nameof(ConvexHull.Create), new[] { typeof(IEnumerable) })!; + = typeof(ConvexHull).GetRuntimeMethod(nameof(ConvexHull.Create), [typeof(IEnumerable)])!; private static readonly MethodInfo UnionMethod - = typeof(UnaryUnionOp).GetRuntimeMethod(nameof(UnaryUnionOp.Union), new[] { typeof(IEnumerable) })!; + = typeof(UnaryUnionOp).GetRuntimeMethod(nameof(UnaryUnionOp.Union), [typeof(IEnumerable)])!; private static readonly MethodInfo EnvelopeCombineMethod - = typeof(EnvelopeCombiner).GetRuntimeMethod(nameof(EnvelopeCombiner.CombineAsGeometry), new[] { typeof(IEnumerable) })!; + = typeof(EnvelopeCombiner).GetRuntimeMethod(nameof(EnvelopeCombiner.CombineAsGeometry), [typeof(IEnumerable)])!; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; private readonly IRelationalTypeMappingSource _typeMappingSource; diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs index 95726cb29..1ab9fd45b 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs @@ -44,7 +44,7 @@ public class NpgsqlGeometryMemberTranslator : IMemberTranslator private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly CaseWhenClause[] _ogcGeometryTypeWhenThenList; - private static readonly bool[][] TrueArrays = { Array.Empty(), new[] { true }, new[] { true, true }, new[] { true, true, true } }; + private static readonly bool[][] TrueArrays = [[], [true], [true, true], [true, true, true]]; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -59,8 +59,8 @@ public NpgsqlGeometryMemberTranslator( _sqlExpressionFactory = sqlExpressionFactory; _typeMappingSource = typeMappingSource; - _ogcGeometryTypeWhenThenList = new[] - { + _ogcGeometryTypeWhenThenList = + [ new CaseWhenClause( _sqlExpressionFactory.Constant("ST_CircularString"), _sqlExpressionFactory.Constant(OgcGeometryType.CircularString)), new CaseWhenClause( @@ -88,7 +88,7 @@ public NpgsqlGeometryMemberTranslator( _sqlExpressionFactory.Constant("ST_PolyhedralSurface"), _sqlExpressionFactory.Constant(OgcGeometryType.PolyhedralSurface)), new CaseWhenClause(_sqlExpressionFactory.Constant("ST_Tin"), _sqlExpressionFactory.Constant(OgcGeometryType.TIN)) - }; + ]; } /// @@ -127,7 +127,7 @@ public NpgsqlGeometryMemberTranslator( if (function is not null) { - return Function(function, new[] { instance }, typeof(double)); + return Function(function, [instance], typeof(double)); } } @@ -135,37 +135,37 @@ public NpgsqlGeometryMemberTranslator( { if (member.Name == "Count") { - return Function("ST_NumPoints", new[] { instance }, typeof(int)); + return Function("ST_NumPoints", [instance], typeof(int)); } } return member.Name switch { - nameof(Geometry.Area) => Function("ST_Area", new[] { instance }, typeof(double)), - nameof(Geometry.Boundary) => Function("ST_Boundary", new[] { instance }, typeof(Geometry), ResultGeometryMapping()), - nameof(Geometry.Centroid) => Function("ST_Centroid", new[] { instance }, typeof(Point), ResultGeometryMapping()), - nameof(GeometryCollection.Count) => Function("ST_NumGeometries", new[] { instance }, typeof(int)), - nameof(Geometry.Dimension) => Function("ST_Dimension", new[] { instance }, typeof(Dimension)), - nameof(LineString.EndPoint) => Function("ST_EndPoint", new[] { instance }, typeof(Point), ResultGeometryMapping()), - nameof(Geometry.Envelope) => Function("ST_Envelope", new[] { instance }, typeof(Geometry), ResultGeometryMapping()), - nameof(Polygon.ExteriorRing) => Function("ST_ExteriorRing", new[] { instance }, typeof(LineString), ResultGeometryMapping()), - nameof(Geometry.GeometryType) => Function("GeometryType", new[] { instance }, typeof(string)), - nameof(LineString.IsClosed) => Function("ST_IsClosed", new[] { instance }, typeof(bool)), - nameof(Geometry.IsEmpty) => Function("ST_IsEmpty", new[] { instance }, typeof(bool)), - nameof(LineString.IsRing) => Function("ST_IsRing", new[] { instance }, typeof(bool)), - nameof(Geometry.IsSimple) => Function("ST_IsSimple", new[] { instance }, typeof(bool)), - nameof(Geometry.IsValid) => Function("ST_IsValid", new[] { instance }, typeof(bool)), - nameof(Geometry.Length) => Function("ST_Length", new[] { instance }, typeof(double)), - nameof(Geometry.NumGeometries) => Function("ST_NumGeometries", new[] { instance }, typeof(int)), - nameof(Polygon.NumInteriorRings) => Function("ST_NumInteriorRings", new[] { instance }, typeof(int)), - nameof(Geometry.NumPoints) => Function("ST_NumPoints", new[] { instance }, typeof(int)), - nameof(Geometry.PointOnSurface) => Function("ST_PointOnSurface", new[] { instance }, typeof(Geometry), ResultGeometryMapping()), - nameof(Geometry.InteriorPoint) => Function("ST_PointOnSurface", new[] { instance }, typeof(Geometry), ResultGeometryMapping()), - nameof(Geometry.SRID) => Function("ST_SRID", new[] { instance }, typeof(int)), - nameof(LineString.StartPoint) => Function("ST_StartPoint", new[] { instance }, typeof(Point), ResultGeometryMapping()), + nameof(Geometry.Area) => Function("ST_Area", [instance], typeof(double)), + nameof(Geometry.Boundary) => Function("ST_Boundary", [instance], typeof(Geometry), ResultGeometryMapping()), + nameof(Geometry.Centroid) => Function("ST_Centroid", [instance], typeof(Point), ResultGeometryMapping()), + nameof(GeometryCollection.Count) => Function("ST_NumGeometries", [instance], typeof(int)), + nameof(Geometry.Dimension) => Function("ST_Dimension", [instance], typeof(Dimension)), + nameof(LineString.EndPoint) => Function("ST_EndPoint", [instance], typeof(Point), ResultGeometryMapping()), + nameof(Geometry.Envelope) => Function("ST_Envelope", [instance], typeof(Geometry), ResultGeometryMapping()), + nameof(Polygon.ExteriorRing) => Function("ST_ExteriorRing", [instance], typeof(LineString), ResultGeometryMapping()), + nameof(Geometry.GeometryType) => Function("GeometryType", [instance], typeof(string)), + nameof(LineString.IsClosed) => Function("ST_IsClosed", [instance], typeof(bool)), + nameof(Geometry.IsEmpty) => Function("ST_IsEmpty", [instance], typeof(bool)), + nameof(LineString.IsRing) => Function("ST_IsRing", [instance], typeof(bool)), + nameof(Geometry.IsSimple) => Function("ST_IsSimple", [instance], typeof(bool)), + nameof(Geometry.IsValid) => Function("ST_IsValid", [instance], typeof(bool)), + nameof(Geometry.Length) => Function("ST_Length", [instance], typeof(double)), + nameof(Geometry.NumGeometries) => Function("ST_NumGeometries", [instance], typeof(int)), + nameof(Polygon.NumInteriorRings) => Function("ST_NumInteriorRings", [instance], typeof(int)), + nameof(Geometry.NumPoints) => Function("ST_NumPoints", [instance], typeof(int)), + nameof(Geometry.PointOnSurface) => Function("ST_PointOnSurface", [instance], typeof(Geometry), ResultGeometryMapping()), + nameof(Geometry.InteriorPoint) => Function("ST_PointOnSurface", [instance], typeof(Geometry), ResultGeometryMapping()), + nameof(Geometry.SRID) => Function("ST_SRID", [instance], typeof(int)), + nameof(LineString.StartPoint) => Function("ST_StartPoint", [instance], typeof(Point), ResultGeometryMapping()), nameof(Geometry.OgcGeometryType) => _sqlExpressionFactory.Case( - Function("ST_GeometryType", new[] { instance }, typeof(string)), + Function("ST_GeometryType", [instance], typeof(string)), _ogcGeometryTypeWhenThenList, elseResult: null), diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs index fc0746aaf..25e0b1f34 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs @@ -53,9 +53,9 @@ public class NpgsqlGeometryMethodTranslator : IMethodCallTranslator private readonly IRelationalTypeMappingSource _typeMappingSource; private static readonly bool[][] TrueArrays = - { - Array.Empty(), new[] { true }, new[] { true, true }, new[] { true, true, true }, new[] { true, true, true, true } - }; + [ + [], [true], [true, true], [true, true, true], [true, true, true, true] + ]; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -157,65 +157,65 @@ public NpgsqlGeometryMethodTranslator( return method.Name switch { nameof(Geometry.AsBinary) - => Function("ST_AsBinary", new[] { instance }, typeof(byte[])), + => Function("ST_AsBinary", [instance], typeof(byte[])), nameof(Geometry.AsText) - => Function("ST_AsText", new[] { instance }, typeof(string)), + => Function("ST_AsText", [instance], typeof(string)), nameof(Geometry.Buffer) => Function("ST_Buffer", new[] { instance }.Concat(arguments).ToArray(), typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.Contains) - => Function("ST_Contains", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Contains", [instance, arguments[0]], typeof(bool)), nameof(Geometry.ConvexHull) - => Function("ST_ConvexHull", new[] { instance }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_ConvexHull", [instance], typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.CoveredBy) - => Function("ST_CoveredBy", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_CoveredBy", [instance, arguments[0]], typeof(bool)), nameof(Geometry.Covers) - => Function("ST_Covers", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Covers", [instance, arguments[0]], typeof(bool)), nameof(Geometry.Crosses) - => Function("ST_Crosses", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Crosses", [instance, arguments[0]], typeof(bool)), nameof(Geometry.Disjoint) - => Function("ST_Disjoint", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Disjoint", [instance, arguments[0]], typeof(bool)), nameof(Geometry.Difference) - => Function("ST_Difference", new[] { instance, arguments[0] }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_Difference", [instance, arguments[0]], typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.Distance) => Function("ST_Distance", new[] { instance }.Concat(arguments).ToArray(), typeof(double)), nameof(Geometry.EqualsExact) - => Function("ST_OrderingEquals", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_OrderingEquals", [instance, arguments[0]], typeof(bool)), nameof(Geometry.EqualsTopologically) - => Function("ST_Equals", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Equals", [instance, arguments[0]], typeof(bool)), nameof(Geometry.GetGeometryN) - => Function("ST_GeometryN", new[] { instance, OneBased(arguments[0]) }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_GeometryN", [instance, OneBased(arguments[0])], typeof(Geometry), ResultGeometryMapping()), nameof(Polygon.GetInteriorRingN) - => Function("ST_InteriorRingN", new[] { instance, OneBased(arguments[0]) }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_InteriorRingN", [instance, OneBased(arguments[0])], typeof(Geometry), ResultGeometryMapping()), nameof(LineString.GetPointN) - => Function("ST_PointN", new[] { instance, OneBased(arguments[0]) }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_PointN", [instance, OneBased(arguments[0])], typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.Intersection) - => Function("ST_Intersection", new[] { instance, arguments[0] }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_Intersection", [instance, arguments[0]], typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.Intersects) - => Function("ST_Intersects", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Intersects", [instance, arguments[0]], typeof(bool)), nameof(Geometry.IsWithinDistance) => Function("ST_DWithin", new[] { instance }.Concat(arguments).ToArray(), typeof(bool)), nameof(Geometry.Normalized) - => Function("ST_Normalize", new[] { instance }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_Normalize", [instance], typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.Overlaps) - => Function("ST_Overlaps", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Overlaps", [instance, arguments[0]], typeof(bool)), nameof(Geometry.Relate) - => Function("ST_Relate", new[] { instance, arguments[0], arguments[1] }, typeof(bool)), + => Function("ST_Relate", [instance, arguments[0], arguments[1]], typeof(bool)), nameof(Geometry.Reverse) - => Function("ST_Reverse", new[] { instance }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_Reverse", [instance], typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.SymmetricDifference) - => Function("ST_SymDifference", new[] { instance, arguments[0] }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_SymDifference", [instance, arguments[0]], typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.ToBinary) - => Function("ST_AsBinary", new[] { instance }, typeof(byte[])), + => Function("ST_AsBinary", [instance], typeof(byte[])), nameof(Geometry.ToText) - => Function("ST_AsText", new[] { instance }, typeof(string)), + => Function("ST_AsText", [instance], typeof(string)), nameof(Geometry.Touches) - => Function("ST_Touches", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Touches", [instance, arguments[0]], typeof(bool)), nameof(Geometry.Within) - => Function("ST_Within", new[] { instance, arguments[0] }, typeof(bool)), + => Function("ST_Within", [instance, arguments[0]], typeof(bool)), nameof(Geometry.Union) when arguments.Count == 0 - => Function("ST_UnaryUnion", new[] { instance }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_UnaryUnion", [instance], typeof(Geometry), ResultGeometryMapping()), nameof(Geometry.Union) when arguments.Count == 1 - => Function("ST_Union", new[] { instance, arguments[0] }, typeof(Geometry), ResultGeometryMapping()), + => Function("ST_Union", [instance, arguments[0]], typeof(Geometry), ResultGeometryMapping()), _ => null }; diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin.cs index 396abd401..f4b7da247 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin.cs @@ -48,7 +48,7 @@ public NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin( /// public class NpgsqlNodaTimeAggregateMethodTranslator : IAggregateMethodCallTranslator { - private static readonly bool[][] FalseArrays = { Array.Empty(), new[] { false } }; + private static readonly bool[][] FalseArrays = [[], [false]]; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; private readonly IRelationalTypeMappingSource _typeMappingSource; diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeEvaluatableExpressionFilterPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeEvaluatableExpressionFilterPlugin.cs index 53085d2e4..6e837baf6 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeEvaluatableExpressionFilterPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeEvaluatableExpressionFilterPlugin.cs @@ -9,7 +9,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime.Query.Internal; public class NpgsqlNodaTimeEvaluatableExpressionFilterPlugin : IEvaluatableExpressionFilterPlugin { private static readonly MethodInfo GetCurrentInstantMethod = - typeof(SystemClock).GetRuntimeMethod(nameof(SystemClock.GetCurrentInstant), Array.Empty())!; + typeof(SystemClock).GetRuntimeMethod(nameof(SystemClock.GetCurrentInstant), [])!; private static readonly MemberInfo SystemClockInstanceMember = typeof(SystemClock).GetMember(nameof(SystemClock.Instance)).FirstOrDefault()!; diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index 37bffc7c6..f08aa47f6 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -100,7 +100,7 @@ public NpgsqlNodaTimeMemberTranslator( _localDateTimeTypeMapping = typeMappingSource.FindMapping(typeof(LocalDateTime))!; } - private static readonly bool[][] TrueArrays = { Array.Empty(), new[] { true }, new[] { true, true } }; + private static readonly bool[][] TrueArrays = [[], [true], [true, true]]; /// public virtual SqlExpression? Translate( diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMethodCallTranslatorPlugin.cs index 7dada739f..1a3a9d7c7 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMethodCallTranslatorPlugin.cs @@ -56,14 +56,14 @@ public class NpgsqlNodaTimeMethodCallTranslator : IMethodCallTranslator typeof(Instant).GetRuntimeMethod(nameof(Instant.InUtc), Type.EmptyTypes)!; private static readonly MethodInfo Instant_InZone = - typeof(Instant).GetRuntimeMethod(nameof(Instant.InZone), new[] { typeof(DateTimeZone) })!; + typeof(Instant).GetRuntimeMethod(nameof(Instant.InZone), [typeof(DateTimeZone)])!; private static readonly MethodInfo Instant_ToDateTimeUtc = typeof(Instant).GetRuntimeMethod(nameof(Instant.ToDateTimeUtc), Type.EmptyTypes)!; private static readonly MethodInfo Instant_Distance = typeof(NpgsqlNodaTimeDbFunctionsExtensions).GetRuntimeMethod( - nameof(NpgsqlNodaTimeDbFunctionsExtensions.Distance), new[] { typeof(DbFunctions), typeof(Instant), typeof(Instant) })!; + nameof(NpgsqlNodaTimeDbFunctionsExtensions.Distance), [typeof(DbFunctions), typeof(Instant), typeof(Instant)])!; private static readonly MethodInfo ZonedDateTime_ToInstant = typeof(ZonedDateTime).GetRuntimeMethod(nameof(ZonedDateTime.ToInstant), Type.EmptyTypes)!; @@ -71,56 +71,56 @@ public class NpgsqlNodaTimeMethodCallTranslator : IMethodCallTranslator private static readonly MethodInfo ZonedDateTime_Distance = typeof(NpgsqlNodaTimeDbFunctionsExtensions).GetRuntimeMethod( nameof(NpgsqlNodaTimeDbFunctionsExtensions.Distance), - new[] { typeof(DbFunctions), typeof(ZonedDateTime), typeof(ZonedDateTime) })!; + [typeof(DbFunctions), typeof(ZonedDateTime), typeof(ZonedDateTime)])!; private static readonly MethodInfo LocalDateTime_InZoneLeniently = - typeof(LocalDateTime).GetRuntimeMethod(nameof(LocalDateTime.InZoneLeniently), new[] { typeof(DateTimeZone) })!; + typeof(LocalDateTime).GetRuntimeMethod(nameof(LocalDateTime.InZoneLeniently), [typeof(DateTimeZone)])!; private static readonly MethodInfo LocalDateTime_Distance = typeof(NpgsqlNodaTimeDbFunctionsExtensions).GetRuntimeMethod( nameof(NpgsqlNodaTimeDbFunctionsExtensions.Distance), - new[] { typeof(DbFunctions), typeof(LocalDateTime), typeof(LocalDateTime) })!; + [typeof(DbFunctions), typeof(LocalDateTime), typeof(LocalDateTime)])!; private static readonly MethodInfo LocalDate_Distance = typeof(NpgsqlNodaTimeDbFunctionsExtensions).GetRuntimeMethod( - nameof(NpgsqlNodaTimeDbFunctionsExtensions.Distance), new[] { typeof(DbFunctions), typeof(LocalDate), typeof(LocalDate) })!; + nameof(NpgsqlNodaTimeDbFunctionsExtensions.Distance), [typeof(DbFunctions), typeof(LocalDate), typeof(LocalDate)])!; - private static readonly MethodInfo Period_FromYears = typeof(Period).GetRuntimeMethod(nameof(Period.FromYears), new[] { typeof(int) })!; + private static readonly MethodInfo Period_FromYears = typeof(Period).GetRuntimeMethod(nameof(Period.FromYears), [typeof(int)])!; private static readonly MethodInfo Period_FromMonths = - typeof(Period).GetRuntimeMethod(nameof(Period.FromMonths), new[] { typeof(int) })!; + typeof(Period).GetRuntimeMethod(nameof(Period.FromMonths), [typeof(int)])!; - private static readonly MethodInfo Period_FromWeeks = typeof(Period).GetRuntimeMethod(nameof(Period.FromWeeks), new[] { typeof(int) })!; - private static readonly MethodInfo Period_FromDays = typeof(Period).GetRuntimeMethod(nameof(Period.FromDays), new[] { typeof(int) })!; + private static readonly MethodInfo Period_FromWeeks = typeof(Period).GetRuntimeMethod(nameof(Period.FromWeeks), [typeof(int)])!; + private static readonly MethodInfo Period_FromDays = typeof(Period).GetRuntimeMethod(nameof(Period.FromDays), [typeof(int)])!; private static readonly MethodInfo Period_FromHours = typeof(Period).GetRuntimeMethod( - nameof(Period.FromHours), new[] { typeof(long) })!; + nameof(Period.FromHours), [typeof(long)])!; private static readonly MethodInfo Period_FromMinutes = - typeof(Period).GetRuntimeMethod(nameof(Period.FromMinutes), new[] { typeof(long) })!; + typeof(Period).GetRuntimeMethod(nameof(Period.FromMinutes), [typeof(long)])!; private static readonly MethodInfo Period_FromSeconds = - typeof(Period).GetRuntimeMethod(nameof(Period.FromSeconds), new[] { typeof(long) })!; + typeof(Period).GetRuntimeMethod(nameof(Period.FromSeconds), [typeof(long)])!; private static readonly MethodInfo Interval_Contains - = typeof(Interval).GetRuntimeMethod(nameof(Interval.Contains), new[] { typeof(Instant) })!; + = typeof(Interval).GetRuntimeMethod(nameof(Interval.Contains), [typeof(Instant)])!; private static readonly MethodInfo DateInterval_Contains_LocalDate - = typeof(DateInterval).GetRuntimeMethod(nameof(DateInterval.Contains), new[] { typeof(LocalDate) })!; + = typeof(DateInterval).GetRuntimeMethod(nameof(DateInterval.Contains), [typeof(LocalDate)])!; private static readonly MethodInfo DateInterval_Contains_DateInterval - = typeof(DateInterval).GetRuntimeMethod(nameof(DateInterval.Contains), new[] { typeof(DateInterval) })!; + = typeof(DateInterval).GetRuntimeMethod(nameof(DateInterval.Contains), [typeof(DateInterval)])!; private static readonly MethodInfo DateInterval_Intersection - = typeof(DateInterval).GetRuntimeMethod(nameof(DateInterval.Intersection), new[] { typeof(DateInterval) })!; + = typeof(DateInterval).GetRuntimeMethod(nameof(DateInterval.Intersection), [typeof(DateInterval)])!; private static readonly MethodInfo DateInterval_Union - = typeof(DateInterval).GetRuntimeMethod(nameof(DateInterval.Union), new[] { typeof(DateInterval) })!; + = typeof(DateInterval).GetRuntimeMethod(nameof(DateInterval.Union), [typeof(DateInterval)])!; private static readonly MethodInfo IDateTimeZoneProvider_get_Item - = typeof(IDateTimeZoneProvider).GetRuntimeMethod("get_Item", new[] { typeof(string) })!; + = typeof(IDateTimeZoneProvider).GetRuntimeMethod("get_Item", [typeof(string)])!; - private static readonly bool[][] TrueArrays = { Array.Empty(), new[] { true }, new[] { true, true }, }; + private static readonly bool[][] TrueArrays = [[], [true], [true, true]]; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -179,17 +179,17 @@ public NpgsqlNodaTimeMethodCallTranslator( ? _sqlExpressionFactory.AtTimeZone( _sqlExpressionFactory.Function( "NOW", - Array.Empty(), + [], nullable: false, - argumentsPropagateNullability: Array.Empty(), + argumentsPropagateNullability: [], method.ReturnType), _sqlExpressionFactory.Constant("UTC"), method.ReturnType) : _sqlExpressionFactory.Function( "NOW", - Array.Empty(), + [], nullable: false, - argumentsPropagateNullability: Array.Empty(), + argumentsPropagateNullability: [], method.ReturnType, _typeMappingSource.FindMapping(typeof(Instant), "timestamp with time zone")); } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalRangeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalRangeMapping.cs index 6ac52f361..3a072e892 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalRangeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalRangeMapping.cs @@ -13,10 +13,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; public class DateIntervalRangeMapping : NpgsqlTypeMapping { private static readonly ConstructorInfo _constructorWithDates = - typeof(DateInterval).GetConstructor(new[] { typeof(LocalDate), typeof(LocalDate) })!; + typeof(DateInterval).GetConstructor([typeof(LocalDate), typeof(LocalDate)])!; private static readonly ConstructorInfo _localDateConstructor = - typeof(LocalDate).GetConstructor(new[] { typeof(int), typeof(int), typeof(int) })!; + typeof(LocalDate).GetConstructor([typeof(int), typeof(int), typeof(int)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs index 213f1a488..16cca6b5e 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs @@ -16,7 +16,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; public class DateMapping : NpgsqlTypeMapping { private static readonly ConstructorInfo Constructor = - typeof(LocalDate).GetConstructor(new[] { typeof(int), typeof(int), typeof(int) })!; + typeof(LocalDate).GetConstructor([typeof(int), typeof(int), typeof(int)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateTimeZoneMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateTimeZoneMapping.cs index 0959d6ef9..fea8f021b 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateTimeZoneMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateTimeZoneMapping.cs @@ -52,7 +52,7 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p public override Expression GenerateCodeLiteral(object value) => Expression.Call( Expression.Property(null, typeof(DateTimeZoneProviders).GetProperty(nameof(DateTimeZoneProviders.Tzdb))!), - typeof(IDateTimeZoneProvider).GetMethod(nameof(IDateTimeZoneProvider.GetZoneOrNull), new[] { typeof(string) })!, + typeof(IDateTimeZoneProvider).GetMethod(nameof(IDateTimeZoneProvider.GetZoneOrNull), [typeof(string)])!, Expression.Constant(((DateTimeZone)value).Id)); private sealed class DateTimeZoneConverter() : ValueConverter( diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs index 0f3385681..27a2c2038 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs @@ -13,17 +13,17 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; /// public class DurationIntervalMapping : NpgsqlTypeMapping { - private static readonly MethodInfo FromDays = typeof(Duration).GetRuntimeMethod(nameof(Duration.FromDays), new[] { typeof(int) })!; - private static readonly MethodInfo FromHours = typeof(Duration).GetRuntimeMethod(nameof(Duration.FromHours), new[] { typeof(int) })!; + private static readonly MethodInfo FromDays = typeof(Duration).GetRuntimeMethod(nameof(Duration.FromDays), [typeof(int)])!; + private static readonly MethodInfo FromHours = typeof(Duration).GetRuntimeMethod(nameof(Duration.FromHours), [typeof(int)])!; private static readonly MethodInfo FromMinutes = - typeof(Duration).GetRuntimeMethod(nameof(Duration.FromMinutes), new[] { typeof(long) })!; + typeof(Duration).GetRuntimeMethod(nameof(Duration.FromMinutes), [typeof(long)])!; private static readonly MethodInfo FromSeconds = - typeof(Duration).GetRuntimeMethod(nameof(Duration.FromSeconds), new[] { typeof(long) })!; + typeof(Duration).GetRuntimeMethod(nameof(Duration.FromSeconds), [typeof(long)])!; private static readonly MethodInfo FromMilliseconds = typeof(Duration).GetRuntimeMethod( - nameof(Duration.FromMilliseconds), new[] { typeof(long) })!; + nameof(Duration.FromMilliseconds), [typeof(long)])!; private static readonly PropertyInfo Zero = typeof(Duration).GetProperty(nameof(Duration.Zero))!; diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs index a262d0a11..6d0e08d12 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs @@ -16,10 +16,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; public class IntervalRangeMapping : NpgsqlTypeMapping { private static readonly ConstructorInfo _constructor = - typeof(Interval).GetConstructor(new[] { typeof(Instant), typeof(Instant) })!; + typeof(Interval).GetConstructor([typeof(Instant), typeof(Instant)])!; private static readonly ConstructorInfo _constructorWithNulls = - typeof(Interval).GetConstructor(new[] { typeof(Instant?), typeof(Instant?) })!; + typeof(Interval).GetConstructor([typeof(Instant?), typeof(Instant?)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs b/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs index 7cad0fff4..d3a581c8e 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs @@ -97,30 +97,27 @@ public NpgsqlNodaTimeTypeMappingSourcePlugin(ISqlGenerationHelper sqlGenerationH // migrations, model snapshots still contain old mappings (Instant mapped to timestamp), and EF Core's model differ // expects type mappings to be found for these. See https://github.com/dotnet/efcore/issues/26168. "timestamp without time zone", LegacyTimestampBehavior - ? new RelationalTypeMapping[] { _legacyTimestampInstant, _timestampLocalDateTime } - : new RelationalTypeMapping[] { _timestampLocalDateTime, _legacyTimestampInstant } + ? [_legacyTimestampInstant, _timestampLocalDateTime] + : [_timestampLocalDateTime, _legacyTimestampInstant] }, { - "timestamp with time zone", - new RelationalTypeMapping[] { _timestamptzInstant, _timestamptzZonedDateTime, _timestamptzOffsetDateTime } + "timestamp with time zone", [_timestamptzInstant, _timestamptzZonedDateTime, _timestamptzOffsetDateTime] }, - { "date", new RelationalTypeMapping[] { _date } }, - { "time without time zone", new RelationalTypeMapping[] { _time } }, - { "time with time zone", new RelationalTypeMapping[] { _timetz } }, - { "interval", new RelationalTypeMapping[] { _periodInterval, _durationInterval } }, + { "date", [_date] }, + { "time without time zone", [_time] }, + { "time with time zone", [_timetz] }, + { "interval", [_periodInterval, _durationInterval] }, { "tsrange", LegacyTimestampBehavior - ? new RelationalTypeMapping[] { _legacyTimestampInstantRange, _timestampLocalDateTimeRange } - : new RelationalTypeMapping[] { _timestampLocalDateTimeRange, _legacyTimestampInstantRange } + ? [_legacyTimestampInstantRange, _timestampLocalDateTimeRange] + : [_timestampLocalDateTimeRange, _legacyTimestampInstantRange] }, { - "tstzrange", - new RelationalTypeMapping[] - { + "tstzrange", [ _intervalRange, _timestamptzInstantRange, _timestamptzZonedDateTimeRange, _timestamptzOffsetDateTimeRange - } + ] }, - { "daterange", new RelationalTypeMapping[] { _dateIntervalRange, _dateRange } } + { "daterange", [_dateIntervalRange, _dateRange] } }; // Set up aliases diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs index 07ccff8b2..b46009434 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs @@ -14,19 +14,19 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; /// public class PeriodIntervalMapping : NpgsqlTypeMapping { - private static readonly MethodInfo FromYears = typeof(Period).GetRuntimeMethod(nameof(Period.FromYears), new[] { typeof(int) })!; - private static readonly MethodInfo FromMonths = typeof(Period).GetRuntimeMethod(nameof(Period.FromMonths), new[] { typeof(int) })!; - private static readonly MethodInfo FromWeeks = typeof(Period).GetRuntimeMethod(nameof(Period.FromWeeks), new[] { typeof(int) })!; - private static readonly MethodInfo FromDays = typeof(Period).GetRuntimeMethod(nameof(Period.FromDays), new[] { typeof(int) })!; - private static readonly MethodInfo FromHours = typeof(Period).GetRuntimeMethod(nameof(Period.FromHours), new[] { typeof(long) })!; - private static readonly MethodInfo FromMinutes = typeof(Period).GetRuntimeMethod(nameof(Period.FromMinutes), new[] { typeof(long) })!; - private static readonly MethodInfo FromSeconds = typeof(Period).GetRuntimeMethod(nameof(Period.FromSeconds), new[] { typeof(long) })!; + private static readonly MethodInfo FromYears = typeof(Period).GetRuntimeMethod(nameof(Period.FromYears), [typeof(int)])!; + private static readonly MethodInfo FromMonths = typeof(Period).GetRuntimeMethod(nameof(Period.FromMonths), [typeof(int)])!; + private static readonly MethodInfo FromWeeks = typeof(Period).GetRuntimeMethod(nameof(Period.FromWeeks), [typeof(int)])!; + private static readonly MethodInfo FromDays = typeof(Period).GetRuntimeMethod(nameof(Period.FromDays), [typeof(int)])!; + private static readonly MethodInfo FromHours = typeof(Period).GetRuntimeMethod(nameof(Period.FromHours), [typeof(long)])!; + private static readonly MethodInfo FromMinutes = typeof(Period).GetRuntimeMethod(nameof(Period.FromMinutes), [typeof(long)])!; + private static readonly MethodInfo FromSeconds = typeof(Period).GetRuntimeMethod(nameof(Period.FromSeconds), [typeof(long)])!; private static readonly MethodInfo FromMilliseconds = typeof(Period).GetRuntimeMethod( - nameof(Period.FromMilliseconds), new[] { typeof(long) })!; + nameof(Period.FromMilliseconds), [typeof(long)])!; private static readonly MethodInfo FromNanoseconds = typeof(Period).GetRuntimeMethod( - nameof(Period.FromNanoseconds), new[] { typeof(long) })!; + nameof(Period.FromNanoseconds), [typeof(long)])!; private static readonly PropertyInfo Zero = typeof(Period).GetProperty(nameof(Period.Zero))!; diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs index b3aab9393..a9d25f72b 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs @@ -16,15 +16,15 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; public class TimeMapping : NpgsqlTypeMapping { private static readonly ConstructorInfo ConstructorWithMinutes = - typeof(LocalTime).GetConstructor(new[] { typeof(int), typeof(int) })!; + typeof(LocalTime).GetConstructor([typeof(int), typeof(int)])!; private static readonly ConstructorInfo ConstructorWithSeconds = - typeof(LocalTime).GetConstructor(new[] { typeof(int), typeof(int), typeof(int) })!; + typeof(LocalTime).GetConstructor([typeof(int), typeof(int), typeof(int)])!; private static readonly MethodInfo FromHourMinuteSecondNanosecondMethod = typeof(LocalTime).GetMethod( nameof(LocalTime.FromHourMinuteSecondNanosecond), - new[] { typeof(int), typeof(int), typeof(int), typeof(long) })!; + [typeof(int), typeof(int), typeof(int), typeof(long)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs index f48ec2566..07ea8ac3a 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs @@ -16,24 +16,24 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; public class TimeTzMapping : NpgsqlTypeMapping { private static readonly ConstructorInfo OffsetTimeConstructor = - typeof(OffsetTime).GetConstructor(new[] { typeof(LocalTime), typeof(Offset) })!; + typeof(OffsetTime).GetConstructor([typeof(LocalTime), typeof(Offset)])!; private static readonly ConstructorInfo LocalTimeConstructorWithMinutes = - typeof(LocalTime).GetConstructor(new[] { typeof(int), typeof(int) })!; + typeof(LocalTime).GetConstructor([typeof(int), typeof(int)])!; private static readonly ConstructorInfo LocalTimeConstructorWithSeconds = - typeof(LocalTime).GetConstructor(new[] { typeof(int), typeof(int), typeof(int) })!; + typeof(LocalTime).GetConstructor([typeof(int), typeof(int), typeof(int)])!; private static readonly MethodInfo LocalTimeFromHourMinuteSecondNanosecondMethod = typeof(LocalTime).GetMethod( nameof(LocalTime.FromHourMinuteSecondNanosecond), - new[] { typeof(int), typeof(int), typeof(int), typeof(long) })!; + [typeof(int), typeof(int), typeof(int), typeof(long)])!; private static readonly MethodInfo OffsetFromHoursMethod = - typeof(Offset).GetMethod(nameof(Offset.FromHours), new[] { typeof(int) })!; + typeof(Offset).GetMethod(nameof(Offset.FromHours), [typeof(int)])!; private static readonly MethodInfo OffsetFromSeconds = - typeof(Offset).GetMethod(nameof(Offset.FromSeconds), new[] { typeof(int) })!; + typeof(Offset).GetMethod(nameof(Offset.FromSeconds), [typeof(int)])!; private static readonly OffsetTimePattern Pattern = OffsetTimePattern.CreateWithInvariantCulture("HH':'mm':'ss;FFFFFFo"); diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs index 63cba6b1c..6c85d56da 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs @@ -16,13 +16,13 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; public class TimestampLocalDateTimeMapping : NpgsqlTypeMapping { private static readonly ConstructorInfo ConstructorWithMinutes = - typeof(LocalDateTime).GetConstructor(new[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int) })!; + typeof(LocalDateTime).GetConstructor([typeof(int), typeof(int), typeof(int), typeof(int), typeof(int)])!; private static readonly ConstructorInfo ConstructorWithSeconds = - typeof(LocalDateTime).GetConstructor(new[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int) })!; + typeof(LocalDateTime).GetConstructor([typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int)])!; private static readonly MethodInfo PlusNanosecondsMethod = - typeof(LocalDateTime).GetMethod(nameof(LocalDateTime.PlusNanoseconds), new[] { typeof(long) })!; + typeof(LocalDateTime).GetMethod(nameof(LocalDateTime.PlusNanoseconds), [typeof(long)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs index 47bb9381a..ddeac0787 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs @@ -121,7 +121,7 @@ public override Expression GenerateCodeLiteral(object value) => GenerateCodeLiteral((Instant)value); private static readonly MethodInfo _fromUnixTimeTicks - = typeof(Instant).GetRuntimeMethod(nameof(Instant.FromUnixTimeTicks), new[] { typeof(long) })!; + = typeof(Instant).GetRuntimeMethod(nameof(Instant.FromUnixTimeTicks), [typeof(long)])!; private sealed class JsonInstantReaderWriter : JsonValueReaderWriter { diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs index 89ea37df4..31d8d25fd 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs @@ -16,13 +16,13 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; public class TimestampTzOffsetDateTimeMapping : NpgsqlTypeMapping { private static readonly ConstructorInfo Constructor = - typeof(OffsetDateTime).GetConstructor(new[] { typeof(LocalDateTime), typeof(Offset) })!; + typeof(OffsetDateTime).GetConstructor([typeof(LocalDateTime), typeof(Offset)])!; private static readonly MethodInfo OffsetFromHoursMethod = - typeof(Offset).GetMethod(nameof(Offset.FromHours), new[] { typeof(int) })!; + typeof(Offset).GetMethod(nameof(Offset.FromHours), [typeof(int)])!; private static readonly MethodInfo OffsetFromSecondsMethod = - typeof(Offset).GetMethod(nameof(Offset.FromSeconds), new[] { typeof(int) })!; + typeof(Offset).GetMethod(nameof(Offset.FromSeconds), [typeof(int)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs index 999bbe751..d6e637156 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs @@ -117,7 +117,7 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(ZonedDateTime).GetConstructor(new[] { typeof(Instant), typeof(DateTimeZone) })!; + typeof(ZonedDateTime).GetConstructor([typeof(Instant), typeof(DateTimeZone)])!; private static readonly MemberInfo TzdbDateTimeZoneSourceDefaultMember = typeof(TzdbDateTimeZoneSource).GetMember(nameof(TzdbDateTimeZoneSource.Default))[0]; @@ -125,7 +125,7 @@ public override Expression GenerateCodeLiteral(object value) private static readonly MethodInfo ForIdMethod = typeof(TzdbDateTimeZoneSource).GetRuntimeMethod( nameof(TzdbDateTimeZoneSource.ForId), - new[] { typeof(string) })!; + [typeof(string)])!; private sealed class JsonZonedDateTimeReaderWriter : JsonValueReaderWriter { diff --git a/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs b/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs index d61efedfb..d11a08d6a 100644 --- a/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs +++ b/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs @@ -62,7 +62,7 @@ private static readonly MethodInfo ModelHasAnnotationMethodInfo private static readonly MethodInfo ModelUseKeySequencesMethodInfo = typeof(NpgsqlModelBuilderExtensions).GetRuntimeMethod( - nameof(NpgsqlModelBuilderExtensions.UseKeySequences), new[] { typeof(ModelBuilder), typeof(string), typeof(string) })!; + nameof(NpgsqlModelBuilderExtensions.UseKeySequences), [typeof(ModelBuilder), typeof(string), typeof(string)])!; private static readonly MethodInfo EntityTypeIsUnloggedMethodInfo = typeof(NpgsqlEntityTypeBuilderExtensions).GetRequiredRuntimeMethod( @@ -91,7 +91,7 @@ private static readonly MethodInfo PropertyHasIdentityOptionsMethodInfo private static readonly MethodInfo PropertyUseSequenceMethodInfo = typeof(NpgsqlPropertyBuilderExtensions).GetRuntimeMethod( - nameof(NpgsqlPropertyBuilderExtensions.UseSequence), new[] { typeof(PropertyBuilder), typeof(string), typeof(string) })!; + nameof(NpgsqlPropertyBuilderExtensions.UseSequence), [typeof(PropertyBuilder), typeof(string), typeof(string)])!; private static readonly MethodInfo IndexUseCollationMethodInfo = typeof(NpgsqlIndexBuilderExtensions).GetRequiredRuntimeMethod( @@ -352,9 +352,9 @@ public override IReadOnlyList GenerateFluentApiCalls( onModel ? ModelUseHiLoMethodInfo : PropertyUseHiLoMethodInfo, (name, schema) switch { - (null, null) => Array.Empty(), - (_, null) => new object[] { name }, - _ => new object?[] { name!, schema } + (null, null) => [], + (_, null) => [name], + _ => [name!, schema] }); } @@ -368,9 +368,9 @@ public override IReadOnlyList GenerateFluentApiCalls( onModel ? ModelUseKeySequencesMethodInfo : PropertyUseSequenceMethodInfo, (name: nameOrSuffix, schema) switch { - (null, null) => Array.Empty(), - (_, null) => new object[] { nameOrSuffix }, - _ => new object[] { nameOrSuffix!, schema } + (null, null) => [], + (_, null) => [nameOrSuffix], + _ => [nameOrSuffix!, schema] }); } case NpgsqlValueGenerationStrategy.None: diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index 0d4fd224e..1ed164bb4 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -84,7 +84,7 @@ public virtual IReadOnlyList UserRangeDefinitions /// public NpgsqlOptionsExtension() { - _userRangeDefinitions = new List(); + _userRangeDefinitions = []; } // NB: When adding new options, make sure to update the copy ctor below. @@ -99,7 +99,7 @@ public NpgsqlOptionsExtension(NpgsqlOptionsExtension copyFrom) AdminDatabase = copyFrom.AdminDatabase; _postgresVersion = copyFrom._postgresVersion; UseRedshift = copyFrom.UseRedshift; - _userRangeDefinitions = new List(copyFrom._userRangeDefinitions); + _userRangeDefinitions = [..copyFrom._userRangeDefinitions]; ProvideClientCertificatesCallback = copyFrom.ProvideClientCertificatesCallback; RemoteCertificateValidationCallback = copyFrom.RemoteCertificateValidationCallback; ProvidePasswordCallback = copyFrom.ProvidePasswordCallback; diff --git a/src/EFCore.PG/Internal/EnumerableMethods.cs b/src/EFCore.PG/Internal/EnumerableMethods.cs index f840e16ff..3d8626534 100644 --- a/src/EFCore.PG/Internal/EnumerableMethods.cs +++ b/src/EFCore.PG/Internal/EnumerableMethods.cs @@ -257,14 +257,14 @@ static EnumerableMethods() All = GetMethod( nameof(Enumerable.All), 1, - types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]), typeof(Func<,>).MakeGenericType(types[0], typeof(bool)) }); + types => [typeof(IEnumerable<>).MakeGenericType(types[0]), typeof(Func<,>).MakeGenericType(types[0], typeof(bool))]); // AnyWithoutPredicate = GetMethod(nameof(Enumerable.Any), 1, // types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]) }); AnyWithPredicate = GetMethod( nameof(Enumerable.Any), 1, - types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]), typeof(Func<,>).MakeGenericType(types[0], typeof(bool)) }); + types => [typeof(IEnumerable<>).MakeGenericType(types[0]), typeof(Func<,>).MakeGenericType(types[0], typeof(bool))]); // AsEnumerable = GetMethod(nameof(Enumerable.AsEnumerable), 1, // types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]) }); @@ -280,7 +280,7 @@ static EnumerableMethods() Contains = GetMethod( nameof(Enumerable.Contains), 1, - types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]), types[0] }); + types => [typeof(IEnumerable<>).MakeGenericType(types[0]), types[0]]); // CountWithoutPredicate = GetMethod(nameof(Enumerable.Count), 1, // types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]) }); @@ -325,7 +325,7 @@ static EnumerableMethods() // typeof(IEnumerable<>).MakeGenericType(types[0]) // }); - FirstWithoutPredicate = GetMethod(nameof(Enumerable.First), 1, types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]) }); + FirstWithoutPredicate = GetMethod(nameof(Enumerable.First), 1, types => [typeof(IEnumerable<>).MakeGenericType(types[0])]); // FirstWithPredicate = GetMethod(nameof(Enumerable.First), 1, // types => new[] @@ -339,7 +339,7 @@ static EnumerableMethods() FirstOrDefaultWithPredicate = GetMethod( nameof(Enumerable.FirstOrDefault), 1, - types => new[] { typeof(IEnumerable<>).MakeGenericType(types[0]), typeof(Func<,>).MakeGenericType(types[0], typeof(bool)) }); + types => [typeof(IEnumerable<>).MakeGenericType(types[0]), typeof(Func<,>).MakeGenericType(types[0], typeof(bool))]); // GroupByWithKeySelector = GetMethod(nameof(Enumerable.GroupBy), 2, // types => new[] @@ -645,6 +645,6 @@ MethodInfo GetMethod(string name, int genericParameterCount, Func ((genericParameterCount == 0 && !mi.IsGenericMethod) || (mi.IsGenericMethod && mi.GetGenericArguments().Length == genericParameterCount)) && mi.GetParameters().Select(e => e.ParameterType).SequenceEqual( - parameterGenerator(mi.IsGenericMethod ? mi.GetGenericArguments() : Array.Empty()))); + parameterGenerator(mi.IsGenericMethod ? mi.GetGenericArguments() : []))); } } diff --git a/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs b/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs index 81785d0bf..a6f859155 100644 --- a/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs +++ b/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs @@ -71,7 +71,7 @@ public class NpgsqlSingletonOptions : INpgsqlSingletonOptions /// public NpgsqlSingletonOptions() { - UserRangeDefinitions = Array.Empty(); + UserRangeDefinitions = []; } /// diff --git a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs index a03696320..0d9591058 100644 --- a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs +++ b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs @@ -224,7 +224,7 @@ public override IEnumerable For(IRelationalModel model, bool design { if (!designTime) { - return Array.Empty(); + return []; } return model.Model.GetAnnotations().Where( diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 2442078b9..2b43ed701 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -1951,7 +1951,7 @@ private bool IsSystemColumn(string name) /// /// https://www.postgresql.org/docs/current/static/ddl-system-columns.html /// - private static readonly string[] SystemColumnNames = { "tableoid", "xmin", "cmin", "xmax", "cmax", "ctid" }; + private static readonly string[] SystemColumnNames = ["tableoid", "xmin", "cmin", "xmax", "cmax", "ctid"]; #endregion System column utilities diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs index 2dd0c5db3..286c87646 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs @@ -139,7 +139,7 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) _sqlExpressionFactory.Subtract( _sqlExpressionFactory.Function( "array_position", - new[] { array, item }, + [array, item], nullable: true, TrueArrays[2], arrayOrList.Type), diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs index 39ef22fdb..5ad41bcc8 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs @@ -17,8 +17,8 @@ public class NpgsqlConvertTranslator : IMethodCallTranslator [nameof(Convert.ToString)] = "text" }; - private static readonly List SupportedTypes = new() - { + private static readonly List SupportedTypes = + [ typeof(bool), typeof(byte), typeof(decimal), @@ -28,7 +28,7 @@ public class NpgsqlConvertTranslator : IMethodCallTranslator typeof(long), typeof(short), typeof(string) - }; + ]; private static readonly List SupportedMethods = TypeMapping.Keys diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs index d83544d38..e9d54f133 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs @@ -148,7 +148,7 @@ public NpgsqlDateTimeMemberTranslator(IRelationalTypeMappingSource typeMappingSo SqlExpression UtcNow() => _sqlExpressionFactory.Function( "now", - Array.Empty(), + [], nullable: false, argumentsPropagateNullability: TrueArrays[0], returnType, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs index 960a7966e..e64192022 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs @@ -13,85 +13,85 @@ public class NpgsqlDateTimeMethodTranslator : IMethodCallTranslator { private static readonly Dictionary MethodInfoDatePartMapping = new() { - { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddYears), new[] { typeof(int) })!, "years" }, - { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMonths), new[] { typeof(int) })!, "months" }, - { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddDays), new[] { typeof(double) })!, "days" }, - { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddHours), new[] { typeof(double) })!, "hours" }, - { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMinutes), new[] { typeof(double) })!, "mins" }, - { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddSeconds), new[] { typeof(double) })!, "secs" }, + { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddYears), [typeof(int)])!, "years" }, + { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMonths), [typeof(int)])!, "months" }, + { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddDays), [typeof(double)])!, "days" }, + { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddHours), [typeof(double)])!, "hours" }, + { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMinutes), [typeof(double)])!, "mins" }, + { typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddSeconds), [typeof(double)])!, "secs" }, //{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMilliseconds), new[] { typeof(double) })!, "milliseconds" }, - { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddYears), new[] { typeof(int) })!, "years" }, - { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMonths), new[] { typeof(int) })!, "months" }, - { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddDays), new[] { typeof(double) })!, "days" }, - { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddHours), new[] { typeof(double) })!, "hours" }, - { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMinutes), new[] { typeof(double) })!, "mins" }, - { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddSeconds), new[] { typeof(double) })!, "secs" }, + { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddYears), [typeof(int)])!, "years" }, + { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMonths), [typeof(int)])!, "months" }, + { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddDays), [typeof(double)])!, "days" }, + { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddHours), [typeof(double)])!, "hours" }, + { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMinutes), [typeof(double)])!, "mins" }, + { typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddSeconds), [typeof(double)])!, "secs" }, //{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMilliseconds), new[] { typeof(double) })!, "milliseconds" } // DateOnly.AddDays, AddMonths and AddYears have a specialized translation, see below - { typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.AddHours), new[] { typeof(int) })!, "hours" }, - { typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.AddMinutes), new[] { typeof(int) })!, "mins" }, + { typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.AddHours), [typeof(int)])!, "hours" }, + { typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.AddMinutes), [typeof(int)])!, "mins" }, }; // ReSharper disable InconsistentNaming private static readonly MethodInfo DateTime_ToUniversalTime - = typeof(DateTime).GetRuntimeMethod(nameof(DateTime.ToUniversalTime), Array.Empty())!; + = typeof(DateTime).GetRuntimeMethod(nameof(DateTime.ToUniversalTime), [])!; private static readonly MethodInfo DateTime_ToLocalTime - = typeof(DateTime).GetRuntimeMethod(nameof(DateTime.ToLocalTime), Array.Empty())!; + = typeof(DateTime).GetRuntimeMethod(nameof(DateTime.ToLocalTime), [])!; private static readonly MethodInfo DateTime_SpecifyKind - = typeof(DateTime).GetRuntimeMethod(nameof(DateTime.SpecifyKind), new[] { typeof(DateTime), typeof(DateTimeKind) })!; + = typeof(DateTime).GetRuntimeMethod(nameof(DateTime.SpecifyKind), [typeof(DateTime), typeof(DateTimeKind)])!; private static readonly MethodInfo DateTime_Distance = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( - nameof(NpgsqlDbFunctionsExtensions.Distance), new[] { typeof(DbFunctions), typeof(DateTime), typeof(DateTime) })!; + nameof(NpgsqlDbFunctionsExtensions.Distance), [typeof(DbFunctions), typeof(DateTime), typeof(DateTime)])!; private static readonly MethodInfo DateOnly_FromDateTime - = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.FromDateTime), new[] { typeof(DateTime) })!; + = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.FromDateTime), [typeof(DateTime)])!; private static readonly MethodInfo DateOnly_ToDateTime - = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.ToDateTime), new[] { typeof(TimeOnly) })!; + = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.ToDateTime), [typeof(TimeOnly)])!; private static readonly MethodInfo DateOnly_Distance = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( - nameof(NpgsqlDbFunctionsExtensions.Distance), new[] { typeof(DbFunctions), typeof(DateOnly), typeof(DateOnly) })!; + nameof(NpgsqlDbFunctionsExtensions.Distance), [typeof(DbFunctions), typeof(DateOnly), typeof(DateOnly)])!; private static readonly MethodInfo DateOnly_AddDays - = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddDays), new[] { typeof(int) })!; + = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddDays), [typeof(int)])!; private static readonly MethodInfo DateOnly_AddMonths - = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddMonths), new[] { typeof(int) })!; + = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddMonths), [typeof(int)])!; private static readonly MethodInfo DateOnly_AddYears - = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddYears), new[] { typeof(int) })!; + = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddYears), [typeof(int)])!; private static readonly MethodInfo TimeOnly_FromDateTime - = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.FromDateTime), new[] { typeof(DateTime) })!; + = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.FromDateTime), [typeof(DateTime)])!; private static readonly MethodInfo TimeOnly_FromTimeSpan - = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.FromTimeSpan), new[] { typeof(TimeSpan) })!; + = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.FromTimeSpan), [typeof(TimeSpan)])!; private static readonly MethodInfo TimeOnly_ToTimeSpan = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.ToTimeSpan), Type.EmptyTypes)!; private static readonly MethodInfo TimeOnly_IsBetween - = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.IsBetween), new[] { typeof(TimeOnly), typeof(TimeOnly) })!; + = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.IsBetween), [typeof(TimeOnly), typeof(TimeOnly)])!; private static readonly MethodInfo TimeOnly_Add_TimeSpan - = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.Add), new[] { typeof(TimeSpan) })!; + = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.Add), [typeof(TimeSpan)])!; private static readonly MethodInfo TimeZoneInfo_ConvertTimeBySystemTimeZoneId_DateTime = typeof(TimeZoneInfo).GetRuntimeMethod( - nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId), new[] { typeof(DateTime), typeof(string) })!; + nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId), [typeof(DateTime), typeof(string)])!; private static readonly MethodInfo TimeZoneInfo_ConvertTimeBySystemTimeZoneId_DateTimeOffset = typeof(TimeZoneInfo).GetRuntimeMethod( - nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId), new[] { typeof(DateTimeOffset), typeof(string) })!; + nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId), [typeof(DateTimeOffset), typeof(string)])!; private static readonly MethodInfo TimeZoneInfo_ConvertTimeToUtc - = typeof(TimeZoneInfo).GetRuntimeMethod(nameof(TimeZoneInfo.ConvertTimeToUtc), new[] { typeof(DateTime) })!; + = typeof(TimeZoneInfo).GetRuntimeMethod(nameof(TimeZoneInfo.ConvertTimeToUtc), [typeof(DateTime)])!; // ReSharper restore InconsistentNaming private readonly IRelationalTypeMappingSource _typeMappingSource; diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFuzzyStringMatchMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFuzzyStringMatchMethodTranslator.cs index 1e4b84a88..b0c6aa651 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFuzzyStringMatchMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFuzzyStringMatchMethodTranslator.cs @@ -36,15 +36,15 @@ private static MethodInfo GetRuntimeMethod(string name, params Type[] parameters private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; private static readonly bool[][] TrueArrays = - { - Array.Empty(), - new[] { true }, - new[] { true, true }, - new[] { true, true, true }, - new[] { true, true, true, true }, - new[] { true, true, true, true, true }, - new[] { true, true, true, true, true, true } - }; + [ + [], + [true], + [true, true], + [true, true, true], + [true, true, true, true], + [true, true, true, true, true], + [true, true, true, true, true, true] + ]; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs index e57698740..c314b5926 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs @@ -16,7 +16,7 @@ public class NpgsqlJsonDomTranslator : IMemberTranslator, IMethodCallTranslator private static readonly MemberInfo RootElement = typeof(JsonDocument).GetProperty(nameof(JsonDocument.RootElement))!; private static readonly MethodInfo GetProperty = typeof(JsonElement).GetRuntimeMethod( - nameof(JsonElement.GetProperty), new[] { typeof(string) })!; + nameof(JsonElement.GetProperty), [typeof(string)])!; private static readonly MethodInfo GetArrayLength = typeof(JsonElement).GetRuntimeMethod( nameof(JsonElement.GetArrayLength), Type.EmptyTypes)!; @@ -26,7 +26,7 @@ public class NpgsqlJsonDomTranslator : IMemberTranslator, IMethodCallTranslator .GetMethod!; private static readonly string[] GetMethods = - { + [ nameof(JsonElement.GetBoolean), nameof(JsonElement.GetDateTime), nameof(JsonElement.GetDateTimeOffset), @@ -38,7 +38,7 @@ public class NpgsqlJsonDomTranslator : IMemberTranslator, IMethodCallTranslator nameof(JsonElement.GetInt64), nameof(JsonElement.GetSingle), nameof(JsonElement.GetString) - }; + ]; private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLTreeTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLTreeTranslator.cs index 9d9a55cb2..270797f14 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLTreeTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLTreeTranslator.cs @@ -92,7 +92,7 @@ public NpgsqlLTreeTranslator( => _sqlExpressionFactory.Function( "subpath", arguments.Count == 2 - ? new[] { instance!, arguments[0], arguments[1] } + ? [instance!, arguments[0], arguments[1]] : new[] { instance!, arguments[0] }, nullable: true, arguments.Count == 2 ? TrueArrays[3] : TrueArrays[2], @@ -103,7 +103,7 @@ public NpgsqlLTreeTranslator( => _sqlExpressionFactory.Function( "index", arguments.Count == 2 - ? new[] { instance!, arguments[0], arguments[1] } + ? [instance!, arguments[0], arguments[1]] : new[] { instance!, arguments[0] }, nullable: true, arguments.Count == 2 ? TrueArrays[3] : TrueArrays[2], diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLikeTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLikeTranslator.cs index 470ce23ad..6aba0252d 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLikeTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLikeTranslator.cs @@ -8,24 +8,24 @@ public class NpgsqlLikeTranslator : IMethodCallTranslator private static readonly MethodInfo Like = typeof(DbFunctionsExtensions).GetRuntimeMethod( nameof(DbFunctionsExtensions.Like), - new[] { typeof(DbFunctions), typeof(string), typeof(string) })!; + [typeof(DbFunctions), typeof(string), typeof(string)])!; private static readonly MethodInfo LikeWithEscape = typeof(DbFunctionsExtensions).GetRuntimeMethod( nameof(DbFunctionsExtensions.Like), - new[] { typeof(DbFunctions), typeof(string), typeof(string), typeof(string) })!; + [typeof(DbFunctions), typeof(string), typeof(string), typeof(string)])!; // ReSharper disable once InconsistentNaming private static readonly MethodInfo ILike = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( nameof(NpgsqlDbFunctionsExtensions.ILike), - new[] { typeof(DbFunctions), typeof(string), typeof(string) })!; + [typeof(DbFunctions), typeof(string), typeof(string)])!; // ReSharper disable once InconsistentNaming private static readonly MethodInfo ILikeWithEscape = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( nameof(NpgsqlDbFunctionsExtensions.ILike), - new[] { typeof(DbFunctions), typeof(string), typeof(string), typeof(string) })!; + [typeof(DbFunctions), typeof(string), typeof(string), typeof(string)])!; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs index b7a14e134..380463f6f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs @@ -16,71 +16,71 @@ public class NpgsqlMathTranslator : IMethodCallTranslator { private static readonly Dictionary SupportedMethodTranslations = new() { - { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), new[] { typeof(decimal) })!, "abs" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), new[] { typeof(double) })!, "abs" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), new[] { typeof(float) })!, "abs" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), new[] { typeof(int) })!, "abs" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), new[] { typeof(long) })!, "abs" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), new[] { typeof(short) })!, "abs" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Abs), new[] { typeof(float) })!, "abs" }, - { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Abs), new[] { typeof(BigInteger) })!, "abs" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Ceiling), new[] { typeof(decimal) })!, "ceiling" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Ceiling), new[] { typeof(double) })!, "ceiling" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Ceiling), new[] { typeof(float) })!, "ceiling" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Floor), new[] { typeof(decimal) })!, "floor" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Floor), new[] { typeof(double) })!, "floor" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Floor), new[] { typeof(float) })!, "floor" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Pow), new[] { typeof(double), typeof(double) })!, "power" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Pow), new[] { typeof(float), typeof(float) })!, "power" }, - { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Pow), new[] { typeof(BigInteger), typeof(int) })!, "power" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Exp), new[] { typeof(double) })!, "exp" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Exp), new[] { typeof(float) })!, "exp" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Log10), new[] { typeof(double) })!, "log" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Log10), new[] { typeof(float) })!, "log" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Log), new[] { typeof(double) })!, "ln" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Log), new[] { typeof(float) })!, "ln" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), [typeof(decimal)])!, "abs" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), [typeof(double)])!, "abs" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), [typeof(float)])!, "abs" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), [typeof(int)])!, "abs" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), [typeof(long)])!, "abs" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Abs), [typeof(short)])!, "abs" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Abs), [typeof(float)])!, "abs" }, + { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Abs), [typeof(BigInteger)])!, "abs" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Ceiling), [typeof(decimal)])!, "ceiling" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Ceiling), [typeof(double)])!, "ceiling" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Ceiling), [typeof(float)])!, "ceiling" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Floor), [typeof(decimal)])!, "floor" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Floor), [typeof(double)])!, "floor" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Floor), [typeof(float)])!, "floor" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Pow), [typeof(double), typeof(double)])!, "power" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Pow), [typeof(float), typeof(float)])!, "power" }, + { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Pow), [typeof(BigInteger), typeof(int)])!, "power" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Exp), [typeof(double)])!, "exp" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Exp), [typeof(float)])!, "exp" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Log10), [typeof(double)])!, "log" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Log10), [typeof(float)])!, "log" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Log), [typeof(double)])!, "ln" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Log), [typeof(float)])!, "ln" }, // Note: PostgreSQL has log(x,y) but only for decimal, whereas .NET has it only for double/float - { typeof(Math).GetRuntimeMethod(nameof(Math.Sqrt), new[] { typeof(double) })!, "sqrt" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Sqrt), new[] { typeof(float) })!, "sqrt" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Acos), new[] { typeof(double) })!, "acos" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Acos), new[] { typeof(float) })!, "acos" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Asin), new[] { typeof(double) })!, "asin" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Asin), new[] { typeof(float) })!, "asin" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Atan), new[] { typeof(double) })!, "atan" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Atan), new[] { typeof(float) })!, "atan" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Atan2), new[] { typeof(double), typeof(double) })!, "atan2" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Atan2), new[] { typeof(float), typeof(float) })!, "atan2" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Cos), new[] { typeof(double) })!, "cos" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Cos), new[] { typeof(float) })!, "cos" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Sin), new[] { typeof(double) })!, "sin" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Sin), new[] { typeof(float) })!, "sin" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Tan), new[] { typeof(double) })!, "tan" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Tan), new[] { typeof(float) })!, "tan" }, - { typeof(double).GetRuntimeMethod(nameof(double.DegreesToRadians), new[] { typeof(double) })!, "radians" }, - { typeof(float).GetRuntimeMethod(nameof(float.DegreesToRadians), new[] { typeof(float) })!, "radians" }, - { typeof(double).GetRuntimeMethod(nameof(double.RadiansToDegrees), new[] { typeof(double) })!, "degrees" }, - { typeof(float).GetRuntimeMethod(nameof(float.RadiansToDegrees), new[] { typeof(float) })!, "degrees" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Sqrt), [typeof(double)])!, "sqrt" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Sqrt), [typeof(float)])!, "sqrt" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Acos), [typeof(double)])!, "acos" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Acos), [typeof(float)])!, "acos" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Asin), [typeof(double)])!, "asin" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Asin), [typeof(float)])!, "asin" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Atan), [typeof(double)])!, "atan" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Atan), [typeof(float)])!, "atan" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Atan2), [typeof(double), typeof(double)])!, "atan2" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Atan2), [typeof(float), typeof(float)])!, "atan2" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Cos), [typeof(double)])!, "cos" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Cos), [typeof(float)])!, "cos" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Sin), [typeof(double)])!, "sin" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Sin), [typeof(float)])!, "sin" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Tan), [typeof(double)])!, "tan" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Tan), [typeof(float)])!, "tan" }, + { typeof(double).GetRuntimeMethod(nameof(double.DegreesToRadians), [typeof(double)])!, "radians" }, + { typeof(float).GetRuntimeMethod(nameof(float.DegreesToRadians), [typeof(float)])!, "radians" }, + { typeof(double).GetRuntimeMethod(nameof(double.RadiansToDegrees), [typeof(double)])!, "degrees" }, + { typeof(float).GetRuntimeMethod(nameof(float.RadiansToDegrees), [typeof(float)])!, "degrees" }, // https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-GREATEST-LEAST - { typeof(Math).GetRuntimeMethod(nameof(Math.Max), new[] { typeof(decimal), typeof(decimal) })!, "GREATEST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Max), new[] { typeof(double), typeof(double) })!, "GREATEST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Max), new[] { typeof(float), typeof(float) })!, "GREATEST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Max), new[] { typeof(int), typeof(int) })!, "GREATEST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Max), new[] { typeof(long), typeof(long) })!, "GREATEST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Max), new[] { typeof(short), typeof(short) })!, "GREATEST" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Max), new[] { typeof(float), typeof(float) })!, "GREATEST" }, - { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Max), new[] { typeof(BigInteger), typeof(BigInteger) })!, "GREATEST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Max), [typeof(decimal), typeof(decimal)])!, "GREATEST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Max), [typeof(double), typeof(double)])!, "GREATEST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Max), [typeof(float), typeof(float)])!, "GREATEST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Max), [typeof(int), typeof(int)])!, "GREATEST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Max), [typeof(long), typeof(long)])!, "GREATEST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Max), [typeof(short), typeof(short)])!, "GREATEST" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Max), [typeof(float), typeof(float)])!, "GREATEST" }, + { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Max), [typeof(BigInteger), typeof(BigInteger)])!, "GREATEST" }, // https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-GREATEST-LEAST - { typeof(Math).GetRuntimeMethod(nameof(Math.Min), new[] { typeof(decimal), typeof(decimal) })!, "LEAST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Min), new[] { typeof(double), typeof(double) })!, "LEAST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Min), new[] { typeof(float), typeof(float) })!, "LEAST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Min), new[] { typeof(int), typeof(int) })!, "LEAST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Min), new[] { typeof(long), typeof(long) })!, "LEAST" }, - { typeof(Math).GetRuntimeMethod(nameof(Math.Min), new[] { typeof(short), typeof(short) })!, "LEAST" }, - { typeof(MathF).GetRuntimeMethod(nameof(MathF.Min), new[] { typeof(float), typeof(float) })!, "LEAST" }, - { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Min), new[] { typeof(BigInteger), typeof(BigInteger) })!, "LEAST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Min), [typeof(decimal), typeof(decimal)])!, "LEAST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Min), [typeof(double), typeof(double)])!, "LEAST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Min), [typeof(float), typeof(float)])!, "LEAST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Min), [typeof(int), typeof(int)])!, "LEAST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Min), [typeof(long), typeof(long)])!, "LEAST" }, + { typeof(Math).GetRuntimeMethod(nameof(Math.Min), [typeof(short), typeof(short)])!, "LEAST" }, + { typeof(MathF).GetRuntimeMethod(nameof(MathF.Min), [typeof(float), typeof(float)])!, "LEAST" }, + { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Min), [typeof(BigInteger), typeof(BigInteger)])!, "LEAST" }, }; private static readonly IEnumerable TruncateMethodInfos = new[] @@ -99,36 +99,36 @@ public class NpgsqlMathTranslator : IMethodCallTranslator private static readonly IEnumerable SignMethodInfos = new[] { - typeof(Math).GetRuntimeMethod(nameof(Math.Sign), new[] { typeof(decimal) })!, - typeof(Math).GetRuntimeMethod(nameof(Math.Sign), new[] { typeof(double) })!, - typeof(Math).GetRuntimeMethod(nameof(Math.Sign), new[] { typeof(float) })!, - typeof(Math).GetRuntimeMethod(nameof(Math.Sign), new[] { typeof(int) })!, - typeof(Math).GetRuntimeMethod(nameof(Math.Sign), new[] { typeof(long) })!, - typeof(Math).GetRuntimeMethod(nameof(Math.Sign), new[] { typeof(sbyte) })!, - typeof(Math).GetRuntimeMethod(nameof(Math.Sign), new[] { typeof(short) })!, - typeof(MathF).GetRuntimeMethod(nameof(MathF.Sign), new[] { typeof(float) })!, + typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(decimal)])!, + typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(double)])!, + typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(float)])!, + typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(int)])!, + typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(long)])!, + typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(sbyte)])!, + typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(short)])!, + typeof(MathF).GetRuntimeMethod(nameof(MathF.Sign), [typeof(float)])!, }; private static readonly MethodInfo RoundDecimalTwoParams - = typeof(Math).GetRuntimeMethod(nameof(Math.Round), new[] { typeof(decimal), typeof(int) })!; + = typeof(Math).GetRuntimeMethod(nameof(Math.Round), [typeof(decimal), typeof(int)])!; private static readonly MethodInfo DoubleIsNanMethodInfo - = typeof(double).GetRuntimeMethod(nameof(double.IsNaN), new[] { typeof(double) })!; + = typeof(double).GetRuntimeMethod(nameof(double.IsNaN), [typeof(double)])!; private static readonly MethodInfo DoubleIsPositiveInfinityMethodInfo - = typeof(double).GetRuntimeMethod(nameof(double.IsPositiveInfinity), new[] { typeof(double) })!; + = typeof(double).GetRuntimeMethod(nameof(double.IsPositiveInfinity), [typeof(double)])!; private static readonly MethodInfo DoubleIsNegativeInfinityMethodInfo - = typeof(double).GetRuntimeMethod(nameof(double.IsNegativeInfinity), new[] { typeof(double) })!; + = typeof(double).GetRuntimeMethod(nameof(double.IsNegativeInfinity), [typeof(double)])!; private static readonly MethodInfo FloatIsNanMethodInfo - = typeof(float).GetRuntimeMethod(nameof(float.IsNaN), new[] { typeof(float) })!; + = typeof(float).GetRuntimeMethod(nameof(float.IsNaN), [typeof(float)])!; private static readonly MethodInfo FloatIsPositiveInfinityMethodInfo - = typeof(float).GetRuntimeMethod(nameof(float.IsPositiveInfinity), new[] { typeof(float) })!; + = typeof(float).GetRuntimeMethod(nameof(float.IsPositiveInfinity), [typeof(float)])!; private static readonly MethodInfo FloatIsNegativeInfinityMethodInfo - = typeof(float).GetRuntimeMethod(nameof(float.IsNegativeInfinity), new[] { typeof(float) })!; + = typeof(float).GetRuntimeMethod(nameof(float.IsNegativeInfinity), [typeof(float)])!; private readonly ISqlExpressionFactory _sqlExpressionFactory; private readonly RelationalTypeMapping _intTypeMapping; diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs index 2e3077f7c..488994c93 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs @@ -12,10 +12,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte public class NpgsqlMiscAggregateMethodTranslator : IAggregateMethodCallTranslator { private static readonly MethodInfo StringJoin - = typeof(string).GetRuntimeMethod(nameof(string.Join), new[] { typeof(string), typeof(IEnumerable) })!; + = typeof(string).GetRuntimeMethod(nameof(string.Join), [typeof(string), typeof(IEnumerable)])!; private static readonly MethodInfo StringConcat - = typeof(string).GetRuntimeMethod(nameof(string.Concat), new[] { typeof(IEnumerable) })!; + = typeof(string).GetRuntimeMethod(nameof(string.Concat), [typeof(IEnumerable)])!; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; private readonly IRelationalTypeMappingSource _typeMappingSource; diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs index 81ffb280f..cb181134f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs @@ -14,10 +14,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte public class NpgsqlNetworkTranslator : IMethodCallTranslator { private static readonly MethodInfo IPAddressParse = - typeof(IPAddress).GetRuntimeMethod(nameof(IPAddress.Parse), new[] { typeof(string) })!; + typeof(IPAddress).GetRuntimeMethod(nameof(IPAddress.Parse), [typeof(string)])!; private static readonly MethodInfo PhysicalAddressParse = - typeof(PhysicalAddress).GetRuntimeMethod(nameof(PhysicalAddress.Parse), new[] { typeof(string) })!; + typeof(PhysicalAddress).GetRuntimeMethod(nameof(PhysicalAddress.Parse), [typeof(string)])!; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; @@ -180,31 +180,31 @@ public NpgsqlNetworkTranslator( _longAddressMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.Abbreviate) - => NullPropagatingFunction("abbrev", new[] { arguments[1] }, typeof(string)), + => NullPropagatingFunction("abbrev", [arguments[1]], typeof(string)), nameof(NpgsqlNetworkDbFunctionsExtensions.Broadcast) - => NullPropagatingFunction("broadcast", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), + => NullPropagatingFunction("broadcast", [arguments[1]], typeof(IPAddress), _inetMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.Family) - => NullPropagatingFunction("family", new[] { arguments[1] }, typeof(int)), + => NullPropagatingFunction("family", [arguments[1]], typeof(int)), nameof(NpgsqlNetworkDbFunctionsExtensions.Host) - => NullPropagatingFunction("host", new[] { arguments[1] }, typeof(string)), + => NullPropagatingFunction("host", [arguments[1]], typeof(string)), nameof(NpgsqlNetworkDbFunctionsExtensions.HostMask) - => NullPropagatingFunction("hostmask", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), + => NullPropagatingFunction("hostmask", [arguments[1]], typeof(IPAddress), _inetMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.MaskLength) - => NullPropagatingFunction("masklen", new[] { arguments[1] }, typeof(int)), + => NullPropagatingFunction("masklen", [arguments[1]], typeof(int)), nameof(NpgsqlNetworkDbFunctionsExtensions.Netmask) - => NullPropagatingFunction("netmask", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), + => NullPropagatingFunction("netmask", [arguments[1]], typeof(IPAddress), _inetMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.Network) - => NullPropagatingFunction("network", new[] { arguments[1] }, typeof((IPAddress Address, int Subnet)), _cidrMapping), + => NullPropagatingFunction("network", [arguments[1]], typeof((IPAddress Address, int Subnet)), _cidrMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.SetMaskLength) => NullPropagatingFunction( - "set_masklen", new[] { arguments[1], arguments[2] }, arguments[1].Type, arguments[1].TypeMapping), + "set_masklen", [arguments[1], arguments[2]], arguments[1].Type, arguments[1].TypeMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.Text) - => NullPropagatingFunction("text", new[] { arguments[1] }, typeof(string)), + => NullPropagatingFunction("text", [arguments[1]], typeof(string)), nameof(NpgsqlNetworkDbFunctionsExtensions.SameFamily) - => NullPropagatingFunction("inet_same_family", new[] { arguments[1], arguments[2] }, typeof(bool)), + => NullPropagatingFunction("inet_same_family", [arguments[1], arguments[2]], typeof(bool)), nameof(NpgsqlNetworkDbFunctionsExtensions.Merge) => NullPropagatingFunction( - "inet_merge", new[] { arguments[1], arguments[2] }, typeof((IPAddress Address, int Subnet)), _cidrMapping), + "inet_merge", [arguments[1], arguments[2]], typeof((IPAddress Address, int Subnet)), _cidrMapping), _ => null }; @@ -213,10 +213,10 @@ public NpgsqlNetworkTranslator( => method.Name switch { nameof(NpgsqlNetworkDbFunctionsExtensions.Abbreviate) - => NullPropagatingFunction("abbrev", new[] { arguments[1] }, typeof(string)), + => NullPropagatingFunction("abbrev", [arguments[1]], typeof(string)), nameof(NpgsqlNetworkDbFunctionsExtensions.SetMaskLength) => NullPropagatingFunction( - "set_masklen", new[] { arguments[1], arguments[2] }, arguments[1].Type, arguments[1].TypeMapping), + "set_masklen", [arguments[1], arguments[2]], arguments[1].Type, arguments[1].TypeMapping), _ => null }; @@ -241,9 +241,9 @@ public NpgsqlNetworkTranslator( => _sqlExpressionFactory.Or(arguments[1], arguments[2]), nameof(NpgsqlNetworkDbFunctionsExtensions.Truncate) => NullPropagatingFunction( - "trunc", new[] { arguments[1] }, typeof(PhysicalAddress), arguments[1].TypeMapping), + "trunc", [arguments[1]], typeof(PhysicalAddress), arguments[1].TypeMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.Set7BitMac8) => NullPropagatingFunction( - "macaddr8_set7bit", new[] { arguments[1] }, typeof(PhysicalAddress), _macaddr8Mapping), + "macaddr8_set7bit", [arguments[1]], typeof(PhysicalAddress), _macaddr8Mapping), _ => null }; diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNewGuidTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNewGuidTranslator.cs index 6ea22c4d8..8762343ff 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNewGuidTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNewGuidTranslator.cs @@ -10,7 +10,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte /// public class NpgsqlNewGuidTranslator : IMethodCallTranslator { - private static readonly MethodInfo MethodInfo = typeof(Guid).GetRuntimeMethod(nameof(Guid.NewGuid), Array.Empty())!; + private static readonly MethodInfo MethodInfo = typeof(Guid).GetRuntimeMethod(nameof(Guid.NewGuid), [])!; private readonly ISqlExpressionFactory _sqlExpressionFactory; private readonly string _uuidGenerationFunction; @@ -41,7 +41,7 @@ public NpgsqlNewGuidTranslator(ISqlExpressionFactory sqlExpressionFactory, Versi => MethodInfo.Equals(method) ? _sqlExpressionFactory.Function( _uuidGenerationFunction, - Array.Empty(), + [], nullable: false, argumentsPropagateNullability: FalseArrays[0], method.ReturnType) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs index 94df91dee..8478cae09 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs @@ -10,8 +10,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte /// public class NpgsqlObjectToStringTranslator : IMethodCallTranslator { - private static readonly HashSet _typeMapping = new() - { + private static readonly HashSet _typeMapping = + [ typeof(int), typeof(long), typeof(DateTime), @@ -29,8 +29,8 @@ public class NpgsqlObjectToStringTranslator : IMethodCallTranslator typeof(uint), typeof(ushort), typeof(ulong), - typeof(sbyte), - }; + typeof(sbyte) + ]; private readonly ISqlExpressionFactory _sqlExpressionFactory; private readonly RelationalTypeMapping _textTypeMapping; diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRandomTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRandomTranslator.cs index 2457ff1fa..3368c9a65 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRandomTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRandomTranslator.cs @@ -9,7 +9,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte public class NpgsqlRandomTranslator : IMethodCallTranslator { private static readonly MethodInfo _methodInfo - = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) })!; + = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), [typeof(DbFunctions)])!; private readonly ISqlExpressionFactory _sqlExpressionFactory; @@ -43,9 +43,9 @@ public NpgsqlRandomTranslator(ISqlExpressionFactory sqlExpressionFactory) return _methodInfo.Equals(method) ? _sqlExpressionFactory.Function( "random", - Array.Empty(), + [], nullable: false, - argumentsPropagateNullability: Array.Empty(), + argumentsPropagateNullability: [], method.ReturnType) : null; } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRegexIsMatchTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRegexIsMatchTranslator.cs index 4af7bdcb8..659d27b73 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRegexIsMatchTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRegexIsMatchTranslator.cs @@ -12,10 +12,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte public class NpgsqlRegexIsMatchTranslator : IMethodCallTranslator { private static readonly MethodInfo IsMatch = - typeof(Regex).GetRuntimeMethod(nameof(Regex.IsMatch), new[] { typeof(string), typeof(string) })!; + typeof(Regex).GetRuntimeMethod(nameof(Regex.IsMatch), [typeof(string), typeof(string)])!; private static readonly MethodInfo IsMatchWithRegexOptions = - typeof(Regex).GetRuntimeMethod(nameof(Regex.IsMatch), new[] { typeof(string), typeof(string), typeof(RegexOptions) })!; + typeof(Regex).GetRuntimeMethod(nameof(Regex.IsMatch), [typeof(string), typeof(string), typeof(RegexOptions)])!; private const RegexOptions UnsupportedRegexOptions = RegexOptions.RightToLeft | RegexOptions.ECMAScript; diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRowValueTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRowValueTranslator.cs index aa21383e7..f1e6f3aeb 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRowValueTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRowValueTranslator.cs @@ -18,7 +18,7 @@ public class NpgsqlRowValueTranslator : IMethodCallTranslator private static readonly MethodInfo GreaterThan = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( nameof(NpgsqlDbFunctionsExtensions.GreaterThan), - new[] { typeof(DbFunctions), typeof(ITuple), typeof(ITuple) })!; + [typeof(DbFunctions), typeof(ITuple), typeof(ITuple)])!; private static readonly MethodInfo LessThan = typeof(NpgsqlDbFunctionsExtensions).GetMethods() diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs index cffe8d89f..ae89c3243 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs @@ -19,24 +19,24 @@ public class NpgsqlStringMethodTranslator : IMethodCallTranslator #region MethodInfo - private static readonly MethodInfo IndexOfChar = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), new[] { typeof(char) })!; - private static readonly MethodInfo IndexOfString = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), new[] { typeof(string) })!; + private static readonly MethodInfo IndexOfChar = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(char)])!; + private static readonly MethodInfo IndexOfString = typeof(string).GetRuntimeMethod(nameof(string.IndexOf), [typeof(string)])!; private static readonly MethodInfo IsNullOrWhiteSpace = - typeof(string).GetRuntimeMethod(nameof(string.IsNullOrWhiteSpace), new[] { typeof(string) })!; + typeof(string).GetRuntimeMethod(nameof(string.IsNullOrWhiteSpace), [typeof(string)])!; - private static readonly MethodInfo PadLeft = typeof(string).GetRuntimeMethod(nameof(string.PadLeft), new[] { typeof(int) })!; + private static readonly MethodInfo PadLeft = typeof(string).GetRuntimeMethod(nameof(string.PadLeft), [typeof(int)])!; private static readonly MethodInfo PadLeftWithChar = typeof(string).GetRuntimeMethod( - nameof(string.PadLeft), new[] { typeof(int), typeof(char) })!; + nameof(string.PadLeft), [typeof(int), typeof(char)])!; - private static readonly MethodInfo PadRight = typeof(string).GetRuntimeMethod(nameof(string.PadRight), new[] { typeof(int) })!; + private static readonly MethodInfo PadRight = typeof(string).GetRuntimeMethod(nameof(string.PadRight), [typeof(int)])!; private static readonly MethodInfo PadRightWithChar = typeof(string).GetRuntimeMethod( - nameof(string.PadRight), new[] { typeof(int), typeof(char) })!; + nameof(string.PadRight), [typeof(int), typeof(char)])!; private static readonly MethodInfo Replace = typeof(string).GetRuntimeMethod( - nameof(string.Replace), new[] { typeof(string), typeof(string) })!; + nameof(string.Replace), [typeof(string), typeof(string)])!; private static readonly MethodInfo Substring = typeof(string).GetTypeInfo().GetDeclaredMethods(nameof(string.Substring)) .Single(m => m.GetParameters().Length == 1); @@ -44,38 +44,38 @@ public class NpgsqlStringMethodTranslator : IMethodCallTranslator private static readonly MethodInfo SubstringWithLength = typeof(string).GetTypeInfo().GetDeclaredMethods(nameof(string.Substring)) .Single(m => m.GetParameters().Length == 2); - private static readonly MethodInfo ToLower = typeof(string).GetRuntimeMethod(nameof(string.ToLower), Array.Empty())!; - private static readonly MethodInfo ToUpper = typeof(string).GetRuntimeMethod(nameof(string.ToUpper), Array.Empty())!; + private static readonly MethodInfo ToLower = typeof(string).GetRuntimeMethod(nameof(string.ToLower), [])!; + private static readonly MethodInfo ToUpper = typeof(string).GetRuntimeMethod(nameof(string.ToUpper), [])!; private static readonly MethodInfo TrimBothWithNoParam = typeof(string).GetRuntimeMethod(nameof(string.Trim), Type.EmptyTypes)!; - private static readonly MethodInfo TrimBothWithChars = typeof(string).GetRuntimeMethod(nameof(string.Trim), new[] { typeof(char[]) })!; + private static readonly MethodInfo TrimBothWithChars = typeof(string).GetRuntimeMethod(nameof(string.Trim), [typeof(char[])])!; private static readonly MethodInfo TrimBothWithSingleChar = - typeof(string).GetRuntimeMethod(nameof(string.Trim), new[] { typeof(char) })!; + typeof(string).GetRuntimeMethod(nameof(string.Trim), [typeof(char)])!; private static readonly MethodInfo TrimEndWithNoParam = typeof(string).GetRuntimeMethod(nameof(string.TrimEnd), Type.EmptyTypes)!; private static readonly MethodInfo TrimEndWithChars = typeof(string).GetRuntimeMethod( - nameof(string.TrimEnd), new[] { typeof(char[]) })!; + nameof(string.TrimEnd), [typeof(char[])])!; private static readonly MethodInfo TrimEndWithSingleChar = - typeof(string).GetRuntimeMethod(nameof(string.TrimEnd), new[] { typeof(char) })!; + typeof(string).GetRuntimeMethod(nameof(string.TrimEnd), [typeof(char)])!; private static readonly MethodInfo TrimStartWithNoParam = typeof(string).GetRuntimeMethod(nameof(string.TrimStart), Type.EmptyTypes)!; private static readonly MethodInfo TrimStartWithChars = - typeof(string).GetRuntimeMethod(nameof(string.TrimStart), new[] { typeof(char[]) })!; + typeof(string).GetRuntimeMethod(nameof(string.TrimStart), [typeof(char[])])!; private static readonly MethodInfo TrimStartWithSingleChar = - typeof(string).GetRuntimeMethod(nameof(string.TrimStart), new[] { typeof(char) })!; + typeof(string).GetRuntimeMethod(nameof(string.TrimStart), [typeof(char)])!; private static readonly MethodInfo Reverse = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( - nameof(NpgsqlDbFunctionsExtensions.Reverse), new[] { typeof(DbFunctions), typeof(string) })!; + nameof(NpgsqlDbFunctionsExtensions.Reverse), [typeof(DbFunctions), typeof(string)])!; private static readonly MethodInfo StringToArray = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( - nameof(NpgsqlDbFunctionsExtensions.StringToArray), new[] { typeof(DbFunctions), typeof(string), typeof(string) })!; + nameof(NpgsqlDbFunctionsExtensions.StringToArray), [typeof(DbFunctions), typeof(string), typeof(string)])!; private static readonly MethodInfo StringToArrayNullString = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( - nameof(NpgsqlDbFunctionsExtensions.StringToArray), new[] { typeof(DbFunctions), typeof(string), typeof(string), typeof(string) })!; + nameof(NpgsqlDbFunctionsExtensions.StringToArray), [typeof(DbFunctions), typeof(string), typeof(string), typeof(string)])!; private static readonly MethodInfo FirstOrDefaultMethodInfoWithoutArgs = typeof(Enumerable).GetRuntimeMethods().Single( @@ -89,16 +89,16 @@ private static readonly MethodInfo LastOrDefaultMethodInfoWithoutArgs // ReSharper disable InconsistentNaming private static readonly MethodInfo String_Join1 = - typeof(string).GetMethod(nameof(string.Join), new[] { typeof(string), typeof(object[]) })!; + typeof(string).GetMethod(nameof(string.Join), [typeof(string), typeof(object[])])!; private static readonly MethodInfo String_Join2 = - typeof(string).GetMethod(nameof(string.Join), new[] { typeof(string), typeof(string[]) })!; + typeof(string).GetMethod(nameof(string.Join), [typeof(string), typeof(string[])])!; private static readonly MethodInfo String_Join3 = - typeof(string).GetMethod(nameof(string.Join), new[] { typeof(char), typeof(object[]) })!; + typeof(string).GetMethod(nameof(string.Join), [typeof(char), typeof(object[])])!; private static readonly MethodInfo String_Join4 = - typeof(string).GetMethod(nameof(string.Join), new[] { typeof(char), typeof(string[]) })!; + typeof(string).GetMethod(nameof(string.Join), [typeof(char), typeof(string[])])!; private static readonly MethodInfo String_Join_generic1 = typeof(string).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) @@ -245,7 +245,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I { var args = method == Substring - ? new[] { instance!, GenerateOneBasedIndexExpression(arguments[0]) } + ? [instance!, GenerateOneBasedIndexExpression(arguments[0])] : new[] { instance!, GenerateOneBasedIndexExpression(arguments[0]), arguments[1] }; return _sqlExpressionFactory.Function( "substring", @@ -294,7 +294,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I } trimChars = constantTrimChars.Value is char c - ? new[] { c } + ? [c] : (char[]?)constantTrimChars.Value; } @@ -317,7 +317,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I { var args = method == PadLeft || method == PadRight - ? new[] { instance!, arguments[0] } + ? [instance!, arguments[0]] : new[] { instance!, arguments[0], arguments[1] }; return _sqlExpressionFactory.Function( diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs index 8496ab69c..6efe34dae 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs @@ -23,7 +23,7 @@ public NpgsqlTimeSpanMemberTranslator(ISqlExpressionFactory sqlExpressionFactory _sqlExpressionFactory = sqlExpressionFactory; } - private static readonly bool[] FalseTrueArray = { false, true }; + private static readonly bool[] FalseTrueArray = [false, true]; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTrigramsMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTrigramsMethodTranslator.cs index 647d9ee92..291dcd3d4 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTrigramsMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTrigramsMethodTranslator.cs @@ -57,7 +57,7 @@ private static MethodInfo GetRuntimeMethod(string name, params Type[] parameters private readonly RelationalTypeMapping _boolMapping; private readonly RelationalTypeMapping _floatMapping; - private static readonly bool[][] TrueArrays = { Array.Empty(), new[] { true }, new[] { true, true } }; + private static readonly bool[][] TrueArrays = [[], [true], [true, true]]; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs index c129ad530..7e4601d3a 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs @@ -63,7 +63,7 @@ public static PgFunctionExpression CreateWithNamedArguments( return new PgFunctionExpression( name, arguments, argumentNames, argumentSeparators: null, - aggregateDistinct: false, aggregatePredicate: null, aggregateOrderings: Array.Empty(), + aggregateDistinct: false, aggregatePredicate: null, aggregateOrderings: [], nullable: nullable, argumentsPropagateNullability: argumentsPropagateNullability, type: type, typeMapping: typeMapping); } @@ -85,7 +85,7 @@ public static PgFunctionExpression CreateWithArgumentSeparators( return new PgFunctionExpression( name, arguments, argumentNames: null, argumentSeparators: argumentSeparators, - aggregateDistinct: false, aggregatePredicate: null, aggregateOrderings: Array.Empty(), + aggregateDistinct: false, aggregatePredicate: null, aggregateOrderings: [], nullable: nullable, argumentsPropagateNullability: argumentsPropagateNullability, type: type, typeMapping: typeMapping); } @@ -109,8 +109,8 @@ public PgFunctionExpression( Check.NotEmpty(name, nameof(name)); Check.NotNull(type, nameof(type)); - ArgumentNames = (argumentNames ?? Array.Empty()).ToList(); - ArgumentSeparators = (argumentSeparators ?? Array.Empty()).ToList(); + ArgumentNames = (argumentNames ?? []).ToList(); + ArgumentSeparators = (argumentSeparators ?? []).ToList(); if (ArgumentNames.SkipWhile(a => a is null).Contains(null)) { diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs index 5d0c56412..5aafba511 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs @@ -44,7 +44,7 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) var visitedExpression = (SqlExpression)visitor.Visit(expression); if (visitedExpression != expression && newExpressions is null) { - newExpressions = new List(); + newExpressions = []; for (var j = 0; j < i; j++) { newExpressions.Add(Expressions[j]); diff --git a/src/EFCore.PG/Query/Internal/NpgsqlEvaluatableExpressionFilter.cs b/src/EFCore.PG/Query/Internal/NpgsqlEvaluatableExpressionFilter.cs index b03807f13..da5ebbbd1 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlEvaluatableExpressionFilter.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlEvaluatableExpressionFilter.cs @@ -11,10 +11,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; public class NpgsqlEvaluatableExpressionFilter : RelationalEvaluatableExpressionFilter { private static readonly MethodInfo TsQueryParse = - typeof(NpgsqlTsQuery).GetRuntimeMethod(nameof(NpgsqlTsQuery.Parse), new[] { typeof(string) })!; + typeof(NpgsqlTsQuery).GetRuntimeMethod(nameof(NpgsqlTsQuery.Parse), [typeof(string)])!; private static readonly MethodInfo TsVectorParse = - typeof(NpgsqlTsVector).GetRuntimeMethod(nameof(NpgsqlTsVector.Parse), new[] { typeof(string) })!; + typeof(NpgsqlTsVector).GetRuntimeMethod(nameof(NpgsqlTsVector.Parse), [typeof(string)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index 4cde6c0cd..208e033d8 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -22,15 +22,15 @@ public class NpgsqlQueryableMethodTranslatingExpressionVisitor : RelationalQuery private static readonly MethodInfo Like2MethodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod( - nameof(DbFunctionsExtensions.Like), new[] { typeof(DbFunctions), typeof(string), typeof(string) })!; + nameof(DbFunctionsExtensions.Like), [typeof(DbFunctions), typeof(string), typeof(string)])!; // ReSharper disable once InconsistentNaming private static readonly MethodInfo ILike2MethodInfo = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( - nameof(NpgsqlDbFunctionsExtensions.ILike), new[] { typeof(DbFunctions), typeof(string), typeof(string) })!; + nameof(NpgsqlDbFunctionsExtensions.ILike), [typeof(DbFunctions), typeof(string), typeof(string)])!; private static readonly MethodInfo MatchesLQuery = - typeof(LTree).GetRuntimeMethod(nameof(LTree.MatchesLQuery), new[] { typeof(string) })!; + typeof(LTree).GetRuntimeMethod(nameof(LTree.MatchesLQuery), [typeof(string)])!; #endregion diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 277c50991..cbc64eaf9 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -30,26 +30,26 @@ public class NpgsqlSqlTranslatingExpressionVisitor : RelationalSqlTranslatingExp private static Type? _nodaTimePeriodType; private static readonly ConstructorInfo DateTimeCtor1 = - typeof(DateTime).GetConstructor(new[] { typeof(int), typeof(int), typeof(int) })!; + typeof(DateTime).GetConstructor([typeof(int), typeof(int), typeof(int)])!; private static readonly ConstructorInfo DateTimeCtor2 = - typeof(DateTime).GetConstructor(new[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int) })!; + typeof(DateTime).GetConstructor([typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int)])!; private static readonly ConstructorInfo DateTimeCtor3 = typeof(DateTime).GetConstructor( - new[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(DateTimeKind) })!; + [typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(DateTimeKind)])!; private static readonly ConstructorInfo DateOnlyCtor = - typeof(DateOnly).GetConstructor(new[] { typeof(int), typeof(int), typeof(int) })!; + typeof(DateOnly).GetConstructor([typeof(int), typeof(int), typeof(int)])!; private static readonly MethodInfo StringStartsWithMethod - = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), new[] { typeof(string) })!; + = typeof(string).GetRuntimeMethod(nameof(string.StartsWith), [typeof(string)])!; private static readonly MethodInfo StringEndsWithMethod - = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) })!; + = typeof(string).GetRuntimeMethod(nameof(string.EndsWith), [typeof(string)])!; private static readonly MethodInfo StringContainsMethod - = typeof(string).GetRuntimeMethod(nameof(string.Contains), new[] { typeof(string) })!; + = typeof(string).GetRuntimeMethod(nameof(string.Contains), [typeof(string)])!; private static readonly MethodInfo EscapeLikePatternParameterMethod = typeof(NpgsqlSqlTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ConstructLikePatternParameter))!; diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 3fd39ac8c..91d898672 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -151,7 +151,7 @@ public virtual PgJsonTraversalExpression JsonTraversal( bool returnsText, Type type, RelationalTypeMapping? typeMapping = null) - => JsonTraversal(expression, Array.Empty(), returnsText, type, typeMapping); + => JsonTraversal(expression, [], returnsText, type, typeMapping); /// /// Creates a new , for traversing inside a JSON document. @@ -198,7 +198,7 @@ public virtual SqlExpression NewArrayOrConstant( var addMethod = type.GetMethod("Add")!; for (var i = 0; i < elements.Count; i++) { - addMethod.Invoke(list, new[] { ((SqlConstantExpression)newArrayExpression.Expressions[i]).Value }); + addMethod.Invoke(list, [((SqlConstantExpression)newArrayExpression.Expressions[i]).Value]); } return Constant(list, newArrayExpression.TypeMapping); @@ -1007,7 +1007,7 @@ static bool IsTextualTypeMapping(RelationalTypeMapping mapping) var newExpression = ApplyTypeMapping(expression, elementTypeMapping); if (newExpression != expression && newExpressions is null) { - newExpressions = new List(); + newExpressions = []; for (var j = 0; j < i; j++) { newExpressions.Add(pgNewArrayExpression.Expressions[j]); diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlCodeGenerator.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlCodeGenerator.cs index 52168228c..32d0b4dd7 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlCodeGenerator.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlCodeGenerator.cs @@ -35,6 +35,6 @@ public override MethodCallCodeFragment GenerateUseProvider( => new( _useNpgsqlMethodInfo, providerOptions is null - ? new object[] { connectionString } - : new object[] { connectionString, new NestedClosureCodeFragment("x", providerOptions) }); + ? [connectionString] + : [connectionString, new NestedClosureCodeFragment("x", providerOptions)]); } diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 68bf6b898..5cb3103cd 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -29,7 +29,7 @@ public class NpgsqlDatabaseModelFactory : DatabaseModelFactory RegexOptions.Compiled, TimeSpan.FromMilliseconds(1000.0)); - private static readonly string[] SerialTypes = { "int2", "int4", "int8" }; + private static readonly string[] SerialTypes = ["int2", "int4", "int8"]; private readonly IDiagnosticsLogger _logger; @@ -228,7 +228,7 @@ deptype IN ('e', 'x') var name = reader.GetString("relname"); var type = reader.GetChar("relkind"); var comment = reader.GetValueOrDefault("description"); - var storageParameters = reader.GetValueOrDefault("reloptions") ?? Array.Empty(); + var storageParameters = reader.GetValueOrDefault("reloptions") ?? []; var table = type switch { @@ -772,7 +772,7 @@ deptype IN ('e', 'x') index[NpgsqlAnnotationNames.NullsDistinct] = false; } - foreach (var storageParameter in record.GetValueOrDefault("idx_reloptions") ?? Array.Empty()) + foreach (var storageParameter in record.GetValueOrDefault("idx_reloptions") ?? []) { if (storageParameter.Split("=") is [var paramName, var paramValue]) { @@ -839,7 +839,7 @@ deptype IN ('e', 'x') using var command = new NpgsqlCommand(commandText, connection); using var reader = command.ExecuteReader(); - constraintIndexes = new List(); + constraintIndexes = []; var tableGroups = reader.Cast().GroupBy( ddr => ( tableSchema: ddr.GetFieldValue("nspname"), diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBitTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBitTypeMapping.cs index f2e148e71..977fbd56e 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBitTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBitTypeMapping.cs @@ -87,5 +87,5 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(BitArray).GetConstructor(new[] { typeof(bool[]) })!; + typeof(BitArray).GetConstructor([typeof(bool[])])!; } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs index 18b4f3526..a07b54d41 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs @@ -78,10 +78,10 @@ public override Expression GenerateCodeLiteral(object value) Expression.Constant(cidr.Netmask)); } - private static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", new[] { typeof(string) })!; + private static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", [typeof(string)])!; private static readonly ConstructorInfo NpgsqlCidrConstructor = - typeof(NpgsqlCidr).GetConstructor(new[] { typeof(IPAddress), typeof(byte) })!; + typeof(NpgsqlCidr).GetConstructor([typeof(IPAddress), typeof(byte)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlGeometricTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlGeometricTypeMapping.cs index d7df0b599..d93451429 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlGeometricTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlGeometricTypeMapping.cs @@ -75,7 +75,7 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlPoint).GetConstructor(new[] { typeof(double), typeof(double) })!; + typeof(NpgsqlPoint).GetConstructor([typeof(double), typeof(double)])!; } /// @@ -155,7 +155,7 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlLine).GetConstructor(new[] { typeof(double), typeof(double), typeof(double) })!; + typeof(NpgsqlLine).GetConstructor([typeof(double), typeof(double), typeof(double)])!; } /// @@ -233,7 +233,7 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlLSeg).GetConstructor(new[] { typeof(double), typeof(double), typeof(double), typeof(double) })!; + typeof(NpgsqlLSeg).GetConstructor([typeof(double), typeof(double), typeof(double), typeof(double)])!; } /// @@ -311,7 +311,7 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlBox).GetConstructor(new[] { typeof(double), typeof(double), typeof(double), typeof(double) })!; + typeof(NpgsqlBox).GetConstructor([typeof(double), typeof(double), typeof(double), typeof(double)])!; } /// @@ -412,10 +412,10 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlPath).GetConstructor(new[] { typeof(IEnumerable), typeof(bool) })!; + typeof(NpgsqlPath).GetConstructor([typeof(IEnumerable), typeof(bool)])!; private static readonly ConstructorInfo PointConstructor = - typeof(NpgsqlPoint).GetConstructor(new[] { typeof(double), typeof(double) })!; + typeof(NpgsqlPoint).GetConstructor([typeof(double), typeof(double)])!; } /// @@ -513,10 +513,10 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlPolygon).GetConstructor(new[] { typeof(NpgsqlPoint[]) })!; + typeof(NpgsqlPolygon).GetConstructor([typeof(NpgsqlPoint[])])!; private static readonly ConstructorInfo PointConstructor = - typeof(NpgsqlPoint).GetConstructor(new[] { typeof(double), typeof(double) })!; + typeof(NpgsqlPoint).GetConstructor([typeof(double), typeof(double)])!; } /// @@ -593,5 +593,5 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlCircle).GetConstructor(new[] { typeof(double), typeof(double), typeof(double) })!; + typeof(NpgsqlCircle).GetConstructor([typeof(double), typeof(double), typeof(double)])!; } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs index 59fb0ba15..90a6edb9d 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs @@ -82,8 +82,8 @@ public override Expression GenerateCodeLiteral(object value) _ => throw new UnreachableException() }; - private static readonly MethodInfo IPAddressParseMethod = typeof(IPAddress).GetMethod("Parse", new[] { typeof(string) })!; - private static readonly ConstructorInfo NpgsqlInetConstructor = typeof(NpgsqlInet).GetConstructor(new[] { typeof(string) })!; + private static readonly MethodInfo IPAddressParseMethod = typeof(IPAddress).GetMethod("Parse", [typeof(string)])!; + private static readonly ConstructorInfo NpgsqlInetConstructor = typeof(NpgsqlInet).GetConstructor([typeof(string)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlJsonTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlJsonTypeMapping.cs index dcc449191..e0931b198 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlJsonTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlJsonTypeMapping.cs @@ -126,5 +126,5 @@ public override Expression GenerateCodeLiteral(object value) private static readonly Expression DefaultJsonDocumentOptions = Expression.New(typeof(JsonDocumentOptions)); private static readonly MethodInfo ParseMethod = - typeof(JsonDocument).GetMethod(nameof(JsonDocument.Parse), new[] { typeof(string), typeof(JsonDocumentOptions) })!; + typeof(JsonDocument).GetMethod(nameof(JsonDocument.Parse), [typeof(string), typeof(JsonDocumentOptions)])!; } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs index e6a6c4180..a761cffa7 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs @@ -11,7 +11,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; /// public class NpgsqlLTreeTypeMapping : NpgsqlStringTypeMapping { - private static readonly ConstructorInfo Constructor = typeof(LTree).GetConstructor(new[] { typeof(string) })!; + private static readonly ConstructorInfo Constructor = typeof(LTree).GetConstructor([typeof(string)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMacaddr8TypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMacaddr8TypeMapping.cs index 61bbc1897..c73b20642 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMacaddr8TypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMacaddr8TypeMapping.cs @@ -72,5 +72,5 @@ protected override string GenerateNonNullSqlLiteral(object value) public override Expression GenerateCodeLiteral(object value) => Expression.Call(ParseMethod, Expression.Constant(((PhysicalAddress)value).ToString())); - private static readonly MethodInfo ParseMethod = typeof(PhysicalAddress).GetMethod("Parse", new[] { typeof(string) })!; + private static readonly MethodInfo ParseMethod = typeof(PhysicalAddress).GetMethod("Parse", [typeof(string)])!; } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMacaddrTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMacaddrTypeMapping.cs index 961cfee74..25ccc25bd 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMacaddrTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMacaddrTypeMapping.cs @@ -68,5 +68,5 @@ protected override string GenerateNonNullSqlLiteral(object value) public override Expression GenerateCodeLiteral(object value) => Expression.Call(ParseMethod, Expression.Constant(((PhysicalAddress)value).ToString())); - private static readonly MethodInfo ParseMethod = typeof(PhysicalAddress).GetMethod("Parse", new[] { typeof(string) })!; + private static readonly MethodInfo ParseMethod = typeof(PhysicalAddress).GetMethod("Parse", [typeof(string)])!; } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlOwnedJsonTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlOwnedJsonTypeMapping.cs index 0a0c935fc..0be732a0d 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlOwnedJsonTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlOwnedJsonTypeMapping.cs @@ -17,16 +17,16 @@ public class NpgsqlOwnedJsonTypeMapping : JsonTypeMapping public virtual NpgsqlDbType NpgsqlDbType { get; } private static readonly MethodInfo GetStringMethod - = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetString), new[] { typeof(int) })!; + = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetString), [typeof(int)])!; private static readonly PropertyInfo UTF8Property = typeof(Encoding).GetProperty(nameof(Encoding.UTF8))!; private static readonly MethodInfo EncodingGetBytesMethod - = typeof(Encoding).GetMethod(nameof(Encoding.GetBytes), new[] { typeof(string) })!; + = typeof(Encoding).GetMethod(nameof(Encoding.GetBytes), [typeof(string)])!; private static readonly ConstructorInfo MemoryStreamConstructor - = typeof(MemoryStream).GetConstructor(new[] { typeof(byte[]) })!; + = typeof(MemoryStream).GetConstructor([typeof(byte[])])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs index 4faeb5fb5..7dcb7279e 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs @@ -81,7 +81,7 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlLogSequenceNumber).GetConstructor(new[] { typeof(ulong) })!; + typeof(NpgsqlLogSequenceNumber).GetConstructor([typeof(ulong)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs index 7687410d6..d8e5d62ae 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs @@ -260,10 +260,10 @@ private void InitializeAccessors(Type rangeClrType, Type subtypeClrType) _upperInfiniteProperty = rangeClrType.GetProperty(nameof(NpgsqlRange.UpperBoundInfinite))!; _rangeConstructor1 = rangeClrType.GetConstructor( - new[] { subtypeClrType, subtypeClrType })!; + [subtypeClrType, subtypeClrType])!; _rangeConstructor2 = rangeClrType.GetConstructor( - new[] { subtypeClrType, typeof(bool), subtypeClrType, typeof(bool) })!; + [subtypeClrType, typeof(bool), subtypeClrType, typeof(bool)])!; _rangeConstructor3 = rangeClrType.GetConstructor( - new[] { subtypeClrType, typeof(bool), typeof(bool), subtypeClrType, typeof(bool), typeof(bool) })!; + [subtypeClrType, typeof(bool), typeof(bool), subtypeClrType, typeof(bool), typeof(bool)])!; } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTidTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTidTypeMapping.cs index d4185164e..bcf13e7b4 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTidTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTidTypeMapping.cs @@ -79,5 +79,5 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(NpgsqlTid).GetConstructor(new[] { typeof(uint), typeof(ushort) })!; + typeof(NpgsqlTid).GetConstructor([typeof(uint), typeof(ushort)])!; } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlVarbitTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlVarbitTypeMapping.cs index 4f024940d..909475cc2 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlVarbitTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlVarbitTypeMapping.cs @@ -92,5 +92,5 @@ public override Expression GenerateCodeLiteral(object value) } private static readonly ConstructorInfo Constructor = - typeof(BitArray).GetConstructor(new[] { typeof(bool[]) })!; + typeof(BitArray).GetConstructor([typeof(bool[])])!; } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlSqlGenerationHelper.cs b/src/EFCore.PG/Storage/Internal/NpgsqlSqlGenerationHelper.cs index bd6089b70..bbff1a515 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlSqlGenerationHelper.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlSqlGenerationHelper.cs @@ -19,7 +19,7 @@ static NpgsqlSqlGenerationHelper() using (var conn = new NpgsqlConnection()) { ReservedWords = - new HashSet(conn.GetSchema("ReservedWords").Rows.Cast().Select(r => (string)r["ReservedWord"])); + [..conn.GetSchema("ReservedWords").Rows.Cast().Select(r => (string)r["ReservedWord"])]; } } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 3c9baa486..d6661e7fb 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -217,75 +217,75 @@ public NpgsqlTypeMappingSource( // https://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE var storeTypeMappings = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "smallint", new RelationalTypeMapping[] { _int2, _int2Byte } }, - { "int2", new RelationalTypeMapping[] { _int2, _int2Byte } }, - { "integer", new[] { _int4 } }, - { "int", new[] { _int4 } }, - { "int4", new[] { _int4 } }, - { "bigint", new[] { _int8 } }, - { "int8", new[] { _int8 } }, - { "real", new[] { _float4 } }, - { "float4", new[] { _float4 } }, - { "double precision", new[] { _float8 } }, - { "float8", new[] { _float8 } }, - { "numeric", new RelationalTypeMapping[] { _numeric, _bigInteger, _numericAsFloat, _numericAsDouble } }, - { "decimal", new RelationalTypeMapping[] { _numeric, _bigInteger, _numericAsFloat, _numericAsDouble } }, - { "money", new[] { _money } }, - { "text", new[] { _text } }, - { "jsonb", new RelationalTypeMapping[] { _jsonbString, _jsonbDocument, _jsonbElement } }, - { "json", new RelationalTypeMapping[] { _jsonString, _jsonDocument, _jsonElement } }, - { "xml", new[] { _xml } }, - { "citext", new[] { _citext } }, - { "character varying", new[] { _varchar } }, - { "varchar", new[] { _varchar } }, + { "smallint", [_int2, _int2Byte] }, + { "int2", [_int2, _int2Byte] }, + { "integer", [_int4] }, + { "int", [_int4] }, + { "int4", [_int4] }, + { "bigint", [_int8] }, + { "int8", [_int8] }, + { "real", [_float4] }, + { "float4", [_float4] }, + { "double precision", [_float8] }, + { "float8", [_float8] }, + { "numeric", [_numeric, _bigInteger, _numericAsFloat, _numericAsDouble] }, + { "decimal", [_numeric, _bigInteger, _numericAsFloat, _numericAsDouble] }, + { "money", [_money] }, + { "text", [_text] }, + { "jsonb", [_jsonbString, _jsonbDocument, _jsonbElement] }, + { "json", [_jsonString, _jsonDocument, _jsonElement] }, + { "xml", [_xml] }, + { "citext", [_citext] }, + { "character varying", [_varchar] }, + { "varchar", [_varchar] }, // See FindBaseMapping below for special treatment of 'character' - { "timestamp without time zone", new[] { _timestamp } }, - { "timestamp with time zone", new[] { _timestamptz, _timestamptzDto } }, - { "interval", new[] { _interval } }, - { "date", new RelationalTypeMapping[] { _dateDateOnly, _dateDateTime } }, - { "time without time zone", new RelationalTypeMapping[] { _timeTimeOnly, _timeTimeSpan } }, - { "time with time zone", new[] { _timetz } }, - { "boolean", new[] { _bool } }, - { "bool", new[] { _bool } }, - { "bytea", new[] { _bytea } }, - { "uuid", new[] { _uuid } }, - { "bit", new[] { _bit } }, - { "bit varying", new[] { _varbit } }, - { "varbit", new[] { _varbit } }, - { "hstore", new RelationalTypeMapping[] { _hstore, _immutableHstore } }, - { "macaddr", new[] { _macaddr } }, - { "macaddr8", new[] { _macaddr8 } }, - { "inet", new RelationalTypeMapping[] { _inetAsIPAddress, _inetAsNpgsqlInet } }, - { "cidr", new[] { _cidr } }, - { "point", new[] { _point } }, - { "box", new[] { _box } }, - { "line", new[] { _line } }, - { "lseg", new[] { _lseg } }, - { "path", new[] { _path } }, - { "polygon", new[] { _polygon } }, - { "circle", new[] { _circle } }, - { "xid", new[] { _xid } }, - { "xid8", new[] { _xid8 } }, - { "oid", new[] { _oid } }, - { "cid", new[] { _cid } }, - { "regtype", new[] { _regtype } }, - { "lo", new[] { _lo } }, - { "tid", new[] { _tid } }, - { "pg_lsn", new[] { _pgLsn } }, - { "int4range", new[] { _int4range } }, - { "int8range", new[] { _int8range } }, - { "numrange", new[] { _numrange } }, - { "tsrange", new[] { _tsrange } }, - { "tstzrange", new[] { _tstzrange } }, - { "daterange", new[] { _dateOnlyDaterange, _dateTimeDaterange } }, - { "tsquery", new[] { _tsquery } }, - { "tsvector", new[] { _tsvector } }, - { "regconfig", new[] { _regconfig } }, - { "ltree", new[] { _ltree, _ltreeString } }, - { "lquery", new[] { _lquery } }, - { "ltxtquery", new[] { _ltxtquery } }, - { "regdictionary", new[] { _regdictionary } } + { "timestamp without time zone", [_timestamp] }, + { "timestamp with time zone", [_timestamptz, _timestamptzDto] }, + { "interval", [_interval] }, + { "date", [_dateDateOnly, _dateDateTime] }, + { "time without time zone", [_timeTimeOnly, _timeTimeSpan] }, + { "time with time zone", [_timetz] }, + { "boolean", [_bool] }, + { "bool", [_bool] }, + { "bytea", [_bytea] }, + { "uuid", [_uuid] }, + { "bit", [_bit] }, + { "bit varying", [_varbit] }, + { "varbit", [_varbit] }, + { "hstore", [_hstore, _immutableHstore] }, + { "macaddr", [_macaddr] }, + { "macaddr8", [_macaddr8] }, + { "inet", [_inetAsIPAddress, _inetAsNpgsqlInet] }, + { "cidr", [_cidr] }, + { "point", [_point] }, + { "box", [_box] }, + { "line", [_line] }, + { "lseg", [_lseg] }, + { "path", [_path] }, + { "polygon", [_polygon] }, + { "circle", [_circle] }, + { "xid", [_xid] }, + { "xid8", [_xid8] }, + { "oid", [_oid] }, + { "cid", [_cid] }, + { "regtype", [_regtype] }, + { "lo", [_lo] }, + { "tid", [_tid] }, + { "pg_lsn", [_pgLsn] }, + { "int4range", [_int4range] }, + { "int8range", [_int8range] }, + { "numrange", [_numrange] }, + { "tsrange", [_tsrange] }, + { "tstzrange", [_tstzrange] }, + { "daterange", [_dateOnlyDaterange, _dateTimeDaterange] }, + { "tsquery", [_tsquery] }, + { "tsvector", [_tsvector] }, + { "regconfig", [_regconfig] }, + { "ltree", [_ltree, _ltreeString] }, + { "lquery", [_lquery] }, + { "ltxtquery", [_ltxtquery] }, + { "regdictionary", [_regdictionary] } }; // ReSharper restore CoVariantArrayConversion @@ -403,7 +403,7 @@ is PropertyInfo globalEnumTypeMappingsProperty adoEnumMapping.EnumClrType, adoEnumMapping.NameTranslator); ClrTypeMappings[adoEnumMapping.EnumClrType] = mapping; - StoreTypeMappings[mapping.StoreType] = new RelationalTypeMapping[] { mapping }; + StoreTypeMappings[mapping.StoreType] = [mapping]; } } } diff --git a/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs b/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs index a052ef292..c27fb5a87 100644 --- a/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs +++ b/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs @@ -164,9 +164,9 @@ private static Expression> ArrayConversionExpression())!)), + : New(typeof(TConcreteOutput).GetConstructor([])!)), // Loop over the elements, applying the element converter on them one by one // for (var i = 0; i < length; i++) @@ -187,7 +187,7 @@ elementConversionExpression is null : Invoke(elementConversionExpression, indexer(loopVariable))) : Call( output, - typeof(TConcreteOutput).GetMethod("Add", new[] { outputElementType })!, + typeof(TConcreteOutput).GetMethod("Add", [outputElementType])!, elementConversionExpression is null ? indexer(loopVariable) : Invoke(elementConversionExpression, indexer(loopVariable)))), diff --git a/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs b/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs index aea8747aa..fa7d1fad0 100644 --- a/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs +++ b/src/EFCore.PG/Update/Internal/NpgsqlUpdateSqlGenerator.cs @@ -225,7 +225,7 @@ public override ResultSetMapping AppendDeleteOperation( requiresTransaction = false; - AppendDeleteCommand(commandStringBuilder, name, schema, Array.Empty(), conditionOperations); + AppendDeleteCommand(commandStringBuilder, name, schema, [], conditionOperations); return ResultSetMapping.NoResults; } diff --git a/src/EFCore.PG/Utilities/Util.cs b/src/EFCore.PG/Utilities/Util.cs index 9f0c14ae4..bdbceb282 100644 --- a/src/EFCore.PG/Utilities/Util.cs +++ b/src/EFCore.PG/Utilities/Util.cs @@ -3,17 +3,17 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Utilities; internal static class Statics { internal static readonly bool[][] TrueArrays = - { - Array.Empty(), - new[] { true }, - new[] { true, true }, - new[] { true, true, true }, - new[] { true, true, true, true }, - new[] { true, true, true, true, true }, - new[] { true, true, true, true, true, true }, - new[] { true, true, true, true, true, true, true }, - new[] { true, true, true, true, true, true, true, true } - }; + [ + [], + [true], + [true, true], + [true, true, true], + [true, true, true, true], + [true, true, true, true, true], + [true, true, true, true, true, true], + [true, true, true, true, true, true, true], + [true, true, true, true, true, true, true, true] + ]; - internal static readonly bool[][] FalseArrays = { Array.Empty(), new[] { false }, new[] { false, false } }; + internal static readonly bool[][] FalseArrays = [[], [false], [false, false]]; } diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index d37c585c3..ae06d69f7 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -303,7 +303,7 @@ public static IEnumerable GetTypesInHierarchy(this Type type) public static ConstructorInfo GetDeclaredConstructor(this Type type, Type[] types) { - types ??= Array.Empty(); + types ??= []; return type.GetTypeInfo().DeclaredConstructors .SingleOrDefault( @@ -540,7 +540,7 @@ public static IEnumerable GetNamespaces(this Type type) public static ConstantExpression GetDefaultValueConstant(this Type type) => (ConstantExpression)_generateDefaultValueConstantMethod - .MakeGenericMethod(type).Invoke(null, Array.Empty()); + .MakeGenericMethod(type).Invoke(null, []); private static readonly MethodInfo _generateDefaultValueConstantMethod = typeof(SharedTypeExtensions).GetTypeInfo().GetDeclaredMethod(nameof(GenerateDefaultValueConstant)); diff --git a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs index 771b6bc5b..939fa9ae0 100644 --- a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs @@ -95,7 +95,7 @@ public virtual void Can_query_using_any_mapped_data_type() // CharAsChar1 = 'f', CharAsText = 'g', CharAsVarchar = 'h', - BytesAsBytea = new byte[] { 86 }, + BytesAsBytea = [86], GuidAsUuid = new Guid("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"), EnumAsText = StringEnum16.Value4, EnumAsVarchar = StringEnumU16.Value4, @@ -106,9 +106,9 @@ public virtual void Can_query_using_any_mapped_data_type() DictionaryAsHstore = new Dictionary { { "a", "b" } }, ImmutableDictionaryAsHstore = ImmutableDictionary.Empty.Add("c", "d"), NpgsqlRangeAsRange = new NpgsqlRange(4, true, 8, false), - IntArrayAsIntArray = new[] { 2, 3 }, + IntArrayAsIntArray = [2, 3], PhysicalAddressArrayAsMacaddrArray = - new[] { PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04") }, + [PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04")], UintAsXid = (uint)int.MaxValue + 1, #pragma warning disable CS0618 // Full-text search client-parsing is obsolete @@ -555,7 +555,7 @@ private static void AssertMappedDataTypes(MappedDataTypes entity, int id) // Assert.Equal('f', entity.CharAsChar1); Assert.Equal('g', entity.CharAsText); Assert.Equal('h', entity.CharAsVarchar); - Assert.Equal(new byte[] { 86 }, entity.BytesAsBytea); + Assert.Equal([86], entity.BytesAsBytea); Assert.Equal(new Guid("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"), entity.GuidAsUuid); @@ -569,9 +569,9 @@ private static void AssertMappedDataTypes(MappedDataTypes entity, int id) Assert.Equal(new Dictionary { { "a", "b" } }, entity.DictionaryAsHstore); Assert.Equal(new NpgsqlRange(4, true, 8, false), entity.NpgsqlRangeAsRange); - Assert.Equal(new[] { 2, 3 }, entity.IntArrayAsIntArray); + Assert.Equal([2, 3], entity.IntArrayAsIntArray); Assert.Equal( - new[] { PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04") }, + [PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04")], entity.PhysicalAddressArrayAsMacaddrArray); Assert.Equal((uint)int.MaxValue + 1, entity.UintAsXid); @@ -613,7 +613,7 @@ private static MappedDataTypes CreateMappedDataTypes(int id) // CharAsChar1 = 'f', CharAsText = 'g', CharAsVarchar = 'h', - BytesAsBytea = new byte[] { 86 }, + BytesAsBytea = [86], GuidAsUuid = new Guid("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"), EnumAsText = StringEnum16.Value4, EnumAsVarchar = StringEnumU16.Value4, @@ -623,9 +623,9 @@ private static MappedDataTypes CreateMappedDataTypes(int id) StringAsJson = @"{""a"": ""b""}", DictionaryAsHstore = new Dictionary { { "a", "b" } }, NpgsqlRangeAsRange = new NpgsqlRange(4, true, 8, false), - IntArrayAsIntArray = new[] { 2, 3 }, + IntArrayAsIntArray = [2, 3], PhysicalAddressArrayAsMacaddrArray = - new[] { PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04") }, + [PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04")], UintAsXid = (uint)int.MaxValue + 1, #pragma warning disable CS0618 // Full-text search client-parsing is obsolete SearchQuery = NpgsqlTsQuery.Parse("a & b"), @@ -986,7 +986,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con ((NpgsqlTypeMappingSource)context.GetService()).LoadUserDefinedTypeMappings( context.GetService(), dataSource: null); - modelBuilder.HasPostgresEnum("mood", new[] { "happy", "sad" }); + modelBuilder.HasPostgresEnum("mood", ["happy", "sad"]); MakeRequired(modelBuilder); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs index bf9f677e6..fcd36e8cf 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs @@ -158,7 +158,7 @@ public virtual async Task Update_with_primitive_collection_in_value_selector(boo var contextFactory = await InitializeAsync( seed: ctx => { - ctx.AddRange(new EntityWithPrimitiveCollection { Tags = new List { "tag1", "tag2" }}); + ctx.AddRange(new EntityWithPrimitiveCollection { Tags = ["tag1", "tag2"] }); ctx.SaveChanges(); }); diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index 2e1279923..db8e54053 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -34,14 +34,13 @@ public override void Can_read_write_nullable_ulong_enum_JSON_values(object? valu public override void Can_read_write_collection_of_ulong_enum_JSON_values() => Can_read_and_write_JSON_value>( nameof(EnumU64CollectionType.EnumU64), - new List - { + [ EnumU64.Min, EnumU64.Max, EnumU64.Default, EnumU64.One, (EnumU64)8 - }, + ], // Relational databases don't support unsigned numeric types, so ulong is value-converted to long """{"Prop":[0,-1,0,1,8]}""", mappedCollection: true); @@ -49,15 +48,14 @@ public override void Can_read_write_collection_of_ulong_enum_JSON_values() public override void Can_read_write_collection_of_nullable_ulong_enum_JSON_values() => Can_read_and_write_JSON_value>( nameof(NullableEnumU64CollectionType.EnumU64), - new List - { + [ EnumU64.Min, null, EnumU64.Max, EnumU64.Default, EnumU64.One, (EnumU64?)8 - }, + ], // Relational databases don't support unsigned numeric types, so ulong is value-converted to long """{"Prop":[0,null,-1,0,1,8]}""", mappedCollection: true); @@ -103,14 +101,11 @@ public override void Can_read_write_collection_of_TimeSpan_JSON_values() { Can_read_and_write_JSON_value>( nameof(TimeSpanCollectionType.TimeSpan), - new List - { - // We cannot roundtrip MinValue and MaxValue since these contain sub-microsecond components, which PG does not support. - // TimeSpan.MinValue, + [ new(1, 2, 3, 4), - new(0, 2, 3, 4, 5, 678), + new(0, 2, 3, 4, 5, 678) // TimeSpan.MaxValue - }, + ], """{"Prop":["1 02:03:04","02:03:04.005678"]}""", mappedCollection: true); } @@ -119,15 +114,12 @@ public override void Can_read_write_collection_of_TimeSpan_JSON_values() public override void Can_read_write_collection_of_nullable_TimeSpan_JSON_values() => Can_read_and_write_JSON_value>( nameof(NullableTimeSpanCollectionType.TimeSpan), - new List - { - // We cannot roundtrip MinValue and MaxValue since these contain sub-microsecond components, which PG does not support. - // TimeSpan.MinValue, + [ new(1, 2, 3, 4), new(0, 2, 3, 4, 5, 678), // TimeSpan.MaxValue null - }, + ], """{"Prop":["1 02:03:04","02:03:04.005678",null]}""", mappedCollection: true); @@ -177,12 +169,11 @@ public virtual void Can_read_write_DateOnly_JSON_values_infinity() public override void Can_read_write_collection_of_DateOnly_JSON_values() => Can_read_and_write_JSON_value>( nameof(DateOnlyCollectionType.DateOnly), - new List - { + [ DateOnly.MinValue, new(2023, 5, 29), DateOnly.MaxValue - }, + ], """{"Prop":["-infinity","2023-05-29","infinity"]}""", mappedCollection: true); @@ -190,13 +181,12 @@ public override void Can_read_write_collection_of_DateOnly_JSON_values() public override void Can_read_write_collection_of_nullable_DateOnly_JSON_values() => Can_read_and_write_JSON_value>( nameof(NullableDateOnlyCollectionType.DateOnly), - new List - { + [ DateOnly.MinValue, new(2023, 5, 29), DateOnly.MaxValue, null - }, + ], """{"Prop":["-infinity","2023-05-29","infinity",null]}""", mappedCollection: true); @@ -251,12 +241,11 @@ public virtual void Can_read_write_nullable_DateTime_JSON_values_npgsql(string? public override void Can_read_write_collection_of_DateTime_JSON_values() => Can_read_and_write_JSON_value>( nameof(DateTimeCollectionType.DateTime), - new List - { + [ DateTime.MinValue, new(2023, 5, 29, 10, 52, 47, DateTimeKind.Utc), DateTime.MaxValue - }, + ], """{"Prop":["-infinity","2023-05-29T10:52:47Z","infinity"]}""", mappedCollection: true); @@ -264,13 +253,12 @@ public override void Can_read_write_collection_of_DateTime_JSON_values() public override void Can_read_write_collection_of_nullable_DateTime_JSON_values() => Can_read_and_write_JSON_value>( nameof(NullableDateTimeCollectionType.DateTime), - new List - { + [ DateTime.MinValue, null, new(2023, 5, 29, 10, 52, 47, DateTimeKind.Utc), DateTime.MaxValue - }, + ], """{"Prop":["-infinity",null,"2023-05-29T10:52:47Z","infinity"]}""", mappedCollection: true); @@ -338,14 +326,13 @@ public virtual void Can_read_write_nullable_DateTimeOffset_JSON_values_npgsql(st public override void Can_read_write_collection_of_DateTimeOffset_JSON_values() => Can_read_and_write_JSON_value>( nameof(DateTimeOffsetCollectionType.DateTimeOffset), - new List - { + [ DateTimeOffset.MinValue, new(new DateTime(2023, 5, 29, 10, 52, 47), new TimeSpan(-2, 0, 0)), new(new DateTime(2023, 5, 29, 10, 52, 47), new TimeSpan(0, 0, 0)), new(new DateTime(2023, 5, 29, 10, 52, 47), new TimeSpan(2, 0, 0)), DateTimeOffset.MaxValue - }, + ], """{"Prop":["-infinity","2023-05-29T10:52:47-02:00","2023-05-29T10:52:47\u002B00:00","2023-05-29T10:52:47\u002B02:00","infinity"]}""", mappedCollection: true); @@ -353,15 +340,14 @@ public override void Can_read_write_collection_of_DateTimeOffset_JSON_values() public override void Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values() => Can_read_and_write_JSON_value>( nameof(NullableDateTimeOffsetCollectionType.DateTimeOffset), - new List - { + [ DateTimeOffset.MinValue, new(new DateTime(2023, 5, 29, 10, 52, 47), new TimeSpan(-2, 0, 0)), new(new DateTime(2023, 5, 29, 10, 52, 47), new TimeSpan(0, 0, 0)), null, new(new DateTime(2023, 5, 29, 10, 52, 47), new TimeSpan(2, 0, 0)), DateTimeOffset.MaxValue - }, + ], """{"Prop":["-infinity","2023-05-29T10:52:47-02:00","2023-05-29T10:52:47\u002B00:00",null,"2023-05-29T10:52:47\u002B02:00","infinity"]}""", mappedCollection: true); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 96e931e30..338273052 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2370,10 +2370,10 @@ await Test( builder => builder.Entity( "People", b => { - b.HasIndex(new[] { "Age" }, "IX_NullsDistinct") + b.HasIndex(["Age"], "IX_NullsDistinct") .IsUnique(); - b.HasIndex(new[] { "Age" }, "IX_NullsNotDistinct") + b.HasIndex(["Age"], "IX_NullsNotDistinct") .IsUnique() .AreNullsDistinct(false); }), @@ -2918,7 +2918,7 @@ public virtual async Task Create_enum() { await Test( _ => { }, - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Sad" }), + builder => builder.HasPostgresEnum("Mood", ["Happy", "Sad"]), model => { var moodEnum = Assert.Single(model.GetPostgresEnums()); @@ -2939,7 +2939,7 @@ public virtual async Task Create_enum_with_schema() { await Test( _ => { }, - builder => builder.HasPostgresEnum("some_schema", "Mood", new[] { "Happy", "Sad" }), + builder => builder.HasPostgresEnum("some_schema", "Mood", ["Happy", "Sad"]), model => { var moodEnum = Assert.Single(model.GetPostgresEnums()); @@ -2968,7 +2968,7 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'some_schema') THEN public virtual async Task Drop_enum() { await Test( - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Sad" }), + builder => builder.HasPostgresEnum("Mood", ["Happy", "Sad"]), _ => { }, model => Assert.Empty(model.GetPostgresEnums())); @@ -2980,9 +2980,9 @@ await Test( public virtual async Task Do_not_alter_existing_enum_when_creating_new_one() { await Test( - builder => builder.HasPostgresEnum("Enum1", new[] { "A", "B" }), + builder => builder.HasPostgresEnum("Enum1", ["A", "B"]), _ => { }, - builder => builder.HasPostgresEnum("Enum2", new[] { "X", "Y" }), + builder => builder.HasPostgresEnum("Enum2", ["X", "Y"]), model => Assert.Equal(2, model.GetPostgresEnums().Count())); AssertSql( @@ -2993,8 +2993,8 @@ await Test( public virtual async Task Alter_enum_add_label_at_end() { await Test( - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Sad" }), - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Sad", "Angry" }), + builder => builder.HasPostgresEnum("Mood", ["Happy", "Sad"]), + builder => builder.HasPostgresEnum("Mood", ["Happy", "Sad", "Angry"]), model => { var moodEnum = Assert.Single(model.GetPostgresEnums()); @@ -3013,8 +3013,8 @@ await Test( public virtual async Task Alter_enum_add_label_in_middle() { await Test( - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Sad" }), - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Angry", "Sad" }), + builder => builder.HasPostgresEnum("Mood", ["Happy", "Sad"]), + builder => builder.HasPostgresEnum("Mood", ["Happy", "Angry", "Sad"]), model => { var moodEnum = Assert.Single(model.GetPostgresEnums()); @@ -3032,14 +3032,14 @@ await Test( [Fact] public virtual Task Alter_enum_drop_label_not_supported() => TestThrows( - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Sad" }), - builder => builder.HasPostgresEnum("Mood", new[] { "Happy" })); + builder => builder.HasPostgresEnum("Mood", ["Happy", "Sad"]), + builder => builder.HasPostgresEnum("Mood", ["Happy"])); [Fact] public virtual Task Alter_enum_change_label_not_supported() => TestThrows( - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Sad" }), - builder => builder.HasPostgresEnum("Mood", new[] { "Happy", "Angry" })); + builder => builder.HasPostgresEnum("Mood", ["Happy", "Sad"]), + builder => builder.HasPostgresEnum("Mood", ["Happy", "Angry"])); #endregion @@ -3270,5 +3270,5 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build protected override ICollection GetAdditionalReferences() => AdditionalReferences; - private static readonly BuildReference[] AdditionalReferences = { BuildReference.ByName("Npgsql") }; + private static readonly BuildReference[] AdditionalReferences = [BuildReference.ByName("Npgsql")]; } diff --git a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs index a9e96d2c5..ac5fec041 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs @@ -490,8 +490,7 @@ public void Alter_column_change_serial_to_identity_idempotent(MigrationsSqlGener modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "3.1.0"); modelBuilder.Entity().Property("Id").UseSerialColumn(); }, - new[] - { + [ new AlterColumnOperation { Table = "Person", @@ -508,7 +507,7 @@ public void Alter_column_change_serial_to_identity_idempotent(MigrationsSqlGener NpgsqlValueGenerationStrategy.SerialColumn, } } - }, + ], options); AssertSql( @@ -527,7 +526,7 @@ public void Create_schema_idempotent() { Generate( _ => { }, - new[] { new EnsureSchemaOperation { Name = "some_schema" } }, + [new EnsureSchemaOperation { Name = "some_schema" }], MigrationsSqlGenerationOptions.Idempotent); AssertSql( @@ -563,13 +562,13 @@ public void CreateTableOperation_with_cockroach_interleave_in_parent() IsNullable = false }, }, - PrimaryKey = new AddPrimaryKeyOperation { Columns = new[] { "Id" } } + PrimaryKey = new AddPrimaryKeyOperation { Columns = ["Id"] } }; var interleaveInParent = new CockroachDbInterleaveInParent(op); interleaveInParent.ParentTableSchema = "my_schema"; interleaveInParent.ParentTableName = "my_parent"; - interleaveInParent.InterleavePrefix = new List { "col_a", "col_b" }; + interleaveInParent.InterleavePrefix = ["col_a", "col_b"]; Generate(op); @@ -612,8 +611,8 @@ public override void InsertDataOperation_throws_for_unsupported_column_types() { Table = "People", Schema = "dbo", - Columns = new[] { "First Name" }, - ColumnTypes = new[] { "foo" }, + Columns = ["First Name"], + ColumnTypes = ["foo"], Values = new object[,] { { null } } })).Message); diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlApiConsistencyTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlApiConsistencyTest.cs index c5c596b2c..5b331b827 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlApiConsistencyTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlApiConsistencyTest.cs @@ -14,8 +14,8 @@ protected override Assembly TargetAssembly public class NpgsqlApiConsistencyFixture : ApiConsistencyFixtureBase { - public override HashSet FluentApiTypes { get; } = new() - { + public override HashSet FluentApiTypes { get; } = + [ typeof(NpgsqlDbContextOptionsBuilder), typeof(NpgsqlDbContextOptionsBuilderExtensions), typeof(NpgsqlMigrationBuilderExtensions), @@ -24,14 +24,14 @@ public class NpgsqlApiConsistencyFixture : ApiConsistencyFixtureBase typeof(NpgsqlPropertyBuilderExtensions), typeof(NpgsqlEntityTypeBuilderExtensions), typeof(NpgsqlServiceCollectionExtensions) - }; + ]; - public override HashSet UnmatchedMetadataMethods { get; } = new() - { + public override HashSet UnmatchedMetadataMethods { get; } = + [ typeof(NpgsqlPropertyBuilderExtensions).GetMethod( nameof(NpgsqlPropertyBuilderExtensions.IsGeneratedTsVectorColumn), - new[] { typeof(PropertyBuilder), typeof(string), typeof(string[]) }) - }; + [typeof(PropertyBuilder), typeof(string), typeof(string[])]) + ]; public override Dictionary MetadataMethodExceptions { get; } = new() - { + public override HashSet MetadataMethodExceptions { get; } = + [ typeof(NpgsqlEntityTypeExtensions).GetRuntimeMethod( nameof(NpgsqlEntityTypeExtensions.SetStorageParameter), - new[] { typeof(IConventionEntityType), typeof(string), typeof(object), typeof(bool) }) - }; + [typeof(IConventionEntityType), typeof(string), typeof(object), typeof(bool)]) + ]; } } diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs index 0a2ff9dcb..5efd47db1 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs @@ -208,8 +208,7 @@ private static async Task Creates_physical_database_and_schema_test( Assert.Equal(14, columns.Length); Assert.Equal( - new[] - { + [ "Blogs.AndChew (bytea)", "Blogs.AndRow (bytea)", "Blogs.Cheese (text)", @@ -224,7 +223,7 @@ private static async Task Creates_physical_database_and_schema_test( "Blogs.TheGu (uuid)", "Blogs.ToEat (smallint)", "Blogs.WayRound (bigint)" - }, + ], columns); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs index 98a30442c..bd8d59e33 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs @@ -968,7 +968,7 @@ protected override Expression VisitBinary(BinaryExpression node) var listExpression = Visit(node.Left); if (listExpression.Type.IsGenericList()) { - var getItemMethod = listExpression.Type.GetMethod("get_Item", new[] { typeof(int) })!; + var getItemMethod = listExpression.Type.GetMethod("get_Item", [typeof(int)])!; return Expression.Call(listExpression, getItemMethod, node.Right); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs index 0bc0b3508..dd35fc5f4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs @@ -24,16 +24,15 @@ public void Roundtrip() using var ctx = CreateContext(); var x = ctx.SomeEntities.Single(e => e.Id == 1); - Assert.Equal(new[] { 3, 4 }, x.IntArray); - Assert.Equal(new List { 3, 4 }, x.IntList); - Assert.Equal(new int?[] { 3, 4, null }, x.NullableIntArray); + Assert.Equal([3, 4], x.IntArray); + Assert.Equal([3, 4], x.IntList); + Assert.Equal([3, 4, null], x.NullableIntArray); Assert.Equal( - new List - { - 3, - 4, - null - }, x.NullableIntList); + [ + 3, + 4, + null + ], x.NullableIntList); } #endregion diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index 5676f51a1..c7f577afc 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -224,7 +224,7 @@ public class EnumContext(DbContextOptions options) : PoolableDbContext(options) protected override void OnModelCreating(ModelBuilder builder) => builder - .HasPostgresEnum("mapped_enum", new[] { "happy", "sad" }) + .HasPostgresEnum("mapped_enum", ["happy", "sad"]) .HasPostgresEnum() .HasPostgresEnum() .HasDefaultSchema("test") diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs index 3bb952e3e..096206a07 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs @@ -716,7 +716,7 @@ public static void Seed(JsonPocoQueryContext context) { Id = 1, Customer = CreateCustomer1(), - ToplevelArray = new[] { "one", "two", "three" } + ToplevelArray = ["one", "two", "three"] }, new JsonbEntity { Id = 2, Customer = CreateCustomer2() }); context.JsonEntities.AddRange( @@ -724,7 +724,7 @@ public static void Seed(JsonPocoQueryContext context) { Id = 1, Customer = CreateCustomer1(), - ToplevelArray = new[] { "one", "two", "three" } + ToplevelArray = ["one", "two", "three"] }, new JsonEntity { Id = 2, Customer = CreateCustomer2() }); context.SaveChanges(); @@ -745,12 +745,12 @@ static Customer CreateCustomer1() SomeProperty = 10, SomeNullableInt = 20, SomeNullableGuid = Guid.Parse("d5f2685d-e5c4-47e5-97aa-d0266154eb2d"), - IntArray = new[] { 3, 4 }, - IntList = new List { 3, 4 } + IntArray = [3, 4], + IntList = [3, 4] } }, - Orders = new[] - { + Orders = + [ new Order { Price = 99.5m, ShippingAddress = "Some address 1", @@ -759,7 +759,7 @@ static Customer CreateCustomer1() { Price = 23, ShippingAddress = "Some address 2", } - }, + ], VariousTypes = new VariousTypes { String = "foo", @@ -789,22 +789,22 @@ static Customer CreateCustomer2() SomeProperty = 20, SomeNullableInt = null, SomeNullableGuid = null, - IntArray = new[] { 5, 6, 7 }, - IntList = new List - { + IntArray = [5, 6, 7], + IntList = + [ 5, 6, 7 - } + ] } }, - Orders = new[] - { + Orders = + [ new Order { Price = 5, ShippingAddress = "Moe's address", } - }, + ], VariousTypes = new VariousTypes { String = "bar", diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs index c44c70060..a26bb18fd 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs @@ -24,8 +24,8 @@ public virtual async Task Json_predicate_on_bytea(bool async) seed: context => { context.Entities.AddRange( - new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = new byte[] { 1, 2, 3 } } }, - new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = new byte[] { 1, 2, 4 } } }); + new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 3] } }, + new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 4] } }); context.SaveChanges(); }); @@ -107,19 +107,19 @@ protected override void Seed29219(MyContext29219 ctx) { Id = 1, Reference = new MyJsonEntity29219 { NonNullableScalar = 10, NullableScalar = 11 }, - Collection = new List - { + Collection = + [ new() { NonNullableScalar = 100, NullableScalar = 101 }, new() { NonNullableScalar = 200, NullableScalar = 201 }, - new() { NonNullableScalar = 300, NullableScalar = null }, - } + new() { NonNullableScalar = 300, NullableScalar = null } + ] }; var entity2 = new MyEntity29219 { Id = 2, Reference = new MyJsonEntity29219 { NonNullableScalar = 20, NullableScalar = null }, - Collection = new List { new() { NonNullableScalar = 1001, NullableScalar = null }, } + Collection = [new() { NonNullableScalar = 1001, NullableScalar = null }] }; ctx.Entities.AddRange(entity1, entity2); @@ -178,19 +178,19 @@ protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) Id = 1, Reference = new MyJsonEntityArrayOfPrimitives { - IntArray = new[] { 1, 2, 3 }, - ListOfString = new List - { + IntArray = [1, 2, 3], + ListOfString = + [ "Foo", "Bar", "Baz" - } + ] }, - Collection = new List - { - new() { IntArray = new[] { 111, 112, 113 }, ListOfString = new List { "Foo11", "Bar11" } }, - new() { IntArray = new[] { 211, 212, 213 }, ListOfString = new List { "Foo12", "Bar12" } }, - } + Collection = + [ + new() { IntArray = [111, 112, 113], ListOfString = ["Foo11", "Bar11"] }, + new() { IntArray = [211, 212, 213], ListOfString = ["Foo12", "Bar12"] } + ] }; var entity2 = new MyEntityArrayOfPrimitives @@ -198,19 +198,19 @@ protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) Id = 2, Reference = new MyJsonEntityArrayOfPrimitives { - IntArray = new[] { 10, 20, 30 }, - ListOfString = new List - { + IntArray = [10, 20, 30], + ListOfString = + [ "A", "B", "C" - } + ] }, - Collection = new List - { - new() { IntArray = new[] { 110, 120, 130 }, ListOfString = new List { "A1", "Z1" } }, - new() { IntArray = new[] { 210, 220, 230 }, ListOfString = new List { "A2", "Z2" } }, - } + Collection = + [ + new() { IntArray = [110, 120, 130], ListOfString = ["A1", "Z1"] }, + new() { IntArray = [210, 220, 230], ListOfString = ["A2", "Z2"] } + ] }; ctx.Entities.AddRange(entity1, entity2); diff --git a/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs index 67a8ecc95..91781976a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs @@ -523,7 +523,7 @@ public static void Seed(LTreeQueryContext context) { ltreeEntity.LTreeAsString = ltreeEntity.LTree; ltreeEntity.SomeString = "*.Astrophysics"; - ltreeEntity.LTrees = new LTree[] { ltreeEntity.LTree, "Foo" }; + ltreeEntity.LTrees = [ltreeEntity.LTree, "Foo"]; } context.LTreeEntities.AddRange(ltreeEntities); diff --git a/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs index f88be7e10..d62f2518c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs @@ -488,7 +488,7 @@ public void Intersect_aggregate() .Select(g => g.Select(x => x.IntMultirange).RangeIntersectAgg()) .Single(); - Assert.Equal(new NpgsqlRange[] { new(4, true, 6, false), new(7, true, 9, false) }, intersection); + Assert.Equal([new(4, true, 6, false), new(7, true, 9, false)], intersection); AssertSql( """ @@ -759,36 +759,34 @@ public static void Seed(MultirangeContext context) new MultirangeTestEntity { Id = 1, - IntMultirange = new NpgsqlRange[] { new(0, 5), new(7, 10) }, - LongMultirange = new NpgsqlRange[] { new(0, 5), new(7, 10) }, - DecimalMultirange = new NpgsqlRange[] { new(0, 5), new(7, 10) }, + IntMultirange = [new(0, 5), new(7, 10)], + LongMultirange = [new(0, 5), new(7, 10)], + DecimalMultirange = [new(0, 5), new(7, 10)], DateOnlyDateMultirange = - new NpgsqlRange[] - { - new(new DateOnly(2020, 1, 1), new DateOnly(2020, 1, 5)), + [ + new(new DateOnly(2020, 1, 1), new DateOnly(2020, 1, 5)), new(new DateOnly(2020, 1, 7), new DateOnly(2020, 1, 10)) - }, - DateTimeDateMultirange = new NpgsqlRange[] - { + ], + DateTimeDateMultirange = + [ new(new DateTime(2020, 1, 1), new DateTime(2020, 1, 5)), new(new DateTime(2020, 1, 7), new DateTime(2020, 1, 10)) - } + ] }, new MultirangeTestEntity { Id = 2, - IntMultirange = new NpgsqlRange[] { new(4, 8), new(13, 20) }, - LongMultirange = new NpgsqlRange[] { new(4, 8), new(13, 20) }, - DecimalMultirange = new NpgsqlRange[] { new(4, 8), new(13, 20) }, + IntMultirange = [new(4, 8), new(13, 20)], + LongMultirange = [new(4, 8), new(13, 20)], + DecimalMultirange = [new(4, 8), new(13, 20)], DateOnlyDateMultirange = - new NpgsqlRange[] - { - new(new DateOnly(2020, 1, 4), new DateOnly(2020, 1, 8)), + [ + new(new DateOnly(2020, 1, 4), new DateOnly(2020, 1, 8)), new(new DateOnly(2020, 1, 13), new DateOnly(2020, 1, 20)) - }, - DateTimeDateMultirange = new NpgsqlRange[] - { + ], + DateTimeDateMultirange = + [ new(new DateTime(2020, 1, 4), new DateTime(2020, 1, 8)), new(new DateTime(2020, 1, 13), new DateTime(2020, 1, 20)) - } + ] }); context.SaveChanges(); diff --git a/test/EFCore.PG.FunctionalTests/Query/QueryFilterFuncletizationNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/QueryFilterFuncletizationNpgsqlTest.cs index cafe75bdb..4c820b3ef 100644 --- a/test/EFCore.PG.FunctionalTests/Query/QueryFilterFuncletizationNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/QueryFilterFuncletizationNpgsqlTest.cs @@ -22,15 +22,15 @@ public override void DbContext_list_is_parameterized() // when the list is null. We translate to server-side with PostgresAnyExpression, so no exception is thrown. // Assert.Throws(() => context.Set().ToList()); - context.TenantIds = new List(); + context.TenantIds = []; var query = context.Set().ToList(); Assert.Empty(query); - context.TenantIds = new List { 1 }; + context.TenantIds = [1]; query = context.Set().ToList(); Assert.Single(query); - context.TenantIds = new List { 2, 3 }; + context.TenantIds = [2, 3]; query = context.Set().ToList(); Assert.Equal(2, query.Count); } diff --git a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs index 880ade010..764a1a636 100644 --- a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs @@ -259,7 +259,7 @@ public void Union_aggregate() .Select(g => g.Select(x => x.IntRange).RangeAgg()) .Single(); - Assert.Equal(new NpgsqlRange[] { new(1, true, 16, false) }, union); + Assert.Equal([new(1, true, 16, false)], union); AssertSql( """ diff --git a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs index 86d0c9b83..20d115583 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs @@ -24,7 +24,7 @@ protected override bool AssertDistances public static IEnumerable IsAsyncDataAndUseSpheroid = new[] { - new object[] { false, false }, new object[] { false, true }, new object[] { true, false }, new object[] { true, true } + [false, false], [false, true], [true, false], new object[] { true, true } }; public override async Task Area(bool async) diff --git a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeometryTest.cs b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeometryTest.cs index 8e156866b..6bf97811b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeometryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeometryTest.cs @@ -740,7 +740,7 @@ public override async Task Z(bool async) [MemberData(nameof(IsAsyncData))] public virtual async Task MultiString_Any(bool async) { - var lineString = Fixture.GeometryFactory.CreateLineString(new[] { new Coordinate(1, 0), new Coordinate(1, 1) }); + var lineString = Fixture.GeometryFactory.CreateLineString([new Coordinate(1, 0), new Coordinate(1, 1)]); // Note the subtle difference between Contains and Any here: Contains resolves to Geometry.Contains, which checks whether a geometry // is contained in another; this is different from .NET collection/enumerable Contains, which checks whether an item is in a diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index b30d4e609..f94f7c692 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -978,7 +978,7 @@ public static IReadOnlyList CreateEntities() TimestampDateTimeOffset = new DateTimeOffset(utcDateTime1), TimestamptzDateTimeArray = utcDateTimeArray1, TimestampDateTimeArray = localDateTimeArray1, - TimestampDateTimeOffsetArray = new DateTimeOffset[] { new(utcDateTimeArray1[0]), new(utcDateTimeArray1[1]) }, + TimestampDateTimeOffsetArray = [new(utcDateTimeArray1[0]), new(utcDateTimeArray1[1])], TimestamptzDateTimeRange = utcDateTimeRange1, TimestampDateTimeRange = localDateTimeRange1, }, @@ -990,7 +990,7 @@ public static IReadOnlyList CreateEntities() TimestampDateTimeOffset = new DateTimeOffset(utcDateTime2), TimestamptzDateTimeArray = utcDateTimeArray2, TimestampDateTimeArray = localDateTimeArray2, - TimestampDateTimeOffsetArray = new DateTimeOffset[] { new(utcDateTimeArray2[0]), new(utcDateTimeArray2[1]) }, + TimestampDateTimeOffsetArray = [new(utcDateTimeArray2[0]), new(utcDateTimeArray2[1])], TimestamptzDateTimeRange = utcDateTimeRange2, TimestampDateTimeRange = localDateTimeRange2, } diff --git a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs index 06b83238d..35b749fb0 100644 --- a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs @@ -541,7 +541,7 @@ public void Create_primary_key() Assert.Equal("public", pk.Table.Schema); Assert.Equal("PrimaryKeyTable", pk.Table.Name); Assert.StartsWith("PrimaryKeyTable_pkey", pk.Name); - Assert.Equal(new List { "Id" }, pk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id"], pk.Columns.Select(ic => ic.Name).ToList()); }, @"DROP TABLE ""PrimaryKeyTable"""); @@ -571,12 +571,12 @@ public void Create_unique_constraints() Assert.Equal("public", firstConstraint.Table.Schema); Assert.Equal("UniqueConstraint", firstConstraint.Table.Name); //Assert.StartsWith("UQ__UniqueCo", uniqueConstraint.Name); - Assert.Equal(new List { "Name" }, firstConstraint.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Name"], firstConstraint.Columns.Select(ic => ic.Name).ToList()); var secondConstraint = table.UniqueConstraints.Single(c => c.Columns.Count == 2); Assert.Equal("public", secondConstraint.Table.Schema); Assert.Equal("UniqueConstraint", secondConstraint.Table.Name); - Assert.Equal(new List { "Unq1", "Unq2" }, secondConstraint.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Unq1", "Unq2"], secondConstraint.Columns.Select(ic => ic.Name).ToList()); }, @"DROP TABLE ""UniqueConstraint"""); @@ -645,8 +645,8 @@ FOREIGN KEY ("Id") REFERENCES "PrincipalTable"("Id") ON DELETE NO ACTION Assert.Equal("FirstDependent", firstFk.Table.Name); Assert.Equal("public", firstFk.PrincipalTable.Schema); Assert.Equal("PrincipalTable", firstFk.PrincipalTable.Name); - Assert.Equal(new List { "ForeignKeyId" }, firstFk.Columns.Select(ic => ic.Name).ToList()); - Assert.Equal(new List { "Id" }, firstFk.PrincipalColumns.Select(ic => ic.Name).ToList()); + Assert.Equal(["ForeignKeyId"], firstFk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id"], firstFk.PrincipalColumns.Select(ic => ic.Name).ToList()); Assert.Equal(ReferentialAction.Cascade, firstFk.OnDelete); var secondFk = Assert.Single(dbModel.Tables.Single(t => t.Name == "SecondDependent").ForeignKeys); @@ -656,8 +656,8 @@ FOREIGN KEY ("Id") REFERENCES "PrincipalTable"("Id") ON DELETE NO ACTION Assert.Equal("SecondDependent", secondFk.Table.Name); Assert.Equal("public", secondFk.PrincipalTable.Schema); Assert.Equal("PrincipalTable", secondFk.PrincipalTable.Name); - Assert.Equal(new List { "Id" }, secondFk.Columns.Select(ic => ic.Name).ToList()); - Assert.Equal(new List { "Id" }, secondFk.PrincipalColumns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id"], secondFk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id"], secondFk.PrincipalColumns.Select(ic => ic.Name).ToList()); Assert.Equal(ReferentialAction.NoAction, secondFk.OnDelete); }, """ @@ -1090,7 +1090,7 @@ PRIMARY KEY ("Id2", "Id1") Assert.Equal("public", pk.Table.Schema); Assert.Equal("CompositePrimaryKeyTable", pk.Table.Name); - Assert.Equal(new List { "Id2", "Id1" }, pk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id2", "Id1"], pk.Columns.Select(ic => ic.Name).ToList()); }, @"DROP TABLE ""CompositePrimaryKeyTable"""); @@ -1113,7 +1113,7 @@ public void Set_primary_key_name_from_index() Assert.Equal("public", pk.Table.Schema); Assert.Equal("PrimaryKeyName", pk.Table.Name); Assert.StartsWith("MyPK", pk.Name); - Assert.Equal(new List { "Id2" }, pk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id2"], pk.Columns.Select(ic => ic.Name).ToList()); }, @"DROP TABLE ""PrimaryKeyName"""); @@ -1141,7 +1141,7 @@ public void Create_composite_unique_constraint() Assert.Equal("public", uniqueConstraint.Table.Schema); Assert.Equal("CompositeUniqueConstraintTable", uniqueConstraint.Table.Name); Assert.Equal("UX", uniqueConstraint.Name); - Assert.Equal(new List { "Id2", "Id1" }, uniqueConstraint.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id2", "Id1"], uniqueConstraint.Columns.Select(ic => ic.Name).ToList()); }, @"DROP TABLE ""CompositeUniqueConstraintTable"""); @@ -1166,7 +1166,7 @@ public void Set_unique_constraint_name_from_index() Assert.Equal("public", uniqueConstraint.Table.Schema); Assert.Equal("UniqueConstraintName", uniqueConstraint.Table.Name); Assert.Equal("MyUC", uniqueConstraint.Name); - Assert.Equal(new List { "Id2" }, uniqueConstraint.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id2"], uniqueConstraint.Columns.Select(ic => ic.Name).ToList()); Assert.Empty(table.Indexes); }, @"DROP TABLE ""UniqueConstraintName"""); @@ -1196,7 +1196,7 @@ public void Create_composite_index() Assert.Equal("public", index.Table.Schema); Assert.Equal("CompositeIndexTable", index.Table.Name); Assert.Equal("IX_COMPOSITE", index.Name); - Assert.Equal(new List { "Id2", "Id1" }, index.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id2", "Id1"], index.Columns.Select(ic => ic.Name).ToList()); }, @"DROP TABLE ""CompositeIndexTable"""); @@ -1223,7 +1223,7 @@ public void Set_unique_true_for_unique_index() Assert.Equal("IX_UNIQUE", index.Name); Assert.True(index.IsUnique); Assert.Null(index.Filter); - Assert.Equal(new List { "Id2" }, index.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id2"], index.Columns.Select(ic => ic.Name).ToList()); }, @"DROP TABLE ""UniqueIndexTable"""); @@ -1249,7 +1249,7 @@ public void Set_filter_for_filtered_index() Assert.Equal("FilteredIndexTable", index.Table.Name); Assert.Equal("IX_UNIQUE", index.Name); Assert.Equal(@"(""Id2"" > 10)", index.Filter); - Assert.Equal(new List { "Id2" }, index.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id2"], index.Columns.Select(ic => ic.Name).ToList()); }, @"DROP TABLE ""FilteredIndexTable"""); @@ -1285,8 +1285,8 @@ FOREIGN KEY ("ForeignKeyId1", "ForeignKeyId2") REFERENCES "PrincipalTable"("Id1" Assert.Equal("DependentTable", fk.Table.Name); Assert.Equal("public", fk.PrincipalTable.Schema); Assert.Equal("PrincipalTable", fk.PrincipalTable.Name); - Assert.Equal(new List { "ForeignKeyId1", "ForeignKeyId2" }, fk.Columns.Select(ic => ic.Name).ToList()); - Assert.Equal(new List { "Id1", "Id2" }, fk.PrincipalColumns.Select(ic => ic.Name).ToList()); + Assert.Equal(["ForeignKeyId1", "ForeignKeyId2"], fk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id1", "Id2"], fk.PrincipalColumns.Select(ic => ic.Name).ToList()); Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); }, """ @@ -1329,8 +1329,8 @@ FOREIGN KEY ("ForeignKeyId2") REFERENCES "AnotherPrincipalTable"("Id") ON DELETE Assert.Equal("DependentTable", principalFk.Table.Name); Assert.Equal("public", principalFk.PrincipalTable.Schema); Assert.Equal("PrincipalTable", principalFk.PrincipalTable.Name); - Assert.Equal(new List { "ForeignKeyId1" }, principalFk.Columns.Select(ic => ic.Name).ToList()); - Assert.Equal(new List { "Id" }, principalFk.PrincipalColumns.Select(ic => ic.Name).ToList()); + Assert.Equal(["ForeignKeyId1"], principalFk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id"], principalFk.PrincipalColumns.Select(ic => ic.Name).ToList()); Assert.Equal(ReferentialAction.Cascade, principalFk.OnDelete); var anotherPrincipalFk = Assert.Single(foreignKeys.Where(f => f.PrincipalTable.Name == "AnotherPrincipalTable")); @@ -1340,8 +1340,8 @@ FOREIGN KEY ("ForeignKeyId2") REFERENCES "AnotherPrincipalTable"("Id") ON DELETE Assert.Equal("DependentTable", anotherPrincipalFk.Table.Name); Assert.Equal("public", anotherPrincipalFk.PrincipalTable.Schema); Assert.Equal("AnotherPrincipalTable", anotherPrincipalFk.PrincipalTable.Name); - Assert.Equal(new List { "ForeignKeyId2" }, anotherPrincipalFk.Columns.Select(ic => ic.Name).ToList()); - Assert.Equal(new List { "Id" }, anotherPrincipalFk.PrincipalColumns.Select(ic => ic.Name).ToList()); + Assert.Equal(["ForeignKeyId2"], anotherPrincipalFk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id"], anotherPrincipalFk.PrincipalColumns.Select(ic => ic.Name).ToList()); Assert.Equal(ReferentialAction.Cascade, anotherPrincipalFk.OnDelete); }, """ @@ -1376,8 +1376,8 @@ FOREIGN KEY ("ForeignKeyId") REFERENCES "PrincipalTable"("Id2") ON DELETE CASCAD Assert.Equal("DependentTable", fk.Table.Name); Assert.Equal("public", fk.PrincipalTable.Schema); Assert.Equal("PrincipalTable", fk.PrincipalTable.Name); - Assert.Equal(new List { "ForeignKeyId" }, fk.Columns.Select(ic => ic.Name).ToList()); - Assert.Equal(new List { "Id2" }, fk.PrincipalColumns.Select(ic => ic.Name).ToList()); + Assert.Equal(["ForeignKeyId"], fk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id2"], fk.PrincipalColumns.Select(ic => ic.Name).ToList()); Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); }, """ @@ -1410,8 +1410,8 @@ public void Set_name_for_foreign_key() Assert.Equal("DependentTable", fk.Table.Name); Assert.Equal("public", fk.PrincipalTable.Schema); Assert.Equal("PrincipalTable", fk.PrincipalTable.Name); - Assert.Equal(new List { "ForeignKeyId" }, fk.Columns.Select(ic => ic.Name).ToList()); - Assert.Equal(new List { "Id" }, fk.PrincipalColumns.Select(ic => ic.Name).ToList()); + Assert.Equal(["ForeignKeyId"], fk.Columns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id"], fk.PrincipalColumns.Select(ic => ic.Name).ToList()); Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); // ReSharper disable once StringLiteralTypo Assert.Equal("MYFK", fk.Name); @@ -1455,7 +1455,7 @@ FOREIGN KEY ("ForeignKeySetDefaultId") REFERENCES "PrincipalTable"("Id") ON DELE Assert.Equal("DependentTable", fk.Table.Name); Assert.Equal("public", fk.PrincipalTable.Schema); Assert.Equal("PrincipalTable", fk.PrincipalTable.Name); - Assert.Equal(new List { "Id" }, fk.PrincipalColumns.Select(ic => ic.Name).ToList()); + Assert.Equal(["Id"], fk.PrincipalColumns.Select(ic => ic.Name).ToList()); } Assert.Equal( @@ -1886,7 +1886,7 @@ public void Index_covering() // Assert.Equal(new[] { "b", "c" }, indexWith.FindAnnotation(NpgsqlAnnotationNames.IndexInclude).Value); var indexWithout = table.Indexes.Single(i => i.Name == "ix_without"); - Assert.Equal(new[] { "a", "b", "c" }, indexWithout.Columns.Select(i => i.Name).ToArray()); + Assert.Equal(["a", "b", "c"], indexWithout.Columns.Select(i => i.Name).ToArray()); Assert.Null(indexWithout.FindAnnotation(NpgsqlAnnotationNames.IndexInclude)); }, @"DROP TABLE ""IndexCovering"""); @@ -2103,9 +2103,9 @@ line line new TypeMappingSourceDependencies( new ValueConverterSelector(new ValueConverterSelectorDependencies()), new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), - Array.Empty() + [] ), - new RelationalTypeMappingSourceDependencies(Array.Empty()), + new RelationalTypeMappingSourceDependencies([]), new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), options); diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs index 55dd61c1c..34bf3dd30 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs @@ -27,26 +27,16 @@ public static IReadOnlyList CreateArrayEntities() new() { Id = 1, - IntArray = new[] { 3, 4 }, - IntList = new List { 3, 4 }, - NullableIntArray = new int?[] { 3, 4, null }, - NullableIntList = new List - { - 3, - 4, - null - }, - Bytea = new byte[] { 3, 4 }, - ByteArray = new byte[] { 3, 4 }, - StringArray = new[] { "3", "4" }, - NullableStringArray = new[] { "3", "4", null }, - StringList = new List { "3", "4" }, - NullableStringList = new List - { - "3", - "4", - null - }, + IntArray = [3, 4], + IntList = [3, 4], + NullableIntArray = [3, 4, null], + NullableIntList = [3, 4, null], + Bytea = [3, 4], + ByteArray = [3, 4], + StringArray = ["3", "4"], + NullableStringArray = ["3", "4", null], + StringList = ["3", "4"], + NullableStringList = ["3", "4", null], NullableText = "foo", NonNullableText = "foo", Varchar10 = "foo", @@ -55,48 +45,24 @@ public static IReadOnlyList CreateArrayEntities() EnumConvertedToString = SomeEnum.One, NullableEnumConvertedToString = SomeEnum.One, NullableEnumConvertedToStringWithNonNullableLambda = SomeEnum.One, - ValueConvertedArray = new[] { SomeEnum.Eight, SomeEnum.Nine }, - ValueConvertedList = new List { SomeEnum.Eight, SomeEnum.Nine }, + ValueConvertedArray = [SomeEnum.Eight, SomeEnum.Nine], + ValueConvertedList = [SomeEnum.Eight, SomeEnum.Nine], IList = new[] { 8, 9 }, Byte = 10 }, new() { Id = 2, - IntArray = new[] { 5, 6, 7, 8 }, - IntList = new List - { - 5, - 6, - 7, - 8 - }, - NullableIntArray = new int?[] { 5, 6, 7, 8 }, - NullableIntList = new List - { - 5, - 6, - 7, - 8 - }, - Bytea = new byte[] { 5, 6, 7, 8 }, - ByteArray = new byte[] { 5, 6, 7, 8 }, - StringArray = new[] { "5", "6", "7", "8" }, - NullableStringArray = new[] { "5", "6", "7", "8" }, - StringList = new List - { - "5", - "6", - "7", - "8" - }, - NullableStringList = new List - { - "5", - "6", - "7", - "8" - }, + IntArray = [5, 6, 7, 8], + IntList = [5, 6, 7, 8], + NullableIntArray = [5, 6, 7, 8], + NullableIntList = [5, 6, 7, 8], + Bytea = [5, 6, 7, 8], + ByteArray = [5, 6, 7, 8], + StringArray = ["5", "6", "7", "8"], + NullableStringArray = ["5", "6", "7", "8"], + StringList = ["5", "6", "7", "8"], + NullableStringList = ["5", "6", "7", "8"], NullableText = "bar", NonNullableText = "bar", Varchar10 = "bar", @@ -105,8 +71,8 @@ public static IReadOnlyList CreateArrayEntities() EnumConvertedToString = SomeEnum.Two, NullableEnumConvertedToString = SomeEnum.Two, NullableEnumConvertedToStringWithNonNullableLambda = SomeEnum.Two, - ValueConvertedArray = new[] { SomeEnum.Nine, SomeEnum.Ten }, - ValueConvertedList = new List { SomeEnum.Nine, SomeEnum.Ten }, + ValueConvertedArray = [SomeEnum.Nine, SomeEnum.Ten], + ValueConvertedList = [SomeEnum.Nine, SomeEnum.Ten], IList = new[] { 9, 10 }, Byte = 20 } diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/TestNpgsqlRetryingExecutionStrategy.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/TestNpgsqlRetryingExecutionStrategy.cs index 0c3c982f8..c443ccdc8 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/TestNpgsqlRetryingExecutionStrategy.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/TestNpgsqlRetryingExecutionStrategy.cs @@ -4,7 +4,7 @@ public class TestNpgsqlRetryingExecutionStrategy : NpgsqlRetryingExecutionStrate { private const bool ErrorNumberDebugMode = false; - private static readonly string[] AdditionalSqlStates = { "XX000" }; + private static readonly string[] AdditionalSqlStates = ["XX000"]; public TestNpgsqlRetryingExecutionStrategy() : base( diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs index 35e7dd1a4..6dcc65fd1 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs @@ -11,7 +11,7 @@ public virtual IRelationalCommandBuilder Create() private class TestRelationalCommandBuilder(RelationalCommandBuilderDependencies dependencies) : IRelationalCommandBuilder { - private readonly List _parameters = new(); + private readonly List _parameters = []; public IndentedStringBuilder Instance { get; } = new(); diff --git a/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs index 5cebb5470..6b7a7869f 100644 --- a/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs @@ -158,7 +158,7 @@ public async Task Can_insert_and_read_back_with_value_converted_array() { await using var ctx = CreateContext(); - var entity = new ValueConvertedArrayEntity { Values = new IntWrapper[] { new(8), new(9) } }; + var entity = new ValueConvertedArrayEntity { Values = [new(8), new(9)] }; ctx.Add(entity); await ctx.SaveChangesAsync(); diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/LegacyNpgsqlNodaTimeTypeMappingTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/LegacyNpgsqlNodaTimeTypeMappingTest.cs index 080a54bf3..e6e49a8be 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/LegacyNpgsqlNodaTimeTypeMappingTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/LegacyNpgsqlNodaTimeTypeMappingTest.cs @@ -63,7 +63,7 @@ public void GenerateSqlLiteral_returns_instant_range_in_legacy_mode() new TypeMappingSourceDependencies( new ValueConverterSelector(new ValueConverterSelectorDependencies()), new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), - Array.Empty()), + []), new RelationalTypeMappingSourceDependencies( new IRelationalTypeMappingSourcePlugin[] { diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs index dabd1dba4..f59bcf5ef 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs @@ -1208,7 +1208,7 @@ public async Task Interval_RangeAgg(bool async) : query.Single(); var start = Instant.FromUtc(2018, 4, 20, 10, 31, 33).Plus(Duration.FromMilliseconds(666)); - Assert.Equal(new Interval[] { new(start, start + Duration.FromDays(5)) }, union); + Assert.Equal([new(start, start + Duration.FromDays(5))], union); AssertSql( """ @@ -1416,7 +1416,7 @@ public async Task DateInterval_RangeAgg(bool async) ? await query.SingleAsync() : query.Single(); - Assert.Equal(new DateInterval[] { new(new LocalDate(2018, 4, 20), new LocalDate(2018, 4, 24)) }, union); + Assert.Equal([new(new LocalDate(2018, 4, 20), new LocalDate(2018, 4, 24))], union); AssertSql( """ diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NpgsqlNodaTimeTypeMappingTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NpgsqlNodaTimeTypeMappingTest.cs index ddf7e078d..f2cb829af 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NpgsqlNodaTimeTypeMappingTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NpgsqlNodaTimeTypeMappingTest.cs @@ -838,7 +838,7 @@ public void GenerateCodeLiteral_returns_DateTimezone_literal() new TypeMappingSourceDependencies( new ValueConverterSelector(new ValueConverterSelectorDependencies()), new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), - Array.Empty()), + []), new RelationalTypeMappingSourceDependencies( new IRelationalTypeMappingSourcePlugin[] { diff --git a/test/EFCore.PG.Tests/Design/Internal/NpgsqlAnnotationCodeGeneratorTest.cs b/test/EFCore.PG.Tests/Design/Internal/NpgsqlAnnotationCodeGeneratorTest.cs index f0323eac3..ec9cff986 100644 --- a/test/EFCore.PG.Tests/Design/Internal/NpgsqlAnnotationCodeGeneratorTest.cs +++ b/test/EFCore.PG.Tests/Design/Internal/NpgsqlAnnotationCodeGeneratorTest.cs @@ -404,9 +404,9 @@ private NpgsqlAnnotationCodeGenerator CreateGenerator() new TypeMappingSourceDependencies( new ValueConverterSelector(new ValueConverterSelectorDependencies()), new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), - Array.Empty() + [] ), - new RelationalTypeMappingSourceDependencies(Array.Empty()), + new RelationalTypeMappingSourceDependencies([]), new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new NpgsqlSingletonOptions()))); } diff --git a/test/EFCore.PG.Tests/Metadata/NpgsqlBuilderExtensionsTest.cs b/test/EFCore.PG.Tests/Metadata/NpgsqlBuilderExtensionsTest.cs index f16f06749..b4efac2bd 100644 --- a/test/EFCore.PG.Tests/Metadata/NpgsqlBuilderExtensionsTest.cs +++ b/test/EFCore.PG.Tests/Metadata/NpgsqlBuilderExtensionsTest.cs @@ -11,7 +11,7 @@ public void CockroachDbInterleaveInParent() modelBuilder.Entity() .ToTable("customers", "my_schema") - .UseCockroachDbInterleaveInParent(typeof(Customer), new List { "col_a", "col_b" }); + .UseCockroachDbInterleaveInParent(typeof(Customer), ["col_a", "col_b"]); var entityType = modelBuilder.Model.FindEntityType(typeof(Customer)); var interleaveInParent = entityType.GetCockroachDbInterleaveInParent(); diff --git a/test/EFCore.PG.Tests/NpgsqlNetTopologySuiteApiConsistencyTest.cs b/test/EFCore.PG.Tests/NpgsqlNetTopologySuiteApiConsistencyTest.cs index 5f52f567d..029c02ecb 100644 --- a/test/EFCore.PG.Tests/NpgsqlNetTopologySuiteApiConsistencyTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlNetTopologySuiteApiConsistencyTest.cs @@ -12,9 +12,7 @@ protected override Assembly TargetAssembly public class NpgsqlNetTopologySuiteApiConsistencyFixture : ApiConsistencyFixtureBase { - public override HashSet FluentApiTypes { get; } = new() - { - typeof(NpgsqlNetTopologySuiteDbContextOptionsBuilderExtensions), typeof(NpgsqlNetTopologySuiteServiceCollectionExtensions) - }; + public override HashSet FluentApiTypes { get; } = + [typeof(NpgsqlNetTopologySuiteDbContextOptionsBuilderExtensions), typeof(NpgsqlNetTopologySuiteServiceCollectionExtensions)]; } } diff --git a/test/EFCore.PG.Tests/NpgsqlNodaTimeApiConsistencyTest.cs b/test/EFCore.PG.Tests/NpgsqlNodaTimeApiConsistencyTest.cs index ff79bc1ce..d1e6798a4 100644 --- a/test/EFCore.PG.Tests/NpgsqlNodaTimeApiConsistencyTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlNodaTimeApiConsistencyTest.cs @@ -11,9 +11,7 @@ protected override Assembly TargetAssembly public class NpgsqlNodaTimeApiConsistencyFixture : ApiConsistencyFixtureBase { - public override HashSet FluentApiTypes { get; } = new() - { - typeof(NpgsqlNodaTimeDbContextOptionsBuilderExtensions), typeof(NpgsqlNodaTimeServiceCollectionExtensions) - }; + public override HashSet FluentApiTypes { get; } = + [typeof(NpgsqlNodaTimeDbContextOptionsBuilderExtensions), typeof(NpgsqlNodaTimeServiceCollectionExtensions)]; } } diff --git a/test/EFCore.PG.Tests/Scaffolding/NpgsqlCodeGeneratorTest.cs b/test/EFCore.PG.Tests/Scaffolding/NpgsqlCodeGeneratorTest.cs index 3f68c3341..5b2d1348b 100644 --- a/test/EFCore.PG.Tests/Scaffolding/NpgsqlCodeGeneratorTest.cs +++ b/test/EFCore.PG.Tests/Scaffolding/NpgsqlCodeGeneratorTest.cs @@ -95,7 +95,7 @@ public virtual void Use_provider_method_is_generated_correctly_with_NodaTime() } private static readonly MethodInfo _setProviderOptionMethodInfo - = typeof(NpgsqlCodeGeneratorTest).GetRuntimeMethod(nameof(SetProviderOption), new[] { typeof(DbContextOptionsBuilder) }); + = typeof(NpgsqlCodeGeneratorTest).GetRuntimeMethod(nameof(SetProviderOption), [typeof(DbContextOptionsBuilder)]); public static NpgsqlDbContextOptionsBuilder SetProviderOption(DbContextOptionsBuilder optionsBuilder) => throw new NotSupportedException(); diff --git a/test/EFCore.PG.Tests/Storage/LegacyNpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/LegacyNpgsqlTypeMappingTest.cs index 64400b9c4..ac536bd4d 100644 --- a/test/EFCore.PG.Tests/Storage/LegacyNpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/LegacyNpgsqlTypeMappingTest.cs @@ -49,9 +49,9 @@ public void GenerateSqlLiteral_returns_timestamptz_datetime_literal() new TypeMappingSourceDependencies( new ValueConverterSelector(new ValueConverterSelectorDependencies()), new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), - Array.Empty() + [] ), - new RelationalTypeMappingSourceDependencies(Array.Empty()), + new RelationalTypeMappingSourceDependencies([]), new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new NpgsqlSingletonOptions()); diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlArrayValueConverterTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlArrayValueConverterTest.cs index ebfe3ba45..0f16dd6b2 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlArrayValueConverterTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlArrayValueConverterTest.cs @@ -12,12 +12,12 @@ public void Can_convert_enum_arrays_to_number_arrays() { var converter = EnumArrayToNumberArray.ConvertToProviderExpression.Compile(); - Assert.Equal(new[] { 7 }, converter(new[] { Beatles.John })); - Assert.Equal(new[] { 4 }, converter(new[] { Beatles.Paul })); - Assert.Equal(new[] { 1 }, converter(new[] { Beatles.George })); - Assert.Equal(new[] { -1 }, converter(new[] { Beatles.Ringo })); - Assert.Equal(new[] { 77 }, converter(new[] { (Beatles)77 })); - Assert.Equal(new[] { 0 }, converter(new[] { default(Beatles) })); + Assert.Equal([7], converter([Beatles.John])); + Assert.Equal([4], converter([Beatles.Paul])); + Assert.Equal([1], converter([Beatles.George])); + Assert.Equal([-1], converter([Beatles.Ringo])); + Assert.Equal([77], converter([(Beatles)77])); + Assert.Equal([0], converter([default(Beatles)])); Assert.Null(converter(null)); } @@ -40,12 +40,12 @@ public void Can_convert_number_arrays_to_enum_arrays() { var converter = EnumArrayToNumberArray.ConvertFromProviderExpression.Compile(); - Assert.Equal(new[] { Beatles.John }, converter(new[] { 7 })); - Assert.Equal(new[] { Beatles.Paul }, converter(new[] { 4 })); - Assert.Equal(new[] { Beatles.George }, converter(new[] { 1 })); - Assert.Equal(new[] { Beatles.Ringo }, converter(new[] { -1 })); - Assert.Equal(new[] { (Beatles)77 }, converter(new[] { 77 })); - Assert.Equal(new[] { default(Beatles) }, converter(new[] { 0 })); + Assert.Equal([Beatles.John], converter([7])); + Assert.Equal([Beatles.Paul], converter([4])); + Assert.Equal([Beatles.George], converter([1])); + Assert.Equal([Beatles.Ringo], converter([-1])); + Assert.Equal([(Beatles)77], converter([77])); + Assert.Equal([default(Beatles)], converter([0])); Assert.Null(converter(null)); } diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs index a1dca3a49..c16e5d2d2 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -308,7 +308,7 @@ private NpgsqlTypeMappingSource CreateTypeMappingSource(Version postgresVersion new TypeMappingSourceDependencies( new ValueConverterSelector(new ValueConverterSelectorDependencies()), new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), - Array.Empty()), + []), new RelationalTypeMappingSourceDependencies( new IRelationalTypeMappingSourcePlugin[] { diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs index f2e558f1e..77f45d8bf 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs @@ -939,8 +939,8 @@ public void ValueComparer_JsonElement() Name = "Joe", Age = 25, IsVip = false, - Orders = new[] - { + Orders = + [ new Order { Price = 99.5m, @@ -953,7 +953,7 @@ public void ValueComparer_JsonElement() ShippingAddress = "Some address 2", ShippingDate = new DateTime(2019, 10, 10) } - } + ] }; public class Customer @@ -979,9 +979,9 @@ public class Order new TypeMappingSourceDependencies( new ValueConverterSelector(new ValueConverterSelectorDependencies()), new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), - Array.Empty() + [] ), - new RelationalTypeMappingSourceDependencies(Array.Empty()), + new RelationalTypeMappingSourceDependencies([]), new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new NpgsqlSingletonOptions() ); From 35f0fd5a75771382b97a3ee49825fdfdae932907 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 25 Jan 2024 18:04:07 +0200 Subject: [PATCH 022/107] Always add parentheses around PgUnknownBinaryExpression (#3073) Fixes #3072 --- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 5 +- .../Query/TrigramsQueryNpgsqlTest.cs | 48 ++++++++++++------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index b71d138c3..7569ecd1e 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -1401,7 +1401,7 @@ protected override bool RequiresParentheses(SqlExpression outerExpression, SqlEx // If both operators have the same precedence, add parentheses unless they're the same operator, and // that operator is associative (e.g. a + b + c) - 0 => outerExpression is not PgBinaryExpression outerBinary + _ => outerExpression is not PgBinaryExpression outerBinary || outerBinary.OperatorType != innerBinary.OperatorType || !isOuterAssociative // Arithmetic operators on floating points aren't associative, because of rounding errors. @@ -1416,6 +1416,9 @@ protected override bool RequiresParentheses(SqlExpression outerExpression, SqlEx return true; } + case PgUnknownBinaryExpression: + return true; + default: return base.RequiresParentheses(outerExpression, innerExpression); } diff --git a/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs index e9b5e1d85..d42779741 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs @@ -27,7 +27,7 @@ public TrigramsQueryNpgsqlTest(TrigramsQueryNpgsqlFixture fixture, ITestOutputHe public void TrigramsShow() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsShow(x.Text)) .ToArray(); @@ -38,7 +38,7 @@ public void TrigramsShow() public void TrigramsSimilarity() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsSimilarity(x.Text, "target")) .ToArray(); @@ -49,7 +49,7 @@ public void TrigramsSimilarity() public void TrigramsWordSimilarity() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsWordSimilarity(x.Text, "target")) .ToArray(); @@ -61,7 +61,7 @@ public void TrigramsWordSimilarity() public void TrigramsStrictWordSimilarity() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsStrictWordSimilarity(x.Text, "target")) .ToArray(); @@ -72,7 +72,7 @@ public void TrigramsStrictWordSimilarity() public void TrigramsAreSimilar() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsAreSimilar(x.Text, "target")) .ToArray(); @@ -83,7 +83,7 @@ public void TrigramsAreSimilar() public void TrigramsAreWordSimilar() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsAreWordSimilar(x.Text, "target")) .ToArray(); @@ -94,7 +94,7 @@ public void TrigramsAreWordSimilar() public void TrigramsAreNotWordSimilar() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsAreNotWordSimilar(x.Text, "target")) .ToArray(); @@ -106,7 +106,7 @@ public void TrigramsAreNotWordSimilar() public void TrigramsAreStrictWordSimilar() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsAreStrictWordSimilar(x.Text, "target")) .ToArray(); @@ -118,7 +118,7 @@ public void TrigramsAreStrictWordSimilar() public void TrigramsAreNotStrictWordSimilar() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsAreNotStrictWordSimilar(x.Text, "target")) .ToArray(); @@ -129,7 +129,7 @@ public void TrigramsAreNotStrictWordSimilar() public void TrigramsSimilarityDistance() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsSimilarityDistance(x.Text, "target")) .ToArray(); @@ -140,7 +140,7 @@ public void TrigramsSimilarityDistance() public void TrigramsWordSimilarityDistance() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsWordSimilarityDistance(x.Text, "target")) .ToArray(); @@ -151,7 +151,7 @@ public void TrigramsWordSimilarityDistance() public void TrigramsWordSimilarityDistanceInverted() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsWordSimilarityDistanceInverted(x.Text, "target")) .ToArray(); @@ -163,7 +163,7 @@ public void TrigramsWordSimilarityDistanceInverted() public void TrigramsStrictWordSimilarityDistance() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsStrictWordSimilarityDistance(x.Text, "target")) .ToArray(); @@ -175,7 +175,7 @@ public void TrigramsStrictWordSimilarityDistance() public void TrigramsStrictWordSimilarityDistanceInverted() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Select(x => EF.Functions.TrigramsStrictWordSimilarityDistanceInverted(x.Text, "target")) .ToArray(); @@ -183,10 +183,10 @@ public void TrigramsStrictWordSimilarityDistanceInverted() } [Fact] // #1659 - public void Operator_precedence() + public void Concatenation_operator_precedence() { using var context = CreateContext(); - var _ = context.TrigramsTestEntities + _ = context.TrigramsTestEntities .Where(e => EF.Functions.TrigramsAreSimilar(e.Text + " " + e.Text, "query")) .ToArray(); @@ -198,6 +198,22 @@ public void Operator_precedence() """); } + [Fact] // #3072 + public void PgUnknownBinary_operator_precedence() + { + using var context = CreateContext(); + _ = context.TrigramsTestEntities + .Where(e => 1 - EF.Functions.TrigramsSimilarityDistance(e.Text, "query") > 8) + .ToArray(); + + AssertSql( + """ +SELECT t."Id", t."Text" +FROM "TrigramsTestEntities" AS t +WHERE 1 - (t."Text" <-> 'query') > 8 +"""); + } + #endregion #region Fixtures From 2403c06c8b6b6f707ca63ff0f933cfb3a66dd1b9 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 11 Feb 2024 21:13:48 +0200 Subject: [PATCH 023/107] Sync to EF 9.0.0-preview.1.24081.2 (#3089) --- Directory.Packages.props | 10 +- EFCore.PG.sln.DotSettings | 4 + .../PgTableValuedFunctionExpression.cs | 27 +- .../Internal/PgUnnestExpression.cs | 8 + ...NpgsqlDeleteConvertingExpressionVisitor.cs | 4 +- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 9 +- .../NpgsqlQueryTranslationPostprocessor.cs | 2 +- ...sqlQueryTranslationPostprocessorFactory.cs | 5 +- ...yableMethodTranslatingExpressionVisitor.cs | 251 +++---- ...thodTranslatingExpressionVisitorFactory.cs | 5 +- .../Internal/NpgsqlUnnestPostprocessor.cs | 4 +- .../NpgsqlCharacterStringTypeMapping.cs | 16 +- .../BadDataJsonDeserializationNpgsqlTest.cs | 9 + .../BuiltInDataTypesNpgsqlTest.cs | 2 +- .../ComplexTypeBulkUpdatesNpgsqlTest.cs | 122 ++++ .../NonSharedModelBulkUpdatesNpgsqlTest.cs | 49 +- .../NorthwindBulkUpdatesNpgsqlTest.cs | 450 ++++++------- ...FiltersInheritanceBulkUpdatesNpgsqlTest.cs | 28 +- .../TPCInheritanceBulkUpdatesNpgsqlTest.cs | 28 +- ...FiltersInheritanceBulkUpdatesNpgsqlTest.cs | 47 +- .../TPTInheritanceBulkUpdatesNpgsqlTest.cs | 42 +- .../JsonTypesNpgsqlTest.cs | 14 +- .../MaterializationInterceptionNpgsqlTest.cs | 32 +- .../MigrationsInfrastructureNpgsqlTest.cs | 20 + .../Migrations/MigrationsNpgsqlTest.cs | 25 +- .../NpgsqlModelBuilderGenericTest.cs | 97 +++ .../NpgsqlModelBuilderTestBase.cs | 40 ++ .../NpgsqlComplianceTest.cs | 1 + .../NpgsqlDatabaseCreatorTest.cs | 4 +- .../AdHocAdvancedMappingsQueryNpgsqlTest.cs | 19 + ...sqlTest.cs => AdHocJsonQueryNpgsqlTest.cs} | 314 ++------- ...t.cs => AdHocManyToManyQueryNpgsqlTest.cs} | 2 +- .../AdHocMiscellaneousQueryNpgsqlTest.cs | 38 ++ .../Query/AdHocNavigationsQueryNpgsqlTest.cs | 17 + .../Query/AdHocQueryFiltersQueryNpgsqlTest.cs | 9 + .../AdHocQuerySplittingQueryNpgsqlTest.cs | 40 ++ .../Query/ArrayQueryTest.cs | 2 +- .../ComplexNavigationsQueryNpgsqlTest.cs | 6 +- .../Query/ComplexTypeQueryNpgsqlTest.cs | 88 +-- .../Query/Ef6GroupByNpgsqlTest.cs | 40 +- .../Query/EntitySplittingQueryNpgsqlTest.cs | 70 +- .../Query/FunkyDataQueryNpgsqlTest.cs | 24 - .../Query/JsonQueryNpgsqlTest.cs | 308 ++++----- .../Query/NorthwindGroupByQueryNpgsqlTest.cs | 612 +++++++++--------- .../NorthwindMiscellaneousQueryNpgsqlTest.cs | 8 +- .../NorthwindSetOperationsQueryNpgsqlTest.cs | 20 +- .../Query/NorthwindSqlQueryNpgsqlTest.cs | 8 +- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 341 ++++++++-- .../Query/RangeQueryNpgsqlTest.cs | 12 +- .../Query/SimpleQueryNpgsqlTest.cs | 21 - .../Scaffolding/CompiledModelNpgsqlTest.cs | 59 ++ .../NpgsqlDatabaseModelFactoryTest.cs | 13 +- .../TestUtilities/NpgsqlTestStoreFactory.cs | 1 + .../Update/JsonUpdateNpgsqlTest.cs | 74 +-- .../NodaTimeQueryNpgsqlTest.cs | 24 +- .../Storage/NpgsqlArrayValueConverterTest.cs | 12 +- 56 files changed, 2044 insertions(+), 1493 deletions(-) create mode 100644 test/EFCore.PG.FunctionalTests/BadDataJsonDeserializationNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderGenericTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderTestBase.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs rename test/EFCore.PG.FunctionalTests/Query/{JsonQueryAdHocNpgsqlTest.cs => AdHocJsonQueryNpgsqlTest.cs} (53%) rename test/EFCore.PG.FunctionalTests/Query/{ManyToManyHeterogeneousQueryNpgsqlTest.cs => AdHocManyToManyQueryNpgsqlTest.cs} (68%) create mode 100644 test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/AdHocQueryFiltersQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/AdHocQuerySplittingQueryNpgsqlTest.cs delete mode 100644 test/EFCore.PG.FunctionalTests/Query/SimpleQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Scaffolding/CompiledModelNpgsqlTest.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 19b7d4b62..c6c22d7a8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,8 +1,8 @@ - 8.0.0 - 8.0.0 - 8.0.0 + 9.0.0-preview.1.24081.2 + 9.0.0-preview.1.24080.9 + 8.0.2 @@ -23,8 +23,8 @@ - - + + diff --git a/EFCore.PG.sln.DotSettings b/EFCore.PG.sln.DotSettings index 06af66077..0ae73aa7c 100644 --- a/EFCore.PG.sln.DotSettings +++ b/EFCore.PG.sln.DotSettings @@ -275,6 +275,7 @@ True True True +<<<<<<< HEAD True True True @@ -391,3 +392,6 @@ True True True + True + True + diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs index 19c153129..e4e02bc43 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs @@ -21,8 +21,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// -public class PgTableValuedFunctionExpression : TableValuedFunctionExpression, - IEquatable, IClonableTableExpressionBase +public class PgTableValuedFunctionExpression : TableValuedFunctionExpression, IEquatable { /// /// The name of the column to be projected out from the unnest call. @@ -77,15 +76,25 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override PgTableValuedFunctionExpression Update(IReadOnlyList arguments) - => !arguments.SequenceEqual(Arguments) - ? new PgTableValuedFunctionExpression(Alias, Name, arguments, ColumnInfos, WithOrdinality) - : this; + => arguments.SequenceEqual(Arguments, ReferenceEqualityComparer.Instance) + ? this + : new PgTableValuedFunctionExpression(Alias, Name, arguments, ColumnInfos, WithOrdinality); + + /// + public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloningExpressionVisitor) + { + var arguments = new SqlExpression[Arguments.Count]; + for (var i = 0; i < arguments.Length; i++) + { + arguments[i] = (SqlExpression)cloningExpressionVisitor.Visit(Arguments[i]); + } + + return new PgTableValuedFunctionExpression(Alias, Name, arguments, ColumnInfos, WithOrdinality); + } - // TODO: This is a hack for https://github.com/npgsql/efcore.pg/issues/3023; we notably don't visit the arguments, which we should - // (but can't, since the Clone() API doesn't accept the cloning visitor). /// - public TableExpressionBase Clone() - => new PgTableValuedFunctionExpression(Alias, Name, Arguments, ColumnInfos, WithOrdinality); + public override PgTableValuedFunctionExpression WithAlias(string newAlias) + => new(newAlias, Name, Arguments, ColumnInfos, WithOrdinality); /// protected override void Print(ExpressionPrinter expressionPrinter) diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs index e27f49d8e..941014fa0 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs @@ -85,4 +85,12 @@ public virtual PgUnnestExpression Update(SqlExpression array) => array == Array ? this : new PgUnnestExpression(Alias, array, ColumnName, WithOrdinality); + + /// + public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloningExpressionVisitor) + => new PgUnnestExpression(alias!, (SqlExpression)cloningExpressionVisitor.Visit(Array), ColumnName, WithOrdinality); + + /// + public override PgTableValuedFunctionExpression WithAlias(string newAlias) + => new(newAlias, Name, Arguments, ColumnInfos, WithOrdinality); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs index cf7a4f7b5..c2712d10b 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs @@ -56,7 +56,7 @@ protected virtual Expression VisitDelete(DeleteExpression deleteExpression) switch (tableBase) { case TableExpression tableExpression: - if (tableExpression != deleteExpression.Table) + if (tableExpression.Alias != deleteExpression.Table.Alias) { fromItems.Add(tableExpression); } @@ -64,7 +64,7 @@ protected virtual Expression VisitDelete(DeleteExpression deleteExpression) break; case InnerJoinExpression { Table: { } tableExpression } innerJoinExpression: - if (tableExpression != deleteExpression.Table) + if (tableExpression.Alias != deleteExpression.Table.Alias) { fromItems.Add(tableExpression); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 7569ecd1e..3afb677bf 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -760,6 +760,11 @@ protected override Expression VisitValues(ValuesExpression valuesExpression) /// protected override void GenerateValues(ValuesExpression valuesExpression) { + if (valuesExpression.RowValues.Count == 0) + { + throw new InvalidOperationException(RelationalStrings.EmptyCollectionNotSupportedAsInlineQueryRoot); + } + // PostgreSQL supports providing the names of columns projected out of VALUES: (VALUES (1, 3), (2, 4)) AS x(a, b). // But since other databases sometimes don't, the default relational implementation is complex, involving a SELECT for the first row // and a UNION All on the rest. Override to do the nice simple thing. @@ -1540,8 +1545,8 @@ public bool ContainsReferenceToMainTable(SqlExpression sqlExpression) return expression; } - if (expression is ColumnExpression columnExpression - && columnExpression.Table == mainTable) + if (expression is ColumnExpression { TableAlias: var tableAlias } + && tableAlias == mainTable.Alias) { _containsReference = true; diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs index 7b9a9828d..de98b5f84 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs @@ -17,7 +17,7 @@ public class NpgsqlQueryTranslationPostprocessor : RelationalQueryTranslationPos public NpgsqlQueryTranslationPostprocessor( QueryTranslationPostprocessorDependencies dependencies, RelationalQueryTranslationPostprocessorDependencies relationalDependencies, - QueryCompilationContext queryCompilationContext) + RelationalQueryCompilationContext queryCompilationContext) : base(dependencies, relationalDependencies, queryCompilationContext) { } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessorFactory.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessorFactory.cs index 0d28d80a0..e31cc18be 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessorFactory.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessorFactory.cs @@ -34,5 +34,8 @@ public NpgsqlQueryTranslationPostprocessorFactory( /// public virtual QueryTranslationPostprocessor Create(QueryCompilationContext queryCompilationContext) - => new NpgsqlQueryTranslationPostprocessor(Dependencies, RelationalDependencies, queryCompilationContext); + => new NpgsqlQueryTranslationPostprocessor( + Dependencies, + RelationalDependencies, + (RelationalQueryCompilationContext)queryCompilationContext); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index 208e033d8..d258ebb8c 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -15,8 +15,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; /// public class NpgsqlQueryableMethodTranslatingExpressionVisitor : RelationalQueryableMethodTranslatingExpressionVisitor { + private readonly RelationalQueryCompilationContext _queryCompilationContext; private readonly NpgsqlTypeMappingSource _typeMappingSource; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; + private RelationalTypeMapping? _ordinalityTypeMapping; + #region MethodInfos @@ -43,9 +46,10 @@ private static readonly MethodInfo ILike2MethodInfo public NpgsqlQueryableMethodTranslatingExpressionVisitor( QueryableMethodTranslatingExpressionVisitorDependencies dependencies, RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, - QueryCompilationContext queryCompilationContext) + RelationalQueryCompilationContext queryCompilationContext) : base(dependencies, relationalDependencies, queryCompilationContext) { + _queryCompilationContext = queryCompilationContext; _typeMappingSource = (NpgsqlTypeMappingSource)relationalDependencies.TypeMappingSource; _sqlExpressionFactory = (NpgsqlSqlExpressionFactory)relationalDependencies.SqlExpressionFactory; } @@ -59,6 +63,7 @@ public NpgsqlQueryableMethodTranslatingExpressionVisitor( protected NpgsqlQueryableMethodTranslatingExpressionVisitor(NpgsqlQueryableMethodTranslatingExpressionVisitor parentVisitor) : base(parentVisitor) { + _queryCompilationContext = parentVisitor._queryCompilationContext; _typeMappingSource = parentVisitor._typeMappingSource; _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; } @@ -99,7 +104,7 @@ protected override ShapedQueryExpression TranslatePrimitiveCollection( // a special case for geometry collections, where we use SelectExpression selectExpression; -#pragma warning disable EF1001 +#pragma warning disable EF1001 // SelectExpression constructors are currently internal // TODO: Parameters have no type mapping. We can check whether the expression type is one of the NTS geometry collection types, // though in a perfect world we'd actually infer this. In other words, when the type mapping of the element is inferred further on, // we'd replace the unnest expression with ST_Dump. We could even have a special expression type which means "indeterminate, must be @@ -108,8 +113,10 @@ protected override ShapedQueryExpression TranslatePrimitiveCollection( { // TODO: For geometry collection support (not yet supported), see #2850. selectExpression = new SelectExpression( - new TableValuedFunctionExpression(tableAlias, "ST_Dump", new[] { sqlExpression }), - "geom", elementClrType, elementTypeMapping, isElementNullable); + [new TableValuedFunctionExpression(tableAlias, "ST_Dump", new[] { sqlExpression })], + new ColumnExpression("geom", tableAlias, elementClrType.UnwrapNullableType(), elementTypeMapping, isElementNullable), + identifier: [], // TODO + _queryCompilationContext.SqlAliasManager); } else { @@ -123,14 +130,15 @@ protected override ShapedQueryExpression TranslatePrimitiveCollection( // to add ordering like in most other providers (https://www.postgresql.org/docs/current/functions-array.html) // We also don't need to apply any casts or typing, since PG arrays are fully typed (unlike e.g. a JSON string). selectExpression = new SelectExpression( - new PgUnnestExpression(tableAlias, sqlExpression, "value"), - columnName: "value", - columnType: elementClrType, - columnTypeMapping: elementTypeMapping, - isColumnNullable: isElementNullable, - identifierColumnName: "ordinality", - identifierColumnType: typeof(int), - identifierColumnTypeMapping: _typeMappingSource.FindMapping(typeof(int))); + [new PgUnnestExpression(tableAlias, sqlExpression, "value")], + new ColumnExpression( + "value", + tableAlias, + elementClrType.UnwrapNullableType(), + elementTypeMapping, + isElementNullable), + identifier: [GenerateOrdinalityIdentifier(tableAlias)], + _queryCompilationContext.SqlAliasManager); } #pragma warning restore EF1001 @@ -160,7 +168,9 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr // Calculate the table alias for the jsonb_to_recordset function based on the last named path segment // (or the JSON column name if there are none) var lastNamedPathSegment = jsonQueryExpression.Path.LastOrDefault(ps => ps.PropertyName is not null); - var tableAlias = char.ToLowerInvariant((lastNamedPathSegment.PropertyName ?? jsonQueryExpression.JsonColumn.Name)[0]).ToString(); + var tableAlias = + _queryCompilationContext.SqlAliasManager.GenerateTableAlias( + lastNamedPathSegment.PropertyName ?? jsonQueryExpression.JsonColumn.Name); // TODO: This relies on nested JSON columns flowing across the type mapping of the top-most containing JSON column, check this. var functionName = jsonQueryExpression.JsonColumn switch @@ -216,13 +226,13 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr var jsonToRecordSetExpression = new PgTableValuedFunctionExpression( tableAlias, functionName, new[] { jsonScalarExpression }, columnInfos, withOrdinality: true); -#pragma warning disable EF1001 // Internal EF Core API usage. - var selectExpression = new SelectExpression( +#pragma warning disable EF1001 // SelectExpression constructors are currently internal + var selectExpression = CreateSelect( jsonQueryExpression, jsonToRecordSetExpression, - identifierColumnName: "ordinality", - identifierColumnType: typeof(int), - identifierColumnTypeMapping: _typeMappingSource.FindMapping(typeof(int))!); + "ordinality", + typeof(int), + _typeMappingSource.FindMapping(typeof(int))!); #pragma warning restore EF1001 // Internal EF Core API usage. return new ShapedQueryExpression( @@ -253,7 +263,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT /// protected override Expression ApplyInferredTypeMappings( Expression expression, - IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> inferredTypeMappings) + IReadOnlyDictionary<(string, string), RelationalTypeMapping?> inferredTypeMappings) => new NpgsqlInferredTypeMappingApplier( RelationalDependencies.Model, _typeMappingSource, _sqlExpressionFactory, inferredTypeMappings).Visit(expression); @@ -267,7 +277,7 @@ protected override Expression ApplyInferredTypeMappings( { if (source.QueryExpression is SelectExpression { - Tables: [(PgUnnestExpression or ValuesExpression { ColumnNames: ["_ord", "Value"] }) and var sourceTable], + Tables: [var sourceTable], Predicate: null, GroupBy: [], Having: null, @@ -275,6 +285,7 @@ protected override Expression ApplyInferredTypeMappings( Limit: null, Offset: null } + && TryGetArray(sourceTable, out var array) && TranslateLambdaExpression(source, predicate) is { } translatedPredicate) { switch (translatedPredicate) @@ -287,11 +298,11 @@ protected override Expression ApplyInferredTypeMappings( Pattern: ColumnExpression pattern, EscapeChar: SqlConstantExpression { Value: "" } } - when pattern.Table == sourceTable: + when pattern.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( source, - _sqlExpressionFactory.All(match, GetArray(sourceTable), PgAllOperatorType.Like)); + _sqlExpressionFactory.All(match, array, PgAllOperatorType.Like)); } // Pattern match for: new[] { "a", "b", "c" }.All(p => EF.Functions.Like(e.SomeText, p)), @@ -302,11 +313,11 @@ protected override Expression ApplyInferredTypeMappings( Pattern: ColumnExpression pattern, EscapeChar: SqlConstantExpression { Value: "" } } - when pattern.Table == sourceTable: + when pattern.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( source, - _sqlExpressionFactory.All(match, GetArray(sourceTable), PgAllOperatorType.ILike)); + _sqlExpressionFactory.All(match, array, PgAllOperatorType.ILike)); } // Pattern match for: e.SomeArray.All(p => ints.Contains(p)) over non-column, @@ -316,9 +327,9 @@ protected override Expression ApplyInferredTypeMappings( Item: ColumnExpression sourceColumn, Array: var otherArray } - when sourceColumn.Table == sourceTable: + when sourceColumn.TableAlias == sourceTable.Alias: { - return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.ContainedBy(GetArray(sourceTable), otherArray)); + return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.ContainedBy(array, otherArray)); } // Pattern match for: new[] { 4, 5 }.All(p => e.SomeArray.Contains(p)) over column, @@ -329,9 +340,9 @@ protected override Expression ApplyInferredTypeMappings( Left: var otherArray, Right: PgNewArrayExpression { Expressions: [ColumnExpression sourceColumn] } } - when sourceColumn.Table == sourceTable: + when sourceColumn.TableAlias == sourceTable.Alias: { - return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.ContainedBy(GetArray(sourceTable), otherArray)); + return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.ContainedBy(array, otherArray)); } } } @@ -349,14 +360,15 @@ protected override Expression ApplyInferredTypeMappings( { if (source.QueryExpression is SelectExpression { - Tables: [(PgUnnestExpression or ValuesExpression { ColumnNames: ["_ord", "Value"] }) and var sourceTable], + Tables: [var sourceTable], Predicate: null, GroupBy: [], Having: null, IsDistinct: false, Limit: null, Offset: null - }) + } + && TryGetArray(sourceTable, out var array)) { // Pattern match: x.Array.Any() // Translation: cardinality(x.array) > 0 instead of EXISTS (SELECT 1 FROM FROM unnest(x.Array)) @@ -367,7 +379,7 @@ protected override Expression ApplyInferredTypeMappings( _sqlExpressionFactory.GreaterThan( _sqlExpressionFactory.Function( "cardinality", - new[] { GetArray(sourceTable) }, + new[] { array }, nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(int)), @@ -389,10 +401,10 @@ protected override Expression ApplyInferredTypeMappings( Pattern: ColumnExpression pattern, EscapeChar: SqlConstantExpression { Value: "" } } - when pattern.Table == sourceTable: + when pattern.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( - source, _sqlExpressionFactory.Any(match, GetArray(sourceTable), PgAnyOperatorType.Like)); + source, _sqlExpressionFactory.Any(match, array, PgAnyOperatorType.Like)); } // Pattern match: new[] { "a", "b", "c" }.Any(p => EF.Functions.Like(e.SomeText, p)) @@ -403,10 +415,10 @@ protected override Expression ApplyInferredTypeMappings( Pattern: ColumnExpression pattern, EscapeChar: SqlConstantExpression { Value: "" } } - when pattern.Table == sourceTable: + when pattern.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( - source, _sqlExpressionFactory.Any(match, GetArray(sourceTable), PgAnyOperatorType.ILike)); + source, _sqlExpressionFactory.Any(match, array, PgAnyOperatorType.ILike)); } // Array overlap over non-column @@ -417,9 +429,9 @@ protected override Expression ApplyInferredTypeMappings( Item: ColumnExpression sourceColumn, Array: var otherArray } - when sourceColumn.Table == sourceTable: + when sourceColumn.TableAlias == sourceTable.Alias: { - return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.Overlaps(GetArray(sourceTable), otherArray)); + return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.Overlaps(array, otherArray)); } // Array overlap over column @@ -431,9 +443,9 @@ protected override Expression ApplyInferredTypeMappings( Left: var otherArray, Right: PgNewArrayExpression { Expressions: [ColumnExpression sourceColumn] } } - when sourceColumn.Table == sourceTable: + when sourceColumn.TableAlias == sourceTable.Alias: { - return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.Overlaps(GetArray(sourceTable), otherArray)); + return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.Overlaps(array, otherArray)); } #region LTree translations @@ -446,14 +458,14 @@ protected override Expression ApplyInferredTypeMappings( Left: var ltree, Right: SqlUnaryExpression { OperatorType: ExpressionType.Convert, Operand: ColumnExpression lqueryColumn } } - when lqueryColumn.Table == sourceTable: + when lqueryColumn.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( source, new PgBinaryExpression( PgExpressionType.LTreeMatchesAny, ltree, - _sqlExpressionFactory.ApplyTypeMapping(GetArray(sourceTable), _typeMappingSource.FindMapping("lquery[]")), + _sqlExpressionFactory.ApplyTypeMapping(array, _typeMappingSource.FindMapping("lquery[]")), typeof(bool), typeMapping: _typeMappingSource.FindMapping(typeof(bool)))); } @@ -469,13 +481,13 @@ protected override Expression ApplyInferredTypeMappings( // Contains/ContainedBy can happen for non-LTree types too, so check that Right: { TypeMapping: NpgsqlLTreeTypeMapping } ltree } - when ltreeColumn.Table == sourceTable: + when ltreeColumn.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( source, new PgBinaryExpression( operatorType, - _sqlExpressionFactory.ApplyDefaultTypeMapping(GetArray(sourceTable)), + _sqlExpressionFactory.ApplyDefaultTypeMapping(array), ltree, typeof(bool), typeMapping: _typeMappingSource.FindMapping(typeof(bool)))); @@ -491,13 +503,13 @@ protected override Expression ApplyInferredTypeMappings( Left: ColumnExpression ltreeColumn, Right: var lquery } - when ltreeColumn.Table == sourceTable: + when ltreeColumn.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( source, new PgBinaryExpression( PgExpressionType.LTreeMatches, - _sqlExpressionFactory.ApplyDefaultTypeMapping(GetArray(sourceTable)), + _sqlExpressionFactory.ApplyDefaultTypeMapping(array), lquery, typeof(bool), typeMapping: _typeMappingSource.FindMapping(typeof(bool)))); @@ -512,13 +524,13 @@ protected override Expression ApplyInferredTypeMappings( Left: ColumnExpression ltreeColumn, Right: var lqueries } - when ltreeColumn.Table == sourceTable: + when ltreeColumn.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( source, new PgBinaryExpression( PgExpressionType.LTreeMatchesAny, - _sqlExpressionFactory.ApplyDefaultTypeMapping(GetArray(sourceTable)), + _sqlExpressionFactory.ApplyDefaultTypeMapping(array), lqueries, typeof(bool), typeMapping: _typeMappingSource.FindMapping(typeof(bool)))); @@ -578,26 +590,20 @@ protected override Expression ApplyInferredTypeMappings( /// protected override ShapedQueryExpression? TranslateContains(ShapedQueryExpression source, Expression item) { + // Note that most other simplifications convert ValuesExpression to unnest over array constructor, but we avoid doing that + // here for Contains, since the relational translation for ValuesExpression is better. if (source.QueryExpression is SelectExpression { - Tables: [(PgUnnestExpression or ValuesExpression { ColumnNames: ["_ord", "Value"] }) and var sourceTable], + Tables: [PgUnnestExpression { Array: var array }], Predicate: null, GroupBy: [], Having: null, IsDistinct: false, Limit: null, Offset: null - }) - { - if (TranslateExpression(item, applyDefaultTypeMapping: false) is not SqlExpression translatedItem) - { - return null; } - - // Note that most other simplifications here convert ValuesExpression to unnest over array constructor, but we avoid doing that - // here, since the relational translation for ValuesExpression is better. - var array = GetArray(sourceTable); - + && TranslateExpression(item, applyDefaultTypeMapping: false) is SqlExpression translatedItem) + { (translatedItem, array) = _sqlExpressionFactory.ApplyTypeMappingsOnItemAndArray(translatedItem, array); // When the array is a column, we translate Contains to array @> ARRAY[item]. GIN indexes on array are used, but null @@ -685,11 +691,13 @@ protected override Expression ApplyInferredTypeMappings( argumentsPropagateNullability: TrueArrays[1], typeof(int)); +#pragma warning disable EF1001 // SelectExpression constructors are currently internal return source.Update( - _sqlExpressionFactory.Select(translation), + new SelectExpression(translation, _queryCompilationContext.SqlAliasManager), Expression.Convert( new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(int?)), typeof(int))); +#pragma warning restore EF1001 } return base.TranslateCount(source, predicate); @@ -736,16 +744,13 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s // TODO: Conflicting type mappings from both sides? var inferredTypeMapping = projectedColumn1.TypeMapping ?? projectedColumn2.TypeMapping; -#pragma warning disable EF1001 // Internal EF Core API usage. +#pragma warning disable EF1001 // SelectExpression constructors are currently internal + var tableAlias = unnestExpression1.Alias; var selectExpression = new SelectExpression( - new PgUnnestExpression(unnestExpression1.Alias, _sqlExpressionFactory.Add(array1, array2), "value"), - columnName: "value", - columnType: projectedColumn1.Type, - columnTypeMapping: inferredTypeMapping, - isColumnNullable: projectedColumn1.IsNullable || projectedColumn2.IsNullable, - identifierColumnName: "ordinality", - identifierColumnType: typeof(int), - identifierColumnTypeMapping: _typeMappingSource.FindMapping(typeof(int))); + [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), "value")], + new ColumnExpression("value", tableAlias, projectedColumn1.Type, inferredTypeMapping, projectedColumn1.IsNullable || projectedColumn2.IsNullable), + identifier: [GenerateOrdinalityIdentifier(tableAlias)], + _queryCompilationContext.SqlAliasManager); #pragma warning restore EF1001 // Internal EF Core API usage. // TODO: Simplify by using UpdateQueryExpression after https://github.com/dotnet/efcore/issues/31511 @@ -796,11 +801,14 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s && TranslateExpression(index) is { } translatedIndex) { // Note that PostgreSQL arrays are 1-based, so adjust the index. +#pragma warning disable EF1001 // SelectExpression constructors are currently internal return source.UpdateQueryExpression( - _sqlExpressionFactory.Select( + new SelectExpression( _sqlExpressionFactory.ArrayIndex( array, - GenerateOneBasedIndexExpression(translatedIndex), projectedColumn.IsNullable))); + GenerateOneBasedIndexExpression(translatedIndex), projectedColumn.IsNullable), + _queryCompilationContext.SqlAliasManager)); +#pragma warning restore EF1001 } return base.TranslateElementAtOrDefault(source, index, returnDefault); @@ -823,7 +831,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s // select expression should already contain our predicate. if (source.QueryExpression is SelectExpression { - Tables: [(PgUnnestExpression or ValuesExpression { ColumnNames: ["_ord", "Value"] }) and var sourceTable], + Tables: [var sourceTable], Predicate: var translatedPredicate, GroupBy: [], Having: null, @@ -832,6 +840,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s Offset: null, Orderings: [] } + && TryGetArray(sourceTable, out var array) && translatedPredicate is null ^ predicate is null) { if (translatedPredicate is null) @@ -856,7 +865,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s // Contains/ContainedBy can happen for non-LTree types too, so check that Right: { TypeMapping: NpgsqlLTreeTypeMapping } ltree } - when ltreeColumn.Table == sourceTable: + when ltreeColumn.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( source, @@ -864,7 +873,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s operatorType == PgExpressionType.Contains ? PgExpressionType.LTreeFirstAncestor : PgExpressionType.LTreeFirstDescendent, - _sqlExpressionFactory.ApplyDefaultTypeMapping(GetArray(sourceTable)), + _sqlExpressionFactory.ApplyDefaultTypeMapping(array), ltree, typeof(LTree), _typeMappingSource.FindMapping(typeof(LTree)))); @@ -880,13 +889,13 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s Left: ColumnExpression ltreeColumn, Right: var lquery } - when ltreeColumn.Table == sourceTable: + when ltreeColumn.TableAlias == sourceTable.Alias: { return BuildSimplifiedShapedQuery( source, new PgBinaryExpression( PgExpressionType.LTreeFirstMatches, - _sqlExpressionFactory.ApplyDefaultTypeMapping(GetArray(sourceTable)), + _sqlExpressionFactory.ApplyDefaultTypeMapping(array), lquery, typeof(LTree), _typeMappingSource.FindMapping(typeof(LTree)))); @@ -921,25 +930,24 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s && TryGetProjectedColumn(source, out var projectedColumn) && TranslateExpression(count) is { } translatedCount) { -#pragma warning disable EF1001 // Internal EF Core API usage. +#pragma warning disable EF1001 // SelectExpression constructors are currently internal + var tableAlias = unnestExpression.Alias; var selectExpression = new SelectExpression( - new PgUnnestExpression( - unnestExpression.Alias, - _sqlExpressionFactory.ArraySlice( - array, - lowerBound: GenerateOneBasedIndexExpression(translatedCount), - upperBound: null, - projectedColumn.IsNullable), - "value"), - "value", - projectedColumn.Type, - projectedColumn.TypeMapping, - isColumnNullable: projectedColumn.IsNullable, - // isColumnNullable: /*projectedColumn.IsNullable*/ true, // TODO: This fails because of a shaper check - identifierColumnName: "ordinality", - identifierColumnType: typeof(int), - identifierColumnTypeMapping: _typeMappingSource.FindMapping(typeof(int))); -#pragma warning restore EF1001 // Internal EF Core API usage. + [ + new PgUnnestExpression( + tableAlias, + _sqlExpressionFactory.ArraySlice( + array, + lowerBound: GenerateOneBasedIndexExpression(translatedCount), + upperBound: null, + // isColumnNullable: /*projectedColumn.IsNullable*/ true, // TODO: This fails because of a shaper check + projectedColumn.IsNullable), + "value"), + ], + new ColumnExpression("value", tableAlias, projectedColumn.Type, projectedColumn.TypeMapping, projectedColumn.IsNullable), + identifier: [GenerateOrdinalityIdentifier(tableAlias)], + _queryCompilationContext.SqlAliasManager); +#pragma warning restore EF1001 // TODO: Simplify by using UpdateQueryExpression after https://github.com/dotnet/efcore/issues/31511 Expression shaperExpression = new ProjectionBindingExpression( @@ -1027,16 +1035,12 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s projectedColumn.IsNullable); } -#pragma warning disable EF1001 // Internal EF Core API usage. +#pragma warning disable EF1001 // SelectExpression constructors are currently internal var selectExpression = new SelectExpression( - new PgUnnestExpression(unnestExpression.Alias, sliceExpression, "value"), - "value", - projectedColumn.Type, - projectedColumn.TypeMapping, - isColumnNullable: projectedColumn.IsNullable, - identifierColumnName: "ordinality", - identifierColumnType: typeof(int), - identifierColumnTypeMapping: _typeMappingSource.FindMapping(typeof(int))); + [new PgUnnestExpression(unnestExpression.Alias, sliceExpression, "value")], + new ColumnExpression("value", unnestExpression.Alias, projectedColumn.Type, projectedColumn.TypeMapping, projectedColumn.IsNullable), + [GenerateOrdinalityIdentifier(unnestExpression.Alias)], + _queryCompilationContext.SqlAliasManager); #pragma warning restore EF1001 // Internal EF Core API usage. // TODO: Simplify by using UpdateQueryExpression after https://github.com/dotnet/efcore/issues/31511 @@ -1145,11 +1149,7 @@ protected override bool IsValidSelectExpressionForExecuteDelete( var entityProjectionExpression = (StructuralTypeProjectionExpression)selectExpression.GetProjection(projectionBindingExpression); var column = entityProjectionExpression.BindProperty(shaper.StructuralType.GetProperties().First()); - table = column.Table; - if (table is JoinExpressionBase joinExpressionBase) - { - table = joinExpressionBase.Table; - } + table = selectExpression.Tables.Select(t => t.UnwrapJoin()).Single(t => t.Alias == column.TableAlias); } if (table is TableExpression te) @@ -1207,6 +1207,13 @@ private bool TryGetProjectedColumn( return false; } + private (ColumnExpression, ValueComparer) GenerateOrdinalityIdentifier(string tableAlias) + { + _ordinalityTypeMapping ??= _typeMappingSource.FindMapping("int")!; + return (new ColumnExpression("ordinality", tableAlias, typeof(int), _ordinalityTypeMapping, nullable: false), + _ordinalityTypeMapping.Comparer); + } + /// /// PostgreSQL array indexing is 1-based. If the index happens to be a constant, just increment it. Otherwise, append a +1 in the /// SQL. @@ -1216,28 +1223,29 @@ private SqlExpression GenerateOneBasedIndexExpression(SqlExpression expression) ? _sqlExpressionFactory.Constant(Convert.ToInt32(constant.Value) + 1, constant.TypeMapping) : _sqlExpressionFactory.Add(expression, _sqlExpressionFactory.Constant(1)); +#pragma warning disable EF1001 // SelectExpression constructors are currently internal private ShapedQueryExpression BuildSimplifiedShapedQuery(ShapedQueryExpression source, SqlExpression translation) => source.Update( - _sqlExpressionFactory.Select(translation), + new SelectExpression(translation, _queryCompilationContext.SqlAliasManager), Expression.Convert( new ProjectionBindingExpression(translation, new ProjectionMember(), typeof(bool?)), typeof(bool))); +#pragma warning restore EF1001 /// /// Extracts the out of . /// If a is given, converts its literal values into a . /// - private SqlExpression GetArray(TableExpressionBase tableExpression) + private bool TryGetArray(TableExpressionBase tableExpression, [NotNullWhen(true)] out SqlExpression? array) { - Check.DebugAssert( - tableExpression is PgUnnestExpression or ValuesExpression { ColumnNames: ["_ord", "Value"] }, - "Bad tableExpression"); - switch (tableExpression) { case PgUnnestExpression unnest: - return unnest.Array; + array = unnest.Array; + return true; - case ValuesExpression valuesExpression: + // TODO: We currently don't have information type information on empty ValuesExpression, so we can't transform that into an + // array. + case ValuesExpression { ColumnNames: ["_ord", "Value"], RowValues.Count: > 0 } valuesExpression: { // The source table was a constant collection, so translated by default to ValuesExpression. Convert it to an unnest over // an array constructor. @@ -1249,12 +1257,14 @@ private SqlExpression GetArray(TableExpressionBase tableExpression) elements[i] = valuesExpression.RowValues[i].Values[1]; } - return new PgNewArrayExpression( + array = new PgNewArrayExpression( elements, valuesExpression.RowValues[0].Values[1].Type.MakeArrayType(), typeMapping: null); + return true; } default: - throw new ArgumentException(nameof(tableExpression)); + array = null; + return false; } } @@ -1279,8 +1289,7 @@ public bool ContainsReferenceToMainTable(TableExpressionBase tableExpression) return expression; } - if (expression is ColumnExpression columnExpression - && columnExpression.Table == mainTable) + if (expression is ColumnExpression { TableAlias: var tableAlias} && tableAlias == mainTable.Alias) { _containsReference = true; @@ -1312,7 +1321,7 @@ public NpgsqlInferredTypeMappingApplier( IModel model, NpgsqlTypeMappingSource typeMappingSource, NpgsqlSqlExpressionFactory sqlExpressionFactory, - IReadOnlyDictionary<(TableExpressionBase, string), RelationalTypeMapping?> inferredTypeMappings) + IReadOnlyDictionary<(string, string), RelationalTypeMapping?> inferredTypeMappings) : base(model, sqlExpressionFactory, inferredTypeMappings) { _typeMappingSource = typeMappingSource; @@ -1330,7 +1339,7 @@ protected override Expression VisitExtension(Expression expression) switch (expression) { case PgUnnestExpression unnestExpression - when TryGetInferredTypeMapping(unnestExpression, unnestExpression.ColumnName, out var elementTypeMapping): + when TryGetInferredTypeMapping(unnestExpression.Alias, unnestExpression.ColumnName, out var elementTypeMapping): { var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, Model, elementTypeMapping); diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs index ad06c1e83..b6e2eb279 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs @@ -45,5 +45,8 @@ public NpgsqlQueryableMethodTranslatingExpressionVisitorFactory( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext) - => new NpgsqlQueryableMethodTranslatingExpressionVisitor(Dependencies, RelationalDependencies, queryCompilationContext); + => new NpgsqlQueryableMethodTranslatingExpressionVisitor( + Dependencies, + RelationalDependencies, + (RelationalQueryCompilationContext)queryCompilationContext); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs index 826a7e2aa..1ac0dc924 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs @@ -44,8 +44,8 @@ public class NpgsqlUnnestPostprocessor : ExpressionVisitor && !selectExpression.Orderings.Select(o => o.Expression) .Concat(selectExpression.Projection.Select(p => p.Expression)) .Any( - p => p is ColumnExpression { Name: "ordinality", Table: var ordinalityTable } - && ordinalityTable == table)) + p => p is ColumnExpression { Name: "ordinality", } ordinalityColumn + && ordinalityColumn.TableAlias == table.Alias)) { if (newTables is null) { diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCharacterStringTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCharacterStringTypeMapping.cs index ca0a3095e..5df6a7219 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCharacterStringTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCharacterStringTypeMapping.cs @@ -122,7 +122,13 @@ protected override void ConfigureParameter(DbParameter parameter) base.ConfigureParameter(parameter); } - private static bool EqualsWithoutTrailingWhitespace(string? a, string? b) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool EqualsWithoutTrailingWhitespace(string? a, string? b) => (a, b) switch { (null, null) => true, @@ -131,6 +137,12 @@ private static bool EqualsWithoutTrailingWhitespace(string? a, string? b) _ => a.AsSpan().TrimEnd().SequenceEqual(b.AsSpan().TrimEnd()) }; - private static int GetHashCodeWithoutTrailingWhitespace(string a) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static int GetHashCodeWithoutTrailingWhitespace(string a) => a.TrimEnd().GetHashCode(); } diff --git a/test/EFCore.PG.FunctionalTests/BadDataJsonDeserializationNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BadDataJsonDeserializationNpgsqlTest.cs new file mode 100644 index 000000000..dcf301eba --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/BadDataJsonDeserializationNpgsqlTest.cs @@ -0,0 +1,9 @@ +#nullable enable + +namespace Npgsql.EntityFrameworkCore.PostgreSQL; + +public class BadDataJsonDeserializationSqlServerTest : BadDataJsonDeserializationTestBase +{ + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => base.OnConfiguring(optionsBuilder.UseNpgsql(b => b.UseNetTopologySuite())); +} diff --git a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs index 939fa9ae0..4d372c8e1 100644 --- a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs @@ -569,7 +569,7 @@ private static void AssertMappedDataTypes(MappedDataTypes entity, int id) Assert.Equal(new Dictionary { { "a", "b" } }, entity.DictionaryAsHstore); Assert.Equal(new NpgsqlRange(4, true, 8, false), entity.NpgsqlRangeAsRange); - Assert.Equal([2, 3], entity.IntArrayAsIntArray); + Assert.Equal(new[] { 2, 3 }, entity.IntArrayAsIntArray); Assert.Equal( [PhysicalAddress.Parse("08-00-2B-01-02-03"), PhysicalAddress.Parse("08-00-2B-01-02-04")], entity.PhysicalAddressArrayAsMacaddrArray); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs index 58acbafea..abaa17130 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs @@ -94,6 +94,128 @@ public override async Task Update_projected_complex_type_via_OrderBy_Skip_throws AssertExecuteUpdateSql(); } + public override async Task Update_complex_type_to_parameter(bool async) + { + await base.Update_complex_type_to_parameter(async); + + AssertExecuteUpdateSql( + """ +@__complex_type_newAddress_0_AddressLine1='New AddressLine1' +@__complex_type_newAddress_0_AddressLine2='New AddressLine2' +@__complex_type_newAddress_0_Tags={ 'new_tag1', 'new_tag2' } (DbType = Object) +@__complex_type_newAddress_0_ZipCode='99999' (Nullable = true) +@__complex_type_newAddress_0_Code='FR' +@__complex_type_newAddress_0_FullName='France' + +UPDATE "Customer" AS c +SET "ShippingAddress_AddressLine1" = @__complex_type_newAddress_0_AddressLine1, + "ShippingAddress_AddressLine2" = @__complex_type_newAddress_0_AddressLine2, + "ShippingAddress_Tags" = @__complex_type_newAddress_0_Tags, + "ShippingAddress_ZipCode" = @__complex_type_newAddress_0_ZipCode, + "ShippingAddress_Country_Code" = @__complex_type_newAddress_0_Code, + "ShippingAddress_Country_FullName" = @__complex_type_newAddress_0_FullName +"""); + } + + public override async Task Update_nested_complex_type_to_parameter(bool async) + { + await base.Update_nested_complex_type_to_parameter(async); + + AssertExecuteUpdateSql( + """ +@__complex_type_newCountry_0_Code='FR' +@__complex_type_newCountry_0_FullName='France' + +UPDATE "Customer" AS c +SET "ShippingAddress_Country_Code" = @__complex_type_newCountry_0_Code, + "ShippingAddress_Country_FullName" = @__complex_type_newCountry_0_FullName +"""); + } + + public override async Task Update_complex_type_to_another_database_complex_type(bool async) + { + await base.Update_complex_type_to_another_database_complex_type(async); + + AssertExecuteUpdateSql( + """ +UPDATE "Customer" AS c +SET "ShippingAddress_AddressLine1" = c."BillingAddress_AddressLine1", + "ShippingAddress_AddressLine2" = c."BillingAddress_AddressLine2", + "ShippingAddress_Tags" = c."BillingAddress_Tags", + "ShippingAddress_ZipCode" = c."BillingAddress_ZipCode", + "ShippingAddress_Country_Code" = c."ShippingAddress_Country_Code", + "ShippingAddress_Country_FullName" = c."ShippingAddress_Country_FullName" +"""); + } + + public override async Task Update_complex_type_to_inline_without_lambda(bool async) + { + await base.Update_complex_type_to_inline_without_lambda(async); + + AssertExecuteUpdateSql( + """ +UPDATE "Customer" AS c +SET "ShippingAddress_AddressLine1" = 'New AddressLine1', + "ShippingAddress_AddressLine2" = 'New AddressLine2', + "ShippingAddress_Tags" = ARRAY['new_tag1','new_tag2']::text[], + "ShippingAddress_ZipCode" = 99999, + "ShippingAddress_Country_Code" = 'FR', + "ShippingAddress_Country_FullName" = 'France' +"""); + } + + public override async Task Update_complex_type_to_inline_with_lambda(bool async) + { + await base.Update_complex_type_to_inline_with_lambda(async); + + AssertExecuteUpdateSql( + """ +UPDATE "Customer" AS c +SET "ShippingAddress_AddressLine1" = 'New AddressLine1', + "ShippingAddress_AddressLine2" = 'New AddressLine2', + "ShippingAddress_Tags" = ARRAY['new_tag1','new_tag2']::text[], + "ShippingAddress_ZipCode" = 99999, + "ShippingAddress_Country_Code" = 'FR', + "ShippingAddress_Country_FullName" = 'France' +"""); + } + + public override async Task Update_complex_type_to_another_database_complex_type_with_subquery(bool async) + { + await base.Update_complex_type_to_another_database_complex_type_with_subquery(async); + + AssertExecuteUpdateSql( + """ +@__p_0='1' + +UPDATE "Customer" AS c0 +SET "ShippingAddress_AddressLine1" = c1."BillingAddress_AddressLine1", + "ShippingAddress_AddressLine2" = c1."BillingAddress_AddressLine2", + "ShippingAddress_Tags" = c1."BillingAddress_Tags", + "ShippingAddress_ZipCode" = c1."BillingAddress_ZipCode", + "ShippingAddress_Country_Code" = c1."ShippingAddress_Country_Code", + "ShippingAddress_Country_FullName" = c1."ShippingAddress_Country_FullName" +FROM ( + SELECT c."Id", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" + FROM "Customer" AS c + ORDER BY c."Id" NULLS FIRST + OFFSET @__p_0 +) AS c1 +WHERE c0."Id" = c1."Id" +"""); + } + + public override async Task Update_collection_inside_complex_type(bool async) + { + await base.Update_collection_inside_complex_type(async); + + AssertExecuteUpdateSql( + """ +UPDATE "Customer" AS c +SET "ShippingAddress_Tags" = ARRAY['new_tag1','new_tag2']::text[] +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs index fcd36e8cf..2704ed853 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs @@ -22,6 +22,24 @@ DELETE FROM "Owner" AS o """); } + public override async Task Delete_with_owned_collection_and_non_natively_translatable_query(bool async) + { + await base.Delete_with_owned_collection_and_non_natively_translatable_query(async); + + AssertSql( + """ +@__p_0='1' + +DELETE FROM "Owner" AS o +WHERE o."Id" IN ( + SELECT o0."Id" + FROM "Owner" AS o0 + ORDER BY o0."Title" NULLS FIRST + OFFSET @__p_0 +) +"""); + } + public override async Task Delete_aggregate_root_when_table_sharing_with_owned(bool async) { await base.Delete_aggregate_root_when_table_sharing_with_owned(async); @@ -77,6 +95,19 @@ public override async Task Update_non_owned_property_on_entity_with_owned2(bool """); } + public override async Task Update_non_owned_property_on_entity_with_owned_in_join(bool async) + { + await base.Update_non_owned_property_on_entity_with_owned_in_join(async); + + AssertSql( + """ +UPDATE "Owner" AS o +SET "Title" = 'NewValue' +FROM "Owner" AS o0 +WHERE o."Id" = o0."Id" +"""); + } + public override async Task Update_owned_and_non_owned_properties_with_table_sharing(bool async) { await base.Update_owned_and_non_owned_properties_with_table_sharing(async); @@ -130,11 +161,25 @@ public override async Task Update_non_main_table_in_entity_with_entity_splitting UPDATE "BlogsPart1" AS b0 SET "Rating" = length(b0."Title")::int, "Title" = b0."Rating"::text +FROM "Blogs" AS b +WHERE b."Id" = b0."Id" """); } - public override Task Delete_entity_with_auto_include(bool async) - => Assert.ThrowsAsync(() => base.Delete_entity_with_auto_include(async)); // #30577 + public override async Task Delete_entity_with_auto_include(bool async) + { + await base.Delete_entity_with_auto_include(async); + + AssertSql( + """ +DELETE FROM "Context30572_Principal" AS c +WHERE c."Id" IN ( + SELECT c0."Id" + FROM "Context30572_Principal" AS c0 + LEFT JOIN "Context30572_Dependent" AS c1 ON c0."DependentId" = c1."Id" +) +"""); + } public override async Task Update_with_alias_uniquification_in_setter_subquery(bool async) { diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs index 8bbb02ee0..60c20d9d9 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs @@ -76,13 +76,13 @@ DELETE FROM "Order Details" AS o WHERE EXISTS ( SELECT 1 FROM ( - SELECT o0."OrderID", o0."ProductID", o0."Discount", o0."Quantity", o0."UnitPrice" + SELECT o0."OrderID", o0."ProductID" FROM "Order Details" AS o0 WHERE o0."OrderID" < 10300 ORDER BY o0."OrderID" NULLS FIRST OFFSET @__p_0 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS o1 + WHERE o1."OrderID" = o."OrderID" AND o1."ProductID" = o."ProductID") """); } @@ -98,13 +98,13 @@ DELETE FROM "Order Details" AS o WHERE EXISTS ( SELECT 1 FROM ( - SELECT o0."OrderID", o0."ProductID", o0."Discount", o0."Quantity", o0."UnitPrice" + SELECT o0."OrderID", o0."ProductID" FROM "Order Details" AS o0 WHERE o0."OrderID" < 10300 ORDER BY o0."OrderID" NULLS FIRST LIMIT @__p_0 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS o1 + WHERE o1."OrderID" = o."OrderID" AND o1."ProductID" = o."ProductID") """); } @@ -120,13 +120,13 @@ DELETE FROM "Order Details" AS o WHERE EXISTS ( SELECT 1 FROM ( - SELECT o0."OrderID", o0."ProductID", o0."Discount", o0."Quantity", o0."UnitPrice" + SELECT o0."OrderID", o0."ProductID" FROM "Order Details" AS o0 WHERE o0."OrderID" < 10300 ORDER BY o0."OrderID" NULLS FIRST LIMIT @__p_0 OFFSET @__p_0 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS o1 + WHERE o1."OrderID" = o."OrderID" AND o1."ProductID" = o."ProductID") """); } @@ -142,12 +142,12 @@ DELETE FROM "Order Details" AS o WHERE EXISTS ( SELECT 1 FROM ( - SELECT o0."OrderID", o0."ProductID", o0."Discount", o0."Quantity", o0."UnitPrice" + SELECT o0."OrderID", o0."ProductID" FROM "Order Details" AS o0 WHERE o0."OrderID" < 10300 OFFSET @__p_0 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS o1 + WHERE o1."OrderID" = o."OrderID" AND o1."ProductID" = o."ProductID") """); } @@ -163,12 +163,12 @@ DELETE FROM "Order Details" AS o WHERE EXISTS ( SELECT 1 FROM ( - SELECT o0."OrderID", o0."ProductID", o0."Discount", o0."Quantity", o0."UnitPrice" + SELECT o0."OrderID", o0."ProductID" FROM "Order Details" AS o0 WHERE o0."OrderID" < 10300 LIMIT @__p_0 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS o1 + WHERE o1."OrderID" = o."OrderID" AND o1."ProductID" = o."ProductID") """); } @@ -184,12 +184,12 @@ DELETE FROM "Order Details" AS o WHERE EXISTS ( SELECT 1 FROM ( - SELECT o0."OrderID", o0."ProductID", o0."Discount", o0."Quantity", o0."UnitPrice" + SELECT o0."OrderID", o0."ProductID" FROM "Order Details" AS o0 WHERE o0."OrderID" < 10300 LIMIT @__p_0 OFFSET @__p_0 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS o1 + WHERE o1."OrderID" = o."OrderID" AND o1."ProductID" = o."ProductID") """); } @@ -262,16 +262,16 @@ DELETE FROM "Order Details" AS o WHERE EXISTS ( SELECT 1 FROM ( - SELECT t."OrderID", t."ProductID", t."Discount", t."Quantity", t."UnitPrice" + SELECT o0."OrderID", o0."ProductID" FROM ( - SELECT o0."OrderID", o0."ProductID", o0."Discount", o0."Quantity", o0."UnitPrice" - FROM "Order Details" AS o0 - WHERE o0."OrderID" < 10300 + SELECT o1."OrderID", o1."ProductID" + FROM "Order Details" AS o1 + WHERE o1."OrderID" < 10300 LIMIT @__p_0 OFFSET @__p_0 - ) AS t + ) AS o0 LIMIT @__p_2 OFFSET @__p_1 - ) AS t0 - WHERE t0."OrderID" = o."OrderID" AND t0."ProductID" = o."ProductID") + ) AS o2 + WHERE o2."OrderID" = o."OrderID" AND o2."ProductID" = o."ProductID") """); } @@ -309,11 +309,11 @@ WHERE EXISTS ( SELECT 1 FROM "Orders" AS o0 INNER JOIN ( - SELECT o1."OrderID", o1."ProductID", o1."Discount", o1."Quantity", o1."UnitPrice" - FROM "Order Details" AS o1 - WHERE o1."ProductID" > 0 - ) AS t ON o0."OrderID" = t."OrderID" - WHERE o0."OrderID" < 10250 AND t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + SELECT o2."OrderID", o2."ProductID" + FROM "Order Details" AS o2 + WHERE o2."ProductID" > 0 + ) AS o1 ON o0."OrderID" = o1."OrderID" + WHERE o0."OrderID" < 10250 AND o1."OrderID" = o."OrderID" AND o1."ProductID" = o."ProductID") """); } @@ -362,8 +362,8 @@ WHERE o0."OrderID" < 10250 SELECT o1."OrderID", o1."ProductID", o1."Discount", o1."Quantity", o1."UnitPrice" FROM "Order Details" AS o1 WHERE o1."OrderID" > 11250 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS u + WHERE u."OrderID" = o."OrderID" AND u."ProductID" = o."ProductID") """); } @@ -377,15 +377,15 @@ DELETE FROM "Order Details" AS o WHERE EXISTS ( SELECT 1 FROM ( - SELECT o0."OrderID", o0."ProductID", o0."Discount", o0."Quantity", o0."UnitPrice" + SELECT o0."OrderID", o0."ProductID" FROM "Order Details" AS o0 WHERE o0."OrderID" < 10250 UNION ALL - SELECT o1."OrderID", o1."ProductID", o1."Discount", o1."Quantity", o1."UnitPrice" + SELECT o1."OrderID", o1."ProductID" FROM "Order Details" AS o1 WHERE o1."OrderID" > 11250 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS u + WHERE u."OrderID" = o."OrderID" AND u."ProductID" = o."ProductID") """); } @@ -406,8 +406,8 @@ WHERE o0."OrderID" < 10250 SELECT o1."OrderID", o1."ProductID", o1."Discount", o1."Quantity", o1."UnitPrice" FROM "Order Details" AS o1 WHERE o1."OrderID" > 11250 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS i + WHERE i."OrderID" = o."OrderID" AND i."ProductID" = o."ProductID") """); } @@ -428,8 +428,8 @@ WHERE o0."OrderID" < 10250 SELECT o1."OrderID", o1."ProductID", o1."Discount", o1."Quantity", o1."UnitPrice" FROM "Order Details" AS o1 WHERE o1."OrderID" > 11250 - ) AS t - WHERE t."OrderID" = o."OrderID" AND t."ProductID" = o."ProductID") + ) AS e + WHERE e."OrderID" = o."OrderID" AND e."ProductID" = o."ProductID") """); } @@ -499,13 +499,13 @@ public override async Task Delete_with_join(bool async) DELETE FROM "Order Details" AS o USING ( - SELECT o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate" + SELECT o0."OrderID" FROM "Orders" AS o0 WHERE o0."OrderID" < 10300 ORDER BY o0."OrderID" NULLS FIRST LIMIT @__p_1 OFFSET @__p_0 -) AS t -WHERE o."OrderID" = t."OrderID" +) AS o1 +WHERE o."OrderID" = o1."OrderID" """); } @@ -523,12 +523,12 @@ WHERE EXISTS ( SELECT 1 FROM "Order Details" AS o0 LEFT JOIN ( - SELECT o1."OrderID", o1."CustomerID", o1."EmployeeID", o1."OrderDate" - FROM "Orders" AS o1 - WHERE o1."OrderID" < 10300 - ORDER BY o1."OrderID" NULLS FIRST + SELECT o2."OrderID" + FROM "Orders" AS o2 + WHERE o2."OrderID" < 10300 + ORDER BY o2."OrderID" NULLS FIRST LIMIT @__p_1 OFFSET @__p_0 - ) AS t ON o0."OrderID" = t."OrderID" + ) AS o1 ON o0."OrderID" = o1."OrderID" WHERE o0."OrderID" < 10276 AND o0."OrderID" = o."OrderID" AND o0."ProductID" = o."ProductID") """); } @@ -544,12 +544,12 @@ WHERE EXISTS ( SELECT 1 FROM "Order Details" AS o0 CROSS JOIN ( - SELECT o1."OrderID", o1."CustomerID", o1."EmployeeID", o1."OrderDate" - FROM "Orders" AS o1 - WHERE o1."OrderID" < 10300 - ORDER BY o1."OrderID" NULLS FIRST + SELECT 1 + FROM "Orders" AS o2 + WHERE o2."OrderID" < 10300 + ORDER BY o2."OrderID" NULLS FIRST LIMIT 100 OFFSET 0 - ) AS t + ) AS o1 WHERE o0."OrderID" < 10276 AND o0."OrderID" = o."OrderID" AND o0."ProductID" = o."ProductID") """); } @@ -565,12 +565,12 @@ WHERE EXISTS ( SELECT 1 FROM "Order Details" AS o0 JOIN LATERAL ( - SELECT o1."OrderID", o1."CustomerID", o1."EmployeeID", o1."OrderDate" - FROM "Orders" AS o1 - WHERE o1."OrderID" < o0."OrderID" - ORDER BY o1."OrderID" NULLS FIRST + SELECT 1 + FROM "Orders" AS o2 + WHERE o2."OrderID" < o0."OrderID" + ORDER BY o2."OrderID" NULLS FIRST LIMIT 100 OFFSET 0 - ) AS t ON TRUE + ) AS o1 ON TRUE WHERE o0."OrderID" < 10276 AND o0."OrderID" = o."OrderID" AND o0."ProductID" = o."ProductID") """); } @@ -586,12 +586,12 @@ WHERE EXISTS ( SELECT 1 FROM "Order Details" AS o0 LEFT JOIN LATERAL ( - SELECT o1."OrderID", o1."CustomerID", o1."EmployeeID", o1."OrderDate" - FROM "Orders" AS o1 - WHERE o1."OrderID" < o0."OrderID" - ORDER BY o1."OrderID" NULLS FIRST + SELECT 1 + FROM "Orders" AS o2 + WHERE o2."OrderID" < o0."OrderID" + ORDER BY o2."OrderID" NULLS FIRST LIMIT 100 OFFSET 0 - ) AS t ON TRUE + ) AS o1 ON TRUE WHERE o0."OrderID" < 10276 AND o0."OrderID" = o."OrderID" AND o0."ProductID" = o."ProductID") """); } @@ -728,14 +728,14 @@ public override async Task Update_Where_OrderBy_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c0 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" - FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' -) AS t -WHERE c."CustomerID" = t."CustomerID" + SELECT c."CustomerID" + FROM "Customers" AS c + WHERE c."CustomerID" LIKE 'F%' +) AS c1 +WHERE c0."CustomerID" = c1."CustomerID" """); } @@ -747,16 +747,16 @@ public override async Task Update_Where_OrderBy_Skip_set_constant(bool async) """ @__p_0='4' -UPDATE "Customers" AS c +UPDATE "Customers" AS c0 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" - FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' - ORDER BY c0."City" NULLS FIRST + SELECT c."CustomerID" + FROM "Customers" AS c + WHERE c."CustomerID" LIKE 'F%' + ORDER BY c."City" NULLS FIRST OFFSET @__p_0 -) AS t -WHERE c."CustomerID" = t."CustomerID" +) AS c1 +WHERE c0."CustomerID" = c1."CustomerID" """); } @@ -768,16 +768,16 @@ public override async Task Update_Where_OrderBy_Take_set_constant(bool async) """ @__p_0='4' -UPDATE "Customers" AS c +UPDATE "Customers" AS c0 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" - FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' - ORDER BY c0."City" NULLS FIRST + SELECT c."CustomerID" + FROM "Customers" AS c + WHERE c."CustomerID" LIKE 'F%' + ORDER BY c."City" NULLS FIRST LIMIT @__p_0 -) AS t -WHERE c."CustomerID" = t."CustomerID" +) AS c1 +WHERE c0."CustomerID" = c1."CustomerID" """); } @@ -790,16 +790,16 @@ public override async Task Update_Where_OrderBy_Skip_Take_set_constant(bool asyn @__p_1='4' @__p_0='2' -UPDATE "Customers" AS c +UPDATE "Customers" AS c0 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" - FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' - ORDER BY c0."City" NULLS FIRST + SELECT c."CustomerID" + FROM "Customers" AS c + WHERE c."CustomerID" LIKE 'F%' + ORDER BY c."City" NULLS FIRST LIMIT @__p_1 OFFSET @__p_0 -) AS t -WHERE c."CustomerID" = t."CustomerID" +) AS c1 +WHERE c0."CustomerID" = c1."CustomerID" """); } @@ -812,21 +812,21 @@ public override async Task Update_Where_OrderBy_Skip_Take_Skip_Take_set_constant @__p_1='6' @__p_0='2' -UPDATE "Customers" AS c +UPDATE "Customers" AS c1 SET "ContactName" = 'Updated' FROM ( - SELECT t."CustomerID", t."Address", t."City", t."CompanyName", t."ContactName", t."ContactTitle", t."Country", t."Fax", t."Phone", t."PostalCode", t."Region" + SELECT c0."CustomerID" FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" - FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' - ORDER BY c0."City" NULLS FIRST + SELECT c."CustomerID", c."City" + FROM "Customers" AS c + WHERE c."CustomerID" LIKE 'F%' + ORDER BY c."City" NULLS FIRST LIMIT @__p_1 OFFSET @__p_0 - ) AS t - ORDER BY t."City" NULLS FIRST + ) AS c0 + ORDER BY c0."City" NULLS FIRST LIMIT @__p_0 OFFSET @__p_0 -) AS t0 -WHERE c."CustomerID" = t0."CustomerID" +) AS c2 +WHERE c1."CustomerID" = c2."CustomerID" """); } @@ -903,14 +903,14 @@ public override async Task Update_Where_Distinct_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c0 SET "ContactName" = 'Updated' FROM ( - SELECT DISTINCT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" - FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' -) AS t -WHERE c."CustomerID" = t."CustomerID" + SELECT DISTINCT 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" LIKE 'F%' +) AS c1 +WHERE c0."CustomerID" = c1."CustomerID" """); } @@ -920,15 +920,15 @@ public override async Task Update_Where_using_navigation_set_null(bool async) AssertExecuteUpdateSql( """ -UPDATE "Orders" AS o +UPDATE "Orders" AS o0 SET "OrderDate" = NULL FROM ( - SELECT o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate", c."CustomerID" AS "CustomerID0" - FROM "Orders" AS o0 - LEFT JOIN "Customers" AS c ON o0."CustomerID" = c."CustomerID" + SELECT o."OrderID" + FROM "Orders" AS o + LEFT JOIN "Customers" AS c ON o."CustomerID" = c."CustomerID" WHERE c."City" = 'Seattle' -) AS t -WHERE o."OrderID" = t."OrderID" +) AS s +WHERE o0."OrderID" = s."OrderID" """); } @@ -1077,18 +1077,18 @@ public override async Task Update_Union_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c1 SET "ContactName" = 'Updated' FROM ( + 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" LIKE 'F%' + UNION SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' - UNION - SELECT c1."CustomerID", c1."Address", c1."City", c1."CompanyName", c1."ContactName", c1."ContactTitle", c1."Country", c1."Fax", c1."Phone", c1."PostalCode", c1."Region" - FROM "Customers" AS c1 - WHERE c1."CustomerID" LIKE 'A%' -) AS t -WHERE c."CustomerID" = t."CustomerID" + WHERE c0."CustomerID" LIKE 'A%' +) AS u +WHERE c1."CustomerID" = u."CustomerID" """); } @@ -1098,18 +1098,18 @@ public override async Task Update_Concat_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c1 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" - FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' + SELECT c."CustomerID" + FROM "Customers" AS c + WHERE c."CustomerID" LIKE 'F%' UNION ALL - SELECT c1."CustomerID", c1."Address", c1."City", c1."CompanyName", c1."ContactName", c1."ContactTitle", c1."Country", c1."Fax", c1."Phone", c1."PostalCode", c1."Region" - FROM "Customers" AS c1 - WHERE c1."CustomerID" LIKE 'A%' -) AS t -WHERE c."CustomerID" = t."CustomerID" + SELECT c0."CustomerID" + FROM "Customers" AS c0 + WHERE c0."CustomerID" LIKE 'A%' +) AS u +WHERE c1."CustomerID" = u."CustomerID" """); } @@ -1119,18 +1119,18 @@ public override async Task Update_Except_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c1 SET "ContactName" = 'Updated' FROM ( + 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" LIKE 'F%' + EXCEPT SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' - EXCEPT - SELECT c1."CustomerID", c1."Address", c1."City", c1."CompanyName", c1."ContactName", c1."ContactTitle", c1."Country", c1."Fax", c1."Phone", c1."PostalCode", c1."Region" - FROM "Customers" AS c1 - WHERE c1."CustomerID" LIKE 'A%' -) AS t -WHERE c."CustomerID" = t."CustomerID" + WHERE c0."CustomerID" LIKE 'A%' +) AS e +WHERE c1."CustomerID" = e."CustomerID" """); } @@ -1140,18 +1140,18 @@ public override async Task Update_Intersect_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c1 SET "ContactName" = 'Updated' FROM ( + 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" LIKE 'F%' + INTERSECT SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM "Customers" AS c0 - WHERE c0."CustomerID" LIKE 'F%' - INTERSECT - SELECT c1."CustomerID", c1."Address", c1."City", c1."CompanyName", c1."ContactName", c1."ContactTitle", c1."Country", c1."Fax", c1."Phone", c1."PostalCode", c1."Region" - FROM "Customers" AS c1 - WHERE c1."CustomerID" LIKE 'A%' -) AS t -WHERE c."CustomerID" = t."CustomerID" + WHERE c0."CustomerID" LIKE 'A%' +) AS i +WHERE c1."CustomerID" = i."CustomerID" """); } @@ -1164,11 +1164,11 @@ public override async Task Update_with_join_set_constant(bool async) UPDATE "Customers" AS c SET "ContactName" = 'Updated' FROM ( - SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" + SELECT o."CustomerID" FROM "Orders" AS o WHERE o."OrderID" < 10300 -) AS t -WHERE c."CustomerID" = t."CustomerID" AND c."CustomerID" LIKE 'F%' +) AS o0 +WHERE c."CustomerID" = o0."CustomerID" AND c."CustomerID" LIKE 'F%' """); } @@ -1178,19 +1178,19 @@ public override async Task Update_with_left_join_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c0 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region", t."OrderID", t."CustomerID" AS "CustomerID0", t."EmployeeID", t."OrderDate" - FROM "Customers" AS c0 + SELECT c."CustomerID" + FROM "Customers" AS c LEFT JOIN ( - SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" + SELECT o."CustomerID" FROM "Orders" AS o WHERE o."OrderID" < 10300 - ) AS t ON c0."CustomerID" = t."CustomerID" - WHERE c0."CustomerID" LIKE 'F%' -) AS t0 -WHERE c."CustomerID" = t0."CustomerID" + ) AS o0 ON c."CustomerID" = o0."CustomerID" + WHERE c."CustomerID" LIKE 'F%' +) AS s +WHERE c0."CustomerID" = s."CustomerID" """); } @@ -1203,10 +1203,10 @@ public override async Task Update_with_cross_join_set_constant(bool async) UPDATE "Customers" AS c SET "ContactName" = 'Updated' FROM ( - SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" + SELECT 1 FROM "Orders" AS o WHERE o."OrderID" < 10300 -) AS t +) AS o0 WHERE c."CustomerID" LIKE 'F%' """); } @@ -1217,19 +1217,19 @@ public override async Task Update_with_cross_apply_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c0 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region", t."OrderID", t."CustomerID" AS "CustomerID0", t."EmployeeID", t."OrderDate" - FROM "Customers" AS c0 + SELECT c."CustomerID" + FROM "Customers" AS c JOIN LATERAL ( - SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" + SELECT 1 FROM "Orders" AS o - WHERE o."OrderID" < 10300 AND date_part('year', o."OrderDate")::int < length(c0."ContactName")::int - ) AS t ON TRUE - WHERE c0."CustomerID" LIKE 'F%' -) AS t0 -WHERE c."CustomerID" = t0."CustomerID" + WHERE o."OrderID" < 10300 AND date_part('year', o."OrderDate")::int < length(c."ContactName")::int + ) AS o0 ON TRUE + WHERE c."CustomerID" LIKE 'F%' +) AS s +WHERE c0."CustomerID" = s."CustomerID" """); } @@ -1239,19 +1239,19 @@ public override async Task Update_with_outer_apply_set_constant(bool async) AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c0 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region", t."OrderID", t."CustomerID" AS "CustomerID0", t."EmployeeID", t."OrderDate" - FROM "Customers" AS c0 + SELECT c."CustomerID" + FROM "Customers" AS c LEFT JOIN LATERAL ( - SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" + SELECT 1 FROM "Orders" AS o - WHERE o."OrderID" < 10300 AND date_part('year', o."OrderDate")::int < length(c0."ContactName")::int - ) AS t ON TRUE - WHERE c0."CustomerID" LIKE 'F%' -) AS t0 -WHERE c."CustomerID" = t0."CustomerID" + WHERE o."OrderID" < 10300 AND date_part('year', o."OrderDate")::int < length(c."ContactName")::int + ) AS o0 ON TRUE + WHERE c."CustomerID" LIKE 'F%' +) AS s +WHERE c0."CustomerID" = s."CustomerID" """); } @@ -1262,24 +1262,24 @@ public override async Task Update_with_cross_join_left_join_set_constant(bool as AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c2 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region", t."CustomerID" AS "CustomerID0", t."Address" AS "Address0", t."City" AS "City0", t."CompanyName" AS "CompanyName0", t."ContactName" AS "ContactName0", t."ContactTitle" AS "ContactTitle0", t."Country" AS "Country0", t."Fax" AS "Fax0", t."Phone" AS "Phone0", t."PostalCode" AS "PostalCode0", t."Region" AS "Region0", t0."OrderID", t0."CustomerID" AS "CustomerID1", t0."EmployeeID", t0."OrderDate" - FROM "Customers" AS c0 + SELECT c."CustomerID" + FROM "Customers" AS c CROSS JOIN ( - SELECT c1."CustomerID", c1."Address", c1."City", c1."CompanyName", c1."ContactName", c1."ContactTitle", c1."Country", c1."Fax", c1."Phone", c1."PostalCode", c1."Region" - FROM "Customers" AS c1 - WHERE c1."City" LIKE 'S%' - ) AS t + SELECT 1 + FROM "Customers" AS c0 + WHERE c0."City" LIKE 'S%' + ) AS c1 LEFT JOIN ( - SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" + SELECT o."CustomerID" FROM "Orders" AS o WHERE o."OrderID" < 10300 - ) AS t0 ON c0."CustomerID" = t0."CustomerID" - WHERE c0."CustomerID" LIKE 'F%' -) AS t1 -WHERE c."CustomerID" = t1."CustomerID" + ) AS o0 ON c."CustomerID" = o0."CustomerID" + WHERE c."CustomerID" LIKE 'F%' +) AS s +WHERE c2."CustomerID" = s."CustomerID" """); } @@ -1290,24 +1290,24 @@ public override async Task Update_with_cross_join_cross_apply_set_constant(bool AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c2 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region", t0."OrderID", t0."CustomerID" AS "CustomerID0", t0."EmployeeID", t0."OrderDate", t."CustomerID" AS "CustomerID1" - FROM "Customers" AS c0 + SELECT c."CustomerID" + FROM "Customers" AS c CROSS JOIN ( - SELECT c1."CustomerID" - FROM "Customers" AS c1 - WHERE c1."City" LIKE 'S%' - ) AS t + SELECT 1 + FROM "Customers" AS c0 + WHERE c0."City" LIKE 'S%' + ) AS c1 JOIN LATERAL ( - SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" + SELECT 1 FROM "Orders" AS o - WHERE o."OrderID" < 10300 AND date_part('year', o."OrderDate")::int < length(c0."ContactName")::int - ) AS t0 ON TRUE - WHERE c0."CustomerID" LIKE 'F%' -) AS t1 -WHERE c."CustomerID" = t1."CustomerID" + WHERE o."OrderID" < 10300 AND date_part('year', o."OrderDate")::int < length(c."ContactName")::int + ) AS o0 ON TRUE + WHERE c."CustomerID" LIKE 'F%' +) AS s +WHERE c2."CustomerID" = s."CustomerID" """); } @@ -1318,24 +1318,24 @@ public override async Task Update_with_cross_join_outer_apply_set_constant(bool AssertExecuteUpdateSql( """ -UPDATE "Customers" AS c +UPDATE "Customers" AS c2 SET "ContactName" = 'Updated' FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region", t."CustomerID" AS "CustomerID0", t."Address" AS "Address0", t."City" AS "City0", t."CompanyName" AS "CompanyName0", t."ContactName" AS "ContactName0", t."ContactTitle" AS "ContactTitle0", t."Country" AS "Country0", t."Fax" AS "Fax0", t."Phone" AS "Phone0", t."PostalCode" AS "PostalCode0", t."Region" AS "Region0", t0."OrderID", t0."CustomerID" AS "CustomerID1", t0."EmployeeID", t0."OrderDate" - FROM "Customers" AS c0 + SELECT c."CustomerID" + FROM "Customers" AS c CROSS JOIN ( - SELECT c1."CustomerID", c1."Address", c1."City", c1."CompanyName", c1."ContactName", c1."ContactTitle", c1."Country", c1."Fax", c1."Phone", c1."PostalCode", c1."Region" - FROM "Customers" AS c1 - WHERE c1."City" LIKE 'S%' - ) AS t + SELECT 1 + FROM "Customers" AS c0 + WHERE c0."City" LIKE 'S%' + ) AS c1 LEFT JOIN LATERAL ( - SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" + SELECT 1 FROM "Orders" AS o - WHERE o."OrderID" < 10300 AND date_part('year', o."OrderDate")::int < length(c0."ContactName")::int - ) AS t0 ON TRUE - WHERE c0."CustomerID" LIKE 'F%' -) AS t1 -WHERE c."CustomerID" = t1."CustomerID" + WHERE o."OrderID" < 10300 AND date_part('year', o."OrderDate")::int < length(c."ContactName")::int + ) AS o0 ON TRUE + WHERE c."CustomerID" LIKE 'F%' +) AS s +WHERE c2."CustomerID" = s."CustomerID" """); } @@ -1352,19 +1352,19 @@ public override async Task Update_Where_SelectMany_subquery_set_null(bool async) AssertExecuteUpdateSql( """ -UPDATE "Orders" AS o +UPDATE "Orders" AS o1 SET "OrderDate" = NULL FROM ( - SELECT t."OrderID", t."CustomerID", t."EmployeeID", t."OrderDate", c."CustomerID" AS "CustomerID0" + SELECT o0."OrderID" FROM "Customers" AS c INNER JOIN ( - SELECT o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate" - FROM "Orders" AS o0 - WHERE date_part('year', o0."OrderDate")::int = 1997 - ) AS t ON c."CustomerID" = t."CustomerID" + SELECT o."OrderID", o."CustomerID" + FROM "Orders" AS o + WHERE date_part('year', o."OrderDate")::int = 1997 + ) AS o0 ON c."CustomerID" = o0."CustomerID" WHERE c."CustomerID" LIKE 'F%' -) AS t0 -WHERE o."OrderID" = t0."OrderID" +) AS s +WHERE o1."OrderID" = s."OrderID" """); } @@ -1392,12 +1392,12 @@ public override async Task Update_Where_Join_set_property_from_joined_table(bool AssertExecuteUpdateSql( """ UPDATE "Customers" AS c -SET "City" = t."City" +SET "City" = c1."City" FROM ( - SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" + SELECT c0."City" FROM "Customers" AS c0 WHERE c0."CustomerID" = 'ALFKI' -) AS t +) AS c1 WHERE c."CustomerID" LIKE 'F%' """); } diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesNpgsqlTest.cs index 79a758d0c..ae128131e 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCFiltersInheritanceBulkUpdatesNpgsqlTest.cs @@ -35,13 +35,13 @@ DELETE FROM "Countries" AS c WHERE ( SELECT count(*)::int FROM ( - SELECT e."Id", e."CountryId", e."Name", e."Species", e."EagleId", e."IsFlightless", e."Group", NULL AS "FoundOn", 'Eagle' AS "Discriminator" + SELECT e."CountryId" FROM "Eagle" AS e UNION ALL - SELECT k."Id", k."CountryId", k."Name", k."Species", k."EagleId", k."IsFlightless", NULL AS "Group", k."FoundOn", 'Kiwi' AS "Discriminator" + SELECT k."CountryId" FROM "Kiwi" AS k - ) AS t - WHERE t."CountryId" = 1 AND c."Id" = t."CountryId" AND t."CountryId" > 0) > 0 + ) AS u + WHERE u."CountryId" = 1 AND c."Id" = u."CountryId" AND u."CountryId" > 0) > 0 """); } @@ -55,10 +55,10 @@ DELETE FROM "Countries" AS c WHERE ( SELECT count(*)::int FROM ( - SELECT k."Id", k."CountryId", k."Name", k."Species", k."EagleId", k."IsFlightless", NULL AS "Group", k."FoundOn", 'Kiwi' AS "Discriminator" + SELECT k."CountryId" FROM "Kiwi" AS k - ) AS t - WHERE t."CountryId" = 1 AND c."Id" = t."CountryId" AND t."CountryId" > 0) > 0 + ) AS u + WHERE u."CountryId" = 1 AND c."Id" = u."CountryId" AND u."CountryId" > 0) > 0 """); } @@ -153,13 +153,13 @@ public override async Task Update_where_using_hierarchy(bool async) WHERE ( SELECT count(*)::int FROM ( - SELECT e."Id", e."CountryId", e."Name", e."Species", e."EagleId", e."IsFlightless", e."Group", NULL AS "FoundOn", 'Eagle' AS "Discriminator" + SELECT e."CountryId" FROM "Eagle" AS e UNION ALL - SELECT k."Id", k."CountryId", k."Name", k."Species", k."EagleId", k."IsFlightless", NULL AS "Group", k."FoundOn", 'Kiwi' AS "Discriminator" + SELECT k."CountryId" FROM "Kiwi" AS k - ) AS t - WHERE t."CountryId" = 1 AND c."Id" = t."CountryId" AND t."CountryId" > 0) > 0 + ) AS u + WHERE u."CountryId" = 1 AND c."Id" = u."CountryId" AND u."CountryId" > 0) > 0 """); } @@ -187,10 +187,10 @@ public override async Task Update_where_using_hierarchy_derived(bool async) WHERE ( SELECT count(*)::int FROM ( - SELECT k."Id", k."CountryId", k."Name", k."Species", k."EagleId", k."IsFlightless", NULL AS "Group", k."FoundOn", 'Kiwi' AS "Discriminator" + SELECT k."CountryId" FROM "Kiwi" AS k - ) AS t - WHERE t."CountryId" = 1 AND c."Id" = t."CountryId" AND t."CountryId" > 0) > 0 + ) AS u + WHERE u."CountryId" = 1 AND c."Id" = u."CountryId" AND u."CountryId" > 0) > 0 """); } diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesNpgsqlTest.cs index 6b68719e9..f16b4aad5 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPCInheritanceBulkUpdatesNpgsqlTest.cs @@ -35,13 +35,13 @@ DELETE FROM "Countries" AS c WHERE ( SELECT count(*)::int FROM ( - SELECT e."Id", e."CountryId", e."Name", e."Species", e."EagleId", e."IsFlightless", e."Group", NULL AS "FoundOn", 'Eagle' AS "Discriminator" + SELECT e."CountryId" FROM "Eagle" AS e UNION ALL - SELECT k."Id", k."CountryId", k."Name", k."Species", k."EagleId", k."IsFlightless", NULL AS "Group", k."FoundOn", 'Kiwi' AS "Discriminator" + SELECT k."CountryId" FROM "Kiwi" AS k - ) AS t - WHERE c."Id" = t."CountryId" AND t."CountryId" > 0) > 0 + ) AS u + WHERE c."Id" = u."CountryId" AND u."CountryId" > 0) > 0 """); } @@ -55,10 +55,10 @@ DELETE FROM "Countries" AS c WHERE ( SELECT count(*)::int FROM ( - SELECT k."Id", k."CountryId", k."Name", k."Species", k."EagleId", k."IsFlightless", NULL AS "Group", k."FoundOn", 'Kiwi' AS "Discriminator" + SELECT k."CountryId" FROM "Kiwi" AS k - ) AS t - WHERE c."Id" = t."CountryId" AND t."CountryId" > 0) > 0 + ) AS u + WHERE c."Id" = u."CountryId" AND u."CountryId" > 0) > 0 """); } @@ -151,13 +151,13 @@ public override async Task Update_where_using_hierarchy(bool async) WHERE ( SELECT count(*)::int FROM ( - SELECT e."Id", e."CountryId", e."Name", e."Species", e."EagleId", e."IsFlightless", e."Group", NULL AS "FoundOn", 'Eagle' AS "Discriminator" + SELECT e."CountryId" FROM "Eagle" AS e UNION ALL - SELECT k."Id", k."CountryId", k."Name", k."Species", k."EagleId", k."IsFlightless", NULL AS "Group", k."FoundOn", 'Kiwi' AS "Discriminator" + SELECT k."CountryId" FROM "Kiwi" AS k - ) AS t - WHERE c."Id" = t."CountryId" AND t."CountryId" > 0) > 0 + ) AS u + WHERE c."Id" = u."CountryId" AND u."CountryId" > 0) > 0 """); } @@ -184,10 +184,10 @@ public override async Task Update_where_using_hierarchy_derived(bool async) WHERE ( SELECT count(*)::int FROM ( - SELECT k."Id", k."CountryId", k."Name", k."Species", k."EagleId", k."IsFlightless", NULL AS "Group", k."FoundOn", 'Kiwi' AS "Discriminator" + SELECT k."CountryId" FROM "Kiwi" AS k - ) AS t - WHERE c."Id" = t."CountryId" AND t."CountryId" > 0) > 0 + ) AS u + WHERE c."Id" = u."CountryId" AND u."CountryId" > 0) > 0 """); } diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesNpgsqlTest.cs index 9e9a1477e..6ecf13d06 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesNpgsqlTest.cs @@ -31,9 +31,6 @@ DELETE FROM "Countries" AS c WHERE ( SELECT count(*)::int FROM "Animals" AS a - LEFT JOIN "Birds" AS b ON a."Id" = b."Id" - LEFT JOIN "Eagle" AS e ON a."Id" = e."Id" - LEFT JOIN "Kiwi" AS k ON a."Id" = k."Id" WHERE a."CountryId" = 1 AND c."Id" = a."CountryId" AND a."CountryId" > 0) > 0 """); } @@ -48,8 +45,6 @@ DELETE FROM "Countries" AS c WHERE ( SELECT count(*)::int FROM "Animals" AS a - LEFT JOIN "Birds" AS b ON a."Id" = b."Id" - LEFT JOIN "Eagle" AS e ON a."Id" = e."Id" LEFT JOIN "Kiwi" AS k ON a."Id" = k."Id" WHERE a."CountryId" = 1 AND c."Id" = a."CountryId" AND k."Id" IS NOT NULL AND a."CountryId" > 0) > 0 """); @@ -83,20 +78,14 @@ public override async Task Update_base_type(bool async) // TODO: This over-complex SQL would get pruned after https://github.com/dotnet/efcore/issues/31083 AssertExecuteUpdateSql( """ -UPDATE "Animals" AS a +UPDATE "Animals" AS a0 SET "Name" = 'Animal' FROM ( - SELECT a0."Id", a0."CountryId", a0."Name", a0."Species", b0."EagleId", b0."IsFlightless", e0."Group", k0."FoundOn", CASE - WHEN k0."Id" IS NOT NULL THEN 'Kiwi' - WHEN e0."Id" IS NOT NULL THEN 'Eagle' - END AS "Discriminator" - FROM "Animals" AS a0 - LEFT JOIN "Birds" AS b0 ON a0."Id" = b0."Id" - LEFT JOIN "Eagle" AS e0 ON a0."Id" = e0."Id" - LEFT JOIN "Kiwi" AS k0 ON a0."Id" = k0."Id" - WHERE a0."CountryId" = 1 AND a0."Name" = 'Great spotted kiwi' -) AS t -WHERE a."Id" = t."Id" + SELECT a."Id" + FROM "Animals" AS a + WHERE a."CountryId" = 1 AND a."Name" = 'Great spotted kiwi' +) AS s +WHERE a0."Id" = s."Id" """); } @@ -107,20 +96,17 @@ public override async Task Update_base_type_with_OfType(bool async) // TODO: This over-complex SQL would get pruned after https://github.com/dotnet/efcore/issues/31083 AssertExecuteUpdateSql( """ -UPDATE "Animals" AS a +UPDATE "Animals" AS a0 SET "Name" = 'NewBird' FROM "Birds" AS b, - "Kiwi" AS k, + "Kiwi" AS k0, ( - SELECT a0."Id", a0."CountryId", a0."Name", a0."Species", b0."EagleId", b0."IsFlightless", k0."FoundOn", CASE - WHEN k0."Id" IS NOT NULL THEN 'Kiwi' - END AS "Discriminator" - FROM "Animals" AS a0 - LEFT JOIN "Birds" AS b0 ON a0."Id" = b0."Id" - LEFT JOIN "Kiwi" AS k0 ON a0."Id" = k0."Id" - WHERE a0."CountryId" = 1 AND k0."Id" IS NOT NULL - ) AS t -WHERE a."Id" = t."Id" AND a."Id" = k."Id" AND a."Id" = b."Id" + SELECT a."Id" + FROM "Animals" AS a + LEFT JOIN "Kiwi" AS k ON a."Id" = k."Id" + WHERE a."CountryId" = 1 AND k."Id" IS NOT NULL + ) AS s +WHERE a0."Id" = s."Id" AND a0."Id" = k0."Id" AND a0."Id" = b."Id" """); } @@ -191,9 +177,6 @@ public override async Task Update_where_using_hierarchy(bool async) WHERE ( SELECT count(*)::int FROM "Animals" AS a - LEFT JOIN "Birds" AS b ON a."Id" = b."Id" - LEFT JOIN "Eagle" AS e ON a."Id" = e."Id" - LEFT JOIN "Kiwi" AS k ON a."Id" = k."Id" WHERE a."CountryId" = 1 AND c."Id" = a."CountryId" AND a."CountryId" > 0) > 0 """); } @@ -209,8 +192,6 @@ public override async Task Update_where_using_hierarchy_derived(bool async) WHERE ( SELECT count(*)::int FROM "Animals" AS a - LEFT JOIN "Birds" AS b ON a."Id" = b."Id" - LEFT JOIN "Eagle" AS e ON a."Id" = e."Id" LEFT JOIN "Kiwi" AS k ON a."Id" = k."Id" WHERE a."CountryId" = 1 AND c."Id" = a."CountryId" AND k."Id" IS NOT NULL AND a."CountryId" > 0) > 0 """); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesNpgsqlTest.cs index d2e87b654..9516e6ec7 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPTInheritanceBulkUpdatesNpgsqlTest.cs @@ -63,20 +63,14 @@ public override async Task Update_base_type(bool async) // TODO: This over-complex SQL would get pruned after https://github.com/dotnet/efcore/issues/31083 AssertExecuteUpdateSql( """ -UPDATE "Animals" AS a +UPDATE "Animals" AS a0 SET "Name" = 'Animal' FROM ( - SELECT a0."Id", a0."CountryId", a0."Name", a0."Species", b0."EagleId", b0."IsFlightless", e0."Group", k0."FoundOn", CASE - WHEN k0."Id" IS NOT NULL THEN 'Kiwi' - WHEN e0."Id" IS NOT NULL THEN 'Eagle' - END AS "Discriminator" - FROM "Animals" AS a0 - LEFT JOIN "Birds" AS b0 ON a0."Id" = b0."Id" - LEFT JOIN "Eagle" AS e0 ON a0."Id" = e0."Id" - LEFT JOIN "Kiwi" AS k0 ON a0."Id" = k0."Id" - WHERE a0."Name" = 'Great spotted kiwi' -) AS t -WHERE a."Id" = t."Id" + SELECT a."Id" + FROM "Animals" AS a + WHERE a."Name" = 'Great spotted kiwi' +) AS s +WHERE a0."Id" = s."Id" """); } @@ -87,20 +81,17 @@ public override async Task Update_base_type_with_OfType(bool async) // TODO: This over-complex SQL would get pruned after https://github.com/dotnet/efcore/issues/31083 AssertExecuteUpdateSql( """ -UPDATE "Animals" AS a +UPDATE "Animals" AS a0 SET "Name" = 'NewBird' FROM "Birds" AS b, - "Kiwi" AS k, + "Kiwi" AS k0, ( - SELECT a0."Id", a0."CountryId", a0."Name", a0."Species", b0."EagleId", b0."IsFlightless", k0."FoundOn", CASE - WHEN k0."Id" IS NOT NULL THEN 'Kiwi' - END AS "Discriminator" - FROM "Animals" AS a0 - LEFT JOIN "Birds" AS b0 ON a0."Id" = b0."Id" - LEFT JOIN "Kiwi" AS k0 ON a0."Id" = k0."Id" - WHERE k0."Id" IS NOT NULL - ) AS t -WHERE a."Id" = t."Id" AND a."Id" = k."Id" AND a."Id" = b."Id" + SELECT a."Id" + FROM "Animals" AS a + LEFT JOIN "Kiwi" AS k ON a."Id" = k."Id" + WHERE k."Id" IS NOT NULL + ) AS s +WHERE a0."Id" = s."Id" AND a0."Id" = k0."Id" AND a0."Id" = b."Id" """); } @@ -171,9 +162,6 @@ public override async Task Update_where_using_hierarchy(bool async) WHERE ( SELECT count(*)::int FROM "Animals" AS a - LEFT JOIN "Birds" AS b ON a."Id" = b."Id" - LEFT JOIN "Eagle" AS e ON a."Id" = e."Id" - LEFT JOIN "Kiwi" AS k ON a."Id" = k."Id" WHERE c."Id" = a."CountryId" AND a."CountryId" > 0) > 0 """); } @@ -189,8 +177,6 @@ public override async Task Update_where_using_hierarchy_derived(bool async) WHERE ( SELECT count(*)::int FROM "Animals" AS a - LEFT JOIN "Birds" AS b ON a."Id" = b."Id" - LEFT JOIN "Eagle" AS e ON a."Id" = e."Id" LEFT JOIN "Kiwi" AS k ON a."Id" = k."Id" WHERE c."Id" = a."CountryId" AND k."Id" IS NOT NULL AND a."CountryId" > 0) > 0 """); diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index db8e54053..c02e580e1 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.Numerics; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL; @@ -452,8 +454,16 @@ protected class LogSequenceNumberType public NpgsqlLogSequenceNumber LogSequenceNumber { get; set; } } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => base.OnConfiguring(optionsBuilder.UseNpgsql(b => b.UseNetTopologySuite())); + protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; + + protected override IServiceCollection AddServices(IServiceCollection serviceCollection) + => serviceCollection.AddEntityFrameworkNpgsqlNetTopologySuite(); + + protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + { + new NpgsqlDbContextOptionsBuilder(builder).UseNetTopologySuite(); + return builder; + } static JsonTypesNpgsqlTest() { diff --git a/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs index b571ed37c..31735e9ff 100644 --- a/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs @@ -6,11 +6,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class MaterializationInterceptionNpgsqlTest(MaterializationInterceptionNpgsqlTest.MaterializationInterceptionNpgsqlFixture fixture) - : MaterializationInterceptionTestBase(fixture), - IClassFixture +public class MaterializationInterceptionNpgsqlTest : + MaterializationInterceptionTestBase { - public class SqlServerLibraryContext(DbContextOptions options) : LibraryContext(options) + public class NpgsqlLibraryContext(DbContextOptions options) : LibraryContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -23,27 +22,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } - public override LibraryContext CreateContext(IEnumerable interceptors, bool inject) - => new SqlServerLibraryContext(Fixture.CreateOptions(interceptors, inject)); - - public class MaterializationInterceptionNpgsqlFixture : SingletonInterceptorsFixtureBase - { - protected override string StoreName - => "MaterializationInterception"; - - protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; - - protected override IServiceCollection InjectInterceptors( - IServiceCollection serviceCollection, - IEnumerable injectedInterceptors) - => base.InjectInterceptors(serviceCollection.AddEntityFrameworkNpgsql(), injectedInterceptors); - - public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - { - new NpgsqlDbContextOptionsBuilder(base.AddOptions(builder)) - .ExecutionStrategy(d => new NpgsqlExecutionStrategy(d)); - return builder; - } - } + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; } diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs index 43139f7bb..18a8eadcc 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs @@ -14,6 +14,26 @@ public override void Can_get_active_provider() Assert.Equal("Npgsql.EntityFrameworkCore.PostgreSQL", ActiveProvider); } + [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/33056")] + public override void Can_apply_all_migrations() + => base.Can_apply_all_migrations(); + + [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/33056")] + public override void Can_apply_range_of_migrations() + => base.Can_apply_range_of_migrations(); + + [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/33056")] + public override void Can_revert_all_migrations() + => base.Can_revert_all_migrations(); + + [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/33056")] + public override void Can_revert_one_migrations() + => base.Can_revert_one_migrations(); + + [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/33056")] + public override Task Can_apply_all_migrations_async() + => base.Can_apply_all_migrations_async(); + [ConditionalFact] public async Task Empty_Migration_Creates_Database() { diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 338273052..0c558c943 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -1703,8 +1703,31 @@ public override async Task Alter_column_reset_collation() @"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""default"";"); } + public override async Task Convert_string_column_to_a_json_column_containing_reference() + { + var exception = + await Assert.ThrowsAsync(() => base.Convert_string_column_to_a_json_column_containing_reference()); + + Assert.Equal("42804", exception.SqlState); // column "Name" cannot be cast automatically to type jsonb + } + + public override async Task Convert_string_column_to_a_json_column_containing_required_reference() + { + var exception = + await Assert.ThrowsAsync(() => base.Convert_string_column_to_a_json_column_containing_required_reference()); + + Assert.Equal("42804", exception.SqlState); // column "Name" cannot be cast automatically to type jsonb + } + + public override async Task Convert_string_column_to_a_json_column_containing_collection() + { + var exception = + await Assert.ThrowsAsync(() => base.Convert_string_column_to_a_json_column_containing_collection()); + + Assert.Equal("42804", exception.SqlState); // column "Name" cannot be cast automatically to type jsonb + } + #pragma warning disable CS0618 - [Fact] public async Task Alter_column_change_default_column_collation() { await Test( diff --git a/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderGenericTest.cs b/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderGenericTest.cs new file mode 100644 index 000000000..8dc5b2348 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderGenericTest.cs @@ -0,0 +1,97 @@ +using Xunit.Sdk; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.ModelBuilding; + +#nullable enable + +public class NpgsqlModelBuilderGenericTest : NpgsqlModelBuilderTestBase +{ + public class NpgsqlGenericNonRelationship(NpgsqlModelBuilderFixture fixture) : NpgsqlNonRelationship(fixture) + { + // https://github.com/dotnet/efcore/issues/33059 + public override void Can_add_multiple_indexes() + => Assert.Throws(() => base.Can_add_multiple_indexes()); + + // PostgreSQL actually does support mapping multi-dimensional arrays, so no exception is thrown as expected + protected override void Mapping_throws_for_non_ignored_three_dimensional_array() + => Assert.Throws(() => base.Mapping_throws_for_non_ignored_three_dimensional_array()); + + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class NpgsqlGenericComplexType(NpgsqlModelBuilderFixture fixture) : NpgsqlComplexType(fixture) + { + // https://github.com/dotnet/efcore/issues/33059 + public override void Access_mode_can_be_overridden_at_entity_and_property_levels() + => Assert.Throws(() => base.Access_mode_can_be_overridden_at_entity_and_property_levels()); + + // https://github.com/dotnet/efcore/issues/33059 + public override void Complex_properties_not_discovered_by_convention() + => Assert.Throws(() => base.Complex_properties_not_discovered_by_convention()); + + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class NpgsqlGenericInheritance(NpgsqlModelBuilderFixture fixture) : NpgsqlInheritance(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class NpgsqlGenericOneToMany(NpgsqlModelBuilderFixture fixture) : NpgsqlOneToMany(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class NpgsqlGenericManyToOne(NpgsqlModelBuilderFixture fixture) : NpgsqlManyToOne(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class NpgsqlGenericOneToOne(NpgsqlModelBuilderFixture fixture) : NpgsqlOneToOne(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class NpgsqlGenericManyToMany(NpgsqlModelBuilderFixture fixture) : NpgsqlManyToMany(fixture) + { + // https://github.com/dotnet/efcore/issues/33059 + public override void Can_use_implicit_shared_type_with_default_name_and_implicit_relationships_as_join_entity() + => Assert.Throws( + () => base.Can_use_implicit_shared_type_with_default_name_and_implicit_relationships_as_join_entity()); + + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class NpgsqlGenericOwnedTypes(NpgsqlModelBuilderFixture fixture) : NpgsqlOwnedTypes(fixture) + { + // https://github.com/dotnet/efcore/issues/33059 + public override void Can_configure_chained_ownerships() + => Assert.Throws(() => base.Can_configure_chained_ownerships()); + + // https://github.com/dotnet/efcore/issues/33059 + public override void Can_configure_chained_ownerships_different_order() + => Assert.Throws(() => base.Can_configure_chained_ownerships_different_order()); + + // PostgreSQL stored procedures do not support result columns + public override void Can_use_sproc_mapping_with_owned_reference() + => Assert.Throws(() => base.Can_use_sproc_mapping_with_owned_reference()); + + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } +} diff --git a/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderTestBase.cs b/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderTestBase.cs new file mode 100644 index 000000000..95f42a658 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderTestBase.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.ModelBuilding; +using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +#nullable enable + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.ModelBuilding; + +public class NpgsqlModelBuilderTestBase : RelationalModelBuilderTest +{ + public abstract class NpgsqlNonRelationship(NpgsqlModelBuilderFixture fixture) + : RelationalNonRelationshipTestBase(fixture), IClassFixture; + + public abstract class NpgsqlComplexType(NpgsqlModelBuilderFixture fixture) + : RelationalComplexTypeTestBase(fixture), IClassFixture; + + public abstract class NpgsqlInheritance(NpgsqlModelBuilderFixture fixture) + : RelationalInheritanceTestBase(fixture), IClassFixture; + + public abstract class NpgsqlOneToMany(NpgsqlModelBuilderFixture fixture) + : RelationalOneToManyTestBase(fixture), IClassFixture; + + public abstract class NpgsqlManyToOne(NpgsqlModelBuilderFixture fixture) + : RelationalManyToOneTestBase(fixture), IClassFixture; + + public abstract class NpgsqlOneToOne(NpgsqlModelBuilderFixture fixture) + : RelationalOneToOneTestBase(fixture), IClassFixture; + + public abstract class NpgsqlManyToMany(NpgsqlModelBuilderFixture fixture) + : RelationalManyToManyTestBase(fixture), IClassFixture; + + public abstract class NpgsqlOwnedTypes(NpgsqlModelBuilderFixture fixture) + : RelationalOwnedTypesTestBase(fixture), IClassFixture; + + public class NpgsqlModelBuilderFixture : RelationalModelBuilderFixture + { + public override TestHelpers TestHelpers + => NpgsqlTestHelpers.Instance; + } +} diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs index 3f5aa7ae5..b51dea3bc 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs @@ -5,6 +5,7 @@ public class NpgsqlComplianceTest : RelationalComplianceTestBase protected override ICollection IgnoredTestBases { get; } = new HashSet { // Not implemented + typeof(CompiledModelTestBase), typeof(CompiledModelRelationalTestBase), // ##3087 typeof(FromSqlSprocQueryTestBase<>), typeof(UdfDbFunctionTestBase<>), typeof(UpdateSqlGeneratorTestBase), diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs index 5efd47db1..2c9d7e992 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs @@ -208,7 +208,7 @@ private static async Task Creates_physical_database_and_schema_test( Assert.Equal(14, columns.Length); Assert.Equal( - [ + new[] { "Blogs.AndChew (bytea)", "Blogs.AndRow (bytea)", "Blogs.Cheese (text)", @@ -223,7 +223,7 @@ private static async Task Creates_physical_database_and_schema_test( "Blogs.TheGu (uuid)", "Blogs.ToEat (smallint)", "Blogs.WayRound (bigint)" - ], + }, columns); } diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs new file mode 100644 index 000000000..dd80b867a --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs @@ -0,0 +1,19 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class AdHocAdvancedMappingsQueryNpgsqlTest : AdHocAdvancedMappingsQueryRelationalTestBase +{ + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + // Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported. + public override Task Query_generates_correct_datetime2_parameter_definition(int? fractionalSeconds, string postfix) + => Assert.ThrowsAsync( + () => base.Query_generates_correct_datetime2_parameter_definition(fractionalSeconds, postfix)); + + // Cannot write DateTimeOffset with Offset=10:00:00 to PostgreSQL type 'timestamp with time zone', only offset 0 (UTC) is supported. + public override Task Query_generates_correct_datetimeoffset_parameter_definition(int? fractionalSeconds, string postfix) + => Assert.ThrowsAsync( + () => base.Query_generates_correct_datetime2_parameter_definition(fractionalSeconds, postfix)); +} diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs similarity index 53% rename from test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs rename to test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs index a26bb18fd..edb0f85fc 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryAdHocNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs @@ -1,106 +1,12 @@ -using Microsoft.EntityFrameworkCore.Diagnostics.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Diagnostics.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class JsonQueryAdHocNpgsqlTest : JsonQueryAdHocTestBase +public class AdHocJsonQueryNpgsqlTest : AdHocJsonQueryTestBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; - [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/issues/32235")] - public override Task Junk_in_json_basic_tracking(bool async) - => base.Junk_in_json_basic_tracking(async); - - [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/issues/32235")] - public override Task Junk_in_json_basic_no_tracking(bool async) - => base.Junk_in_json_basic_no_tracking(async); - - [ConditionalTheory, MemberData(nameof(IsAsyncData))] - public virtual async Task Json_predicate_on_bytea(bool async) - { - var contextFactory = await InitializeAsync( - seed: context => - { - context.Entities.AddRange( - new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 3] } }, - new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 4] } }); - context.SaveChanges(); - }); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Entities.Where(x => x.JsonEntity.Bytea == new byte[] { 1, 2, 4 }); - - var result = async - ? await query.SingleAsync() - : query.Single(); - - Assert.Equal(2, result.Id); - - AssertSql( - """ -SELECT e."Id", e."JsonEntity" -FROM "Entities" AS e -WHERE (decode(e."JsonEntity" ->> 'Bytea', 'base64')) = BYTEA E'\\x010204' -LIMIT 2 -"""); - } - } - - [ConditionalTheory, MemberData(nameof(IsAsyncData))] - public virtual async Task Json_predicate_on_interval(bool async) - { - var contextFactory = await InitializeAsync( - seed: context => - { - context.Entities.AddRange( - new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Interval = new TimeSpan(1, 2, 3, 4, 123, 456) } }, - new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Interval = new TimeSpan(2, 2, 3, 4, 123, 456) } }); - context.SaveChanges(); - }); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Entities.Where(x => x.JsonEntity.Interval == new TimeSpan(2, 2, 3, 4, 123, 456)); - - var result = async - ? await query.SingleAsync() - : query.Single(); - - Assert.Equal(2, result.Id); - - AssertSql( - """ -SELECT e."Id", e."JsonEntity" -FROM "Entities" AS e -WHERE (CAST(e."JsonEntity" ->> 'Interval' AS interval)) = INTERVAL '2 02:03:04.123456' -LIMIT 2 -"""); - } - } - - protected class TypesDbContext(DbContextOptions options) : DbContext(options) - { - public DbSet Entities { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity().OwnsOne(b => b.JsonEntity).ToJson(); - } - - public class TypesContainerEntity - { - public int Id { get; set; } - public TypesJsonEntity JsonEntity { get; set; } - } - - public class TypesJsonEntity - { - public byte[] Bytea { get; set; } - public TimeSpan Interval { get; set; } - } - protected override void Seed29219(MyContext29219 ctx) { var entity1 = new MyEntity29219 @@ -227,6 +133,14 @@ protected override void SeedJunkInJson(MyContextJunkInJson ctx) '{{"Name":"r1","JunkCollection":[{{"Foo":"junk value"}}],"JunkReference":{{"Something":"SomeValue" }},"Number":1.5,"NestedCollection":[{{"DoB":"2000-02-01T00:00:00","JunkReference":{{"Something":"SomeValue"}}}},{{"DoB":"2000-02-02T00:00:00"}}],"NestedReference":{{"DoB":"2000-01-01T00:00:00"}}}}', '{{"MyBool":true,"JunkCollection":[{{"Foo":"junk value"}}],"Name":"r1 ctor","JunkReference":{{"Something":"SomeValue" }},"NestedCollection":[{{"DoB":"2001-02-01T00:00:00"}},{{"DoB":"2001-02-02T00:00:00"}}],"NestedReference":{{"JunkCollection":[{{"Foo":"junk value"}}],"DoB":"2001-01-01T00:00:00"}}}}', 1) +"""); + + protected override void SeedTrickyBuffering(MyContextTrickyBuffering ctx) + => ctx.Database.ExecuteSqlRaw( + """ +INSERT INTO "Entities" ("Reference", "Id") +VALUES( +'{{"Name": "r1", "Number": 7, "JunkReference":{{"Something": "SomeValue" }}, "JunkCollection": [{{"Foo": "junk value"}}], "NestedReference": {{"DoB": "2000-01-01T00:00:00Z"}}, "NestedCollection": [{{"DoB": "2000-02-01T00:00:00Z", "JunkReference": {{"Something": "SomeValue"}}}}, {{"DoB": "2000-02-02T00:00:00Z"}}]}}',1) """); protected override void SeedShadowProperties(MyContextShadowProperties ctx) @@ -261,200 +175,90 @@ protected override void SeedNotICollection(MyContextNotICollection ctx) """); } - #region EnumLegacyValues - [ConditionalTheory, MemberData(nameof(IsAsyncData))] - public virtual async Task Read_enum_property_with_legacy_values(bool async) + public virtual async Task Json_predicate_on_bytea(bool async) { - var contextFactory = await InitializeAsync( - seed: SeedEnumLegacyValues); + var contextFactory = await InitializeAsync( + seed: context => + { + context.Entities.AddRange( + new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 3] } }, + new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 4] } }); + context.SaveChanges(); + }); using (var context = contextFactory.CreateContext()) { - var query = context.Entities.Select( - x => new - { - x.Reference.IntEnum, - x.Reference.ByteEnum, - x.Reference.LongEnum, - x.Reference.NullableEnum - }); - - var exception = async - ? await (Assert.ThrowsAsync(() => query.ToListAsync())) - : Assert.Throws(() => query.ToList()); - - // Conversion failed when converting the text value '...' to data type int - Assert.Equal("22P02", exception.SqlState); - } - } + var query = context.Entities.Where(x => x.JsonEntity.Bytea == new byte[] { 1, 2, 4 }); - [ConditionalTheory, MemberData(nameof(IsAsyncData))] - public virtual async Task Read_json_entity_with_enum_properties_with_legacy_values(bool async) - { - var contextFactory = await InitializeAsync( - seed: SeedEnumLegacyValues, - shouldLogCategory: c => c == DbLoggerCategory.Query.Name); + var result = async + ? await query.SingleAsync() + : query.Single(); - using (var context = contextFactory.CreateContext()) - { - var query = context.Entities.Select(x => x.Reference).AsNoTracking(); + Assert.Equal(2, result.Id); - var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Single(result); - Assert.Equal(ByteEnumLegacyValues.Redmond, result[0].ByteEnum); - Assert.Equal(IntEnumLegacyValues.Foo, result[0].IntEnum); - Assert.Equal(LongEnumLegacyValues.Three, result[0].LongEnum); - Assert.Equal(ULongEnumLegacyValues.Three, result[0].ULongEnum); - Assert.Equal(IntEnumLegacyValues.Bar, result[0].NullableEnum); + AssertSql( + """ +SELECT e."Id", e."JsonEntity" +FROM "Entities" AS e +WHERE (decode(e."JsonEntity" ->> 'Bytea', 'base64')) = BYTEA E'\\x010204' +LIMIT 2 +"""); } - - var testLogger = new TestLogger(); - Assert.Single( - ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ByteEnumLegacyValues)))); - Assert.Single( - ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(IntEnumLegacyValues)))); - Assert.Single( - ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(LongEnumLegacyValues)))); - Assert.Single( - ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ULongEnumLegacyValues)))); } [ConditionalTheory, MemberData(nameof(IsAsyncData))] - public virtual async Task Read_json_entity_collection_with_enum_properties_with_legacy_values(bool async) + public virtual async Task Json_predicate_on_interval(bool async) { - var contextFactory = await InitializeAsync( - seed: SeedEnumLegacyValues, - shouldLogCategory: c => c == DbLoggerCategory.Query.Name); + var contextFactory = await InitializeAsync( + seed: context => + { + context.Entities.AddRange( + new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Interval = new TimeSpan(1, 2, 3, 4, 123, 456) } }, + new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Interval = new TimeSpan(2, 2, 3, 4, 123, 456) } }); + context.SaveChanges(); + }); using (var context = contextFactory.CreateContext()) { - var query = context.Entities.Select(x => x.Collection).AsNoTracking(); + var query = context.Entities.Where(x => x.JsonEntity.Interval == new TimeSpan(2, 2, 3, 4, 123, 456)); var result = async - ? await query.ToListAsync() - : query.ToList(); - - Assert.Single(result); - Assert.Equal(2, result[0].Count); - Assert.Equal(ByteEnumLegacyValues.Bellevue, result[0][0].ByteEnum); - Assert.Equal(IntEnumLegacyValues.Foo, result[0][0].IntEnum); - Assert.Equal(LongEnumLegacyValues.One, result[0][0].LongEnum); - Assert.Equal(ULongEnumLegacyValues.One, result[0][0].ULongEnum); - Assert.Equal(IntEnumLegacyValues.Bar, result[0][0].NullableEnum); - Assert.Equal(ByteEnumLegacyValues.Seattle, result[0][1].ByteEnum); - Assert.Equal(IntEnumLegacyValues.Baz, result[0][1].IntEnum); - Assert.Equal(LongEnumLegacyValues.Two, result[0][1].LongEnum); - Assert.Equal(ULongEnumLegacyValues.Two, result[0][1].ULongEnum); - Assert.Null(result[0][1].NullableEnum); - } + ? await query.SingleAsync() + : query.Single(); - var testLogger = new TestLogger(); - Assert.Single( - ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ByteEnumLegacyValues)))); - Assert.Single( - ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(IntEnumLegacyValues)))); - Assert.Single( - ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(LongEnumLegacyValues)))); - Assert.Single( - ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ULongEnumLegacyValues)))); - } + Assert.Equal(2, result.Id); - private void SeedEnumLegacyValues(MyContextEnumLegacyValues ctx) - => ctx.Database.ExecuteSqlRaw( - """ -INSERT INTO "Entities" ("Collection", "Reference", "Id", "Name") -VALUES( -'[{{"ByteEnum":"Bellevue","IntEnum":"Foo","LongEnum":"One","ULongEnum":"One","Name":"e1_c1","NullableEnum":"Bar"}},{{"ByteEnum":"Seattle","IntEnum":"Baz","LongEnum":"Two","ULongEnum":"Two","Name":"e1_c2","NullableEnum":null}}]', -'{{"ByteEnum":"Redmond","IntEnum":"Foo","LongEnum":"Three","ULongEnum":"Three","Name":"e1_r","NullableEnum":"Bar"}}', -1, -'e1') + AssertSql( + """ +SELECT e."Id", e."JsonEntity" +FROM "Entities" AS e +WHERE (CAST(e."JsonEntity" ->> 'Interval' AS interval)) = INTERVAL '2 02:03:04.123456' +LIMIT 2 """); - - private class MyContextEnumLegacyValues(DbContextOptions options) : DbContext( - (new DbContextOptionsBuilder(options)).ConfigureWarnings(b => b.Log(CoreEventId.StringEnumValueInJson)).Options) - { - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public DbSet Entities { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne(x => x.Reference, b => b.ToJson()); - modelBuilder.Entity().OwnsMany(x => x.Collection, b => b.ToJson()); } } - private class MyEntityEnumLegacyValues - { - public int Id { get; set; } - public string Name { get; set; } - - public MyJsonEntityEnumLegacyValues Reference { get; set; } - public List Collection { get; set; } - } - - private class MyJsonEntityEnumLegacyValues - { - public string Name { get; set; } - - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public IntEnumLegacyValues IntEnum { get; set; } - - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public ByteEnumLegacyValues ByteEnum { get; set; } - - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public LongEnumLegacyValues LongEnum { get; set; } - - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public ULongEnumLegacyValues ULongEnum { get; set; } - - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public IntEnumLegacyValues? NullableEnum { get; set; } - } - - private enum IntEnumLegacyValues + protected class TypesDbContext(DbContextOptions options) : DbContext(options) { - Foo = int.MinValue, - Bar, - Baz = int.MaxValue, - } + public DbSet Entities { get; set; } - private enum ByteEnumLegacyValues : byte - { - Seattle, - Redmond, - Bellevue = 255, + protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.Entity().OwnsOne(b => b.JsonEntity).ToJson(); } - private enum LongEnumLegacyValues : long + public class TypesContainerEntity { - One = long.MinValue, - Two = 1, - Three = long.MaxValue, + public int Id { get; set; } + public TypesJsonEntity JsonEntity { get; set; } } - private enum ULongEnumLegacyValues : ulong + public class TypesJsonEntity { - One = ulong.MinValue, - Two = 1, - Three = ulong.MaxValue, + public byte[] Bytea { get; set; } + public TimeSpan Interval { get; set; } } - #endregion - protected void AssertSql(params string[] expected) => TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ManyToManyHeterogeneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocManyToManyQueryNpgsqlTest.cs similarity index 68% rename from test/EFCore.PG.FunctionalTests/Query/ManyToManyHeterogeneousQueryNpgsqlTest.cs rename to test/EFCore.PG.FunctionalTests/Query/AdHocManyToManyQueryNpgsqlTest.cs index 6f13329a9..eb60e4c60 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ManyToManyHeterogeneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocManyToManyQueryNpgsqlTest.cs @@ -2,7 +2,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class ManyToManyHeterogeneousQuerySqlServerTest : ManyToManyHeterogeneousQueryRelationalTestBase +public class AdHocManyToManyQueryNpgsqlTest : AdHocManyToManyQueryRelationalTestBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs new file mode 100644 index 000000000..0e1b6a5d4 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs @@ -0,0 +1,38 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class AdHocMiscellaneousQueryNpgsqlTest : AdHocMiscellaneousQueryRelationalTestBase +{ + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + protected override void Seed2951(Context2951 context) + => context.Database.ExecuteSqlRaw( + """ +CREATE TABLE "ZeroKey" ("Id" int); +INSERT INTO "ZeroKey" VALUES (NULL) +"""); + + // https://github.com/dotnet/efcore/pull/32542/files#r1485633978 + public override Task Nested_queries_does_not_cause_concurrency_exception_sync(bool tracking) + => Assert.ThrowsAsync( + () => base.Nested_queries_does_not_cause_concurrency_exception_sync(tracking)); + + // https://github.com/dotnet/efcore/pull/32542/files#r1485633978 + public override Task Select_nested_projection() + => Assert.ThrowsAsync( + () => base.Select_nested_projection()); + + // Writes DateTime with Kind=Unspecified to timestamptz + public override Task SelectMany_where_Select(bool async) + => Task.CompletedTask; + + // 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/AdHocNavigationsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs new file mode 100644 index 000000000..dbbd481b3 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs @@ -0,0 +1,17 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class AdHocNavigationsQueryNpgsqlTest : AdHocNavigationsQueryRelationalTestBase +{ + // Cannot write DateTime with Kind=Local to PostgreSQL type 'timestamp with time zone', only UTC is supported. + public override Task Reference_include_on_derived_type_with_sibling_works() + => Assert.ThrowsAsync(() => base.Reference_include_on_derived_type_with_sibling_works()); + + // https://github.com/dotnet/efcore/pull/32542/files#r1485618022 + public override Task Nested_include_queries_do_not_populate_navigation_twice() + => Assert.ThrowsAsync(() => base.Nested_include_queries_do_not_populate_navigation_twice()); + + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; +} diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocQueryFiltersQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocQueryFiltersQueryNpgsqlTest.cs new file mode 100644 index 000000000..a6c0b0c17 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocQueryFiltersQueryNpgsqlTest.cs @@ -0,0 +1,9 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class AdHocQueryFiltersQueryNpgsqlTest : AdHocQueryFiltersQueryRelationalTestBase +{ + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; +} diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocQuerySplittingQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocQuerySplittingQueryNpgsqlTest.cs new file mode 100644 index 000000000..927fe2ff8 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocQuerySplittingQueryNpgsqlTest.cs @@ -0,0 +1,40 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class AdHocQuerySplittingQueryNpgsqlTest : AdHocQuerySplittingQueryTestBase +{ + protected override DbContextOptionsBuilder SetQuerySplittingBehavior( + DbContextOptionsBuilder optionsBuilder, + QuerySplittingBehavior splittingBehavior) + { + new NpgsqlDbContextOptionsBuilder(optionsBuilder).UseQuerySplittingBehavior(splittingBehavior); + + return optionsBuilder; + } + + protected override DbContextOptionsBuilder ClearQuerySplittingBehavior(DbContextOptionsBuilder optionsBuilder) + { + var extension = optionsBuilder.Options.FindExtension(); + if (extension == null) + { + extension = new NpgsqlOptionsExtension(); + } + else + { + _querySplittingBehaviorFieldInfo.SetValue(extension, null); + } + + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + + return optionsBuilder; + } + + private static readonly FieldInfo _querySplittingBehaviorFieldInfo = + typeof(RelationalOptionsExtension).GetField("_querySplittingBehavior", BindingFlags.NonPublic | BindingFlags.Instance); + + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; +} diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs index dd35fc5f4..1a34cb9d4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs @@ -24,7 +24,7 @@ public void Roundtrip() using var ctx = CreateContext(); var x = ctx.SomeEntities.Single(e => e.Id == 1); - Assert.Equal([3, 4], x.IntArray); + Assert.Equal(new[] { 3, 4 }, x.IntArray); Assert.Equal([3, 4], x.IntList); Assert.Equal([3, 4, null], x.NullableIntArray); Assert.Equal( diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs index 6ec76cd82..1771836d8 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs @@ -10,9 +10,9 @@ public ComplexNavigationsQueryNpgsqlTest(ComplexNavigationsQueryNpgsqlFixture fi Fixture.TestSqlLoggerFactory.Clear(); } - [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/issues/26353")] - public override Task Subquery_with_Distinct_Skip_FirstOrDefault_without_OrderBy(bool async) - => base.Subquery_with_Distinct_Skip_FirstOrDefault_without_OrderBy(async); + // https://github.com/dotnet/efcore/pull/33060 + public override Task Max_in_multi_level_nested_subquery(bool async) + => Assert.ThrowsAsync(() => base.Max_in_multi_level_nested_subquery(async)); public override async Task Join_with_result_selector_returning_queryable_throws_validation_error(bool async) => await Assert.ThrowsAsync( diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs index 1d4e555bd..d490537cc 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs @@ -18,7 +18,7 @@ public override async Task Filter_on_property_inside_complex_type(bool async) AssertSql( """ -SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE c."ShippingAddress_ZipCode" = 7728 """); @@ -30,7 +30,7 @@ public override async Task Filter_on_property_inside_nested_complex_type(bool as AssertSql( """ -SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE c."ShippingAddress_Country_Code" = 'DE' """); @@ -44,14 +44,14 @@ public override async Task Filter_on_property_inside_complex_type_after_subquery """ @__p_0='1' -SELECT DISTINCT t."Id", t."Name", t."BillingAddress_AddressLine1", t."BillingAddress_AddressLine2", t."BillingAddress_ZipCode", t."BillingAddress_Country_Code", t."BillingAddress_Country_FullName", t."ShippingAddress_AddressLine1", t."ShippingAddress_AddressLine2", t."ShippingAddress_ZipCode", t."ShippingAddress_Country_Code", t."ShippingAddress_Country_FullName" +SELECT DISTINCT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM ( - SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c ORDER BY c."Id" NULLS FIRST OFFSET @__p_0 -) AS t -WHERE t."ShippingAddress_ZipCode" = 7728 +) AS c0 +WHERE c0."ShippingAddress_ZipCode" = 7728 """); } @@ -63,14 +63,14 @@ public override async Task Filter_on_property_inside_nested_complex_type_after_s """ @__p_0='1' -SELECT DISTINCT t."Id", t."Name", t."BillingAddress_AddressLine1", t."BillingAddress_AddressLine2", t."BillingAddress_ZipCode", t."BillingAddress_Country_Code", t."BillingAddress_Country_FullName", t."ShippingAddress_AddressLine1", t."ShippingAddress_AddressLine2", t."ShippingAddress_ZipCode", t."ShippingAddress_Country_Code", t."ShippingAddress_Country_FullName" +SELECT DISTINCT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM ( - SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c ORDER BY c."Id" NULLS FIRST OFFSET @__p_0 -) AS t -WHERE t."ShippingAddress_Country_Code" = 'DE' +) AS c0 +WHERE c0."ShippingAddress_Country_Code" = 'DE' """); } @@ -80,7 +80,7 @@ public override async Task Filter_on_required_property_inside_required_complex_t AssertSql( """ -SELECT c."Id", c."OptionalCustomerId", c."RequiredCustomerId", c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName", c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName" +SELECT c."Id", c."OptionalCustomerId", c."RequiredCustomerId", c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName", c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_Tags", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName" FROM "CustomerGroup" AS c LEFT JOIN "Customer" AS c0 ON c."OptionalCustomerId" = c0."Id" INNER JOIN "Customer" AS c1 ON c."RequiredCustomerId" = c1."Id" @@ -94,7 +94,7 @@ public override async Task Filter_on_required_property_inside_required_complex_t AssertSql( """ -SELECT c."Id", c."OptionalCustomerId", c."RequiredCustomerId", c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName", c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" +SELECT c."Id", c."OptionalCustomerId", c."RequiredCustomerId", c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_Tags", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName", c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM "CustomerGroup" AS c INNER JOIN "Customer" AS c0 ON c."RequiredCustomerId" = c0."Id" LEFT JOIN "Customer" AS c1 ON c."OptionalCustomerId" = c1."Id" @@ -118,7 +118,7 @@ public override async Task Project_complex_type_via_required_navigation(bool asy AssertSql( """ -SELECT c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" +SELECT c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM "CustomerGroup" AS c INNER JOIN "Customer" AS c0 ON c."RequiredCustomerId" = c0."Id" """); @@ -132,13 +132,13 @@ public override async Task Load_complex_type_after_subquery_on_entity_type(bool """ @__p_0='1' -SELECT DISTINCT t."Id", t."Name", t."BillingAddress_AddressLine1", t."BillingAddress_AddressLine2", t."BillingAddress_ZipCode", t."BillingAddress_Country_Code", t."BillingAddress_Country_FullName", t."ShippingAddress_AddressLine1", t."ShippingAddress_AddressLine2", t."ShippingAddress_ZipCode", t."ShippingAddress_Country_Code", t."ShippingAddress_Country_FullName" +SELECT DISTINCT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM ( - SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c ORDER BY c."Id" NULLS FIRST OFFSET @__p_0 -) AS t +) AS c0 """); } @@ -148,7 +148,7 @@ public override async Task Select_complex_type(bool async) AssertSql( """ -SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c """); } @@ -181,7 +181,7 @@ public override async Task Select_complex_type_Where(bool async) AssertSql( """ -SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE c."ShippingAddress_ZipCode" = 7728 """); @@ -193,7 +193,7 @@ public override async Task Select_complex_type_Distinct(bool async) AssertSql( """ -SELECT DISTINCT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT DISTINCT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c """); } @@ -204,9 +204,9 @@ public override async Task Complex_type_equals_complex_type(bool async) AssertSql( """ -SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c -WHERE c."ShippingAddress_AddressLine1" = c."BillingAddress_AddressLine1" AND (c."ShippingAddress_AddressLine2" = c."BillingAddress_AddressLine2" OR (c."ShippingAddress_AddressLine2" IS NULL AND c."BillingAddress_AddressLine2" IS NULL)) AND c."ShippingAddress_ZipCode" = c."BillingAddress_ZipCode" +WHERE c."ShippingAddress_AddressLine1" = c."BillingAddress_AddressLine1" AND (c."ShippingAddress_AddressLine2" = c."BillingAddress_AddressLine2" OR (c."ShippingAddress_AddressLine2" IS NULL AND c."BillingAddress_AddressLine2" IS NULL)) AND c."ShippingAddress_Tags" = c."BillingAddress_Tags" AND c."ShippingAddress_ZipCode" = c."BillingAddress_ZipCode" """); } @@ -216,9 +216,9 @@ public override async Task Complex_type_equals_constant(bool async) AssertSql( """ -SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c -WHERE c."ShippingAddress_AddressLine1" = '804 S. Lakeshore Road' AND c."ShippingAddress_AddressLine2" IS NULL AND c."ShippingAddress_ZipCode" = 38654 AND c."ShippingAddress_Country_Code" = 'US' AND c."ShippingAddress_Country_FullName" = 'United States' +WHERE c."ShippingAddress_AddressLine1" = '804 S. Lakeshore Road' AND c."ShippingAddress_AddressLine2" IS NULL AND c."ShippingAddress_Tags" = ARRAY['foo','bar']::text[] AND c."ShippingAddress_ZipCode" = 38654 AND c."ShippingAddress_Country_Code" = 'US' AND c."ShippingAddress_Country_FullName" = 'United States' """); } @@ -229,13 +229,14 @@ public override async Task Complex_type_equals_parameter(bool async) AssertSql( """ @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' +@__entity_equality_address_0_Tags={ 'foo', 'bar' } (DbType = Object) @__entity_equality_address_0_ZipCode='38654' (Nullable = true) @__entity_equality_address_0_Code='US' @__entity_equality_address_0_FullName='United States' -SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c -WHERE c."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c."ShippingAddress_AddressLine2" IS NULL AND c."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND c."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName +WHERE c."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c."ShippingAddress_AddressLine2" IS NULL AND c."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND c."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName """); } @@ -260,16 +261,17 @@ public override async Task Contains_over_complex_type(bool async) AssertSql( """ @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' +@__entity_equality_address_0_Tags={ 'foo', 'bar' } (DbType = Object) @__entity_equality_address_0_ZipCode='38654' (Nullable = true) @__entity_equality_address_0_Code='US' @__entity_equality_address_0_FullName='United States' -SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE EXISTS ( SELECT 1 FROM "Customer" AS c0 - WHERE c0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c0."ShippingAddress_AddressLine2" IS NULL AND c0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND c0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName) + WHERE c0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c0."ShippingAddress_AddressLine2" IS NULL AND c0."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND c0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName) """); } @@ -279,11 +281,11 @@ public override async Task Concat_complex_type(bool async) AssertSql( """ -SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE c."Id" = 1 UNION ALL -SELECT c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" +SELECT c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM "Customer" AS c0 WHERE c0."Id" = 2 """); @@ -295,11 +297,11 @@ public override async Task Concat_entity_type_containing_complex_property(bool a AssertSql( """ -SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE c."Id" = 1 UNION ALL -SELECT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" +SELECT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM "Customer" AS c0 WHERE c0."Id" = 2 """); @@ -311,11 +313,11 @@ public override async Task Union_entity_type_containing_complex_property(bool as AssertSql( """ -SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE c."Id" = 1 UNION -SELECT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" +SELECT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM "Customer" AS c0 WHERE c0."Id" = 2 """); @@ -327,11 +329,11 @@ public override async Task Union_complex_type(bool async) AssertSql( """ -SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE c."Id" = 1 UNION -SELECT c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" +SELECT c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" FROM "Customer" AS c0 WHERE c0."Id" = 2 """); @@ -411,14 +413,14 @@ public override async Task Filter_on_property_inside_struct_complex_type_after_s """ @__p_0='1' -SELECT DISTINCT t."Id", t."Name", t."BillingAddress_AddressLine1", t."BillingAddress_AddressLine2", t."BillingAddress_ZipCode", t."BillingAddress_Country_Code", t."BillingAddress_Country_FullName", t."ShippingAddress_AddressLine1", t."ShippingAddress_AddressLine2", t."ShippingAddress_ZipCode", t."ShippingAddress_Country_Code", t."ShippingAddress_Country_FullName" +SELECT DISTINCT v0."Id", v0."Name", v0."BillingAddress_AddressLine1", v0."BillingAddress_AddressLine2", v0."BillingAddress_ZipCode", v0."BillingAddress_Country_Code", v0."BillingAddress_Country_FullName", v0."ShippingAddress_AddressLine1", v0."ShippingAddress_AddressLine2", v0."ShippingAddress_ZipCode", v0."ShippingAddress_Country_Code", v0."ShippingAddress_Country_FullName" FROM ( SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName" FROM "ValuedCustomer" AS v ORDER BY v."Id" NULLS FIRST OFFSET @__p_0 -) AS t -WHERE t."ShippingAddress_ZipCode" = 7728 +) AS v0 +WHERE v0."ShippingAddress_ZipCode" = 7728 """); } @@ -430,14 +432,14 @@ public override async Task Filter_on_property_inside_nested_struct_complex_type_ """ @__p_0='1' -SELECT DISTINCT t."Id", t."Name", t."BillingAddress_AddressLine1", t."BillingAddress_AddressLine2", t."BillingAddress_ZipCode", t."BillingAddress_Country_Code", t."BillingAddress_Country_FullName", t."ShippingAddress_AddressLine1", t."ShippingAddress_AddressLine2", t."ShippingAddress_ZipCode", t."ShippingAddress_Country_Code", t."ShippingAddress_Country_FullName" +SELECT DISTINCT v0."Id", v0."Name", v0."BillingAddress_AddressLine1", v0."BillingAddress_AddressLine2", v0."BillingAddress_ZipCode", v0."BillingAddress_Country_Code", v0."BillingAddress_Country_FullName", v0."ShippingAddress_AddressLine1", v0."ShippingAddress_AddressLine2", v0."ShippingAddress_ZipCode", v0."ShippingAddress_Country_Code", v0."ShippingAddress_Country_FullName" FROM ( SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName" FROM "ValuedCustomer" AS v ORDER BY v."Id" NULLS FIRST OFFSET @__p_0 -) AS t -WHERE t."ShippingAddress_Country_Code" = 'DE' +) AS v0 +WHERE v0."ShippingAddress_Country_Code" = 'DE' """); } @@ -500,13 +502,13 @@ public override async Task Load_struct_complex_type_after_subquery_on_entity_typ """ @__p_0='1' -SELECT DISTINCT t."Id", t."Name", t."BillingAddress_AddressLine1", t."BillingAddress_AddressLine2", t."BillingAddress_ZipCode", t."BillingAddress_Country_Code", t."BillingAddress_Country_FullName", t."ShippingAddress_AddressLine1", t."ShippingAddress_AddressLine2", t."ShippingAddress_ZipCode", t."ShippingAddress_Country_Code", t."ShippingAddress_Country_FullName" +SELECT DISTINCT v0."Id", v0."Name", v0."BillingAddress_AddressLine1", v0."BillingAddress_AddressLine2", v0."BillingAddress_ZipCode", v0."BillingAddress_Country_Code", v0."BillingAddress_Country_FullName", v0."ShippingAddress_AddressLine1", v0."ShippingAddress_AddressLine2", v0."ShippingAddress_ZipCode", v0."ShippingAddress_Country_Code", v0."ShippingAddress_Country_FullName" FROM ( SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName" FROM "ValuedCustomer" AS v ORDER BY v."Id" NULLS FIRST OFFSET @__p_0 -) AS t +) AS v0 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs index 141f111d5..b83d9748a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs @@ -18,17 +18,17 @@ public override async Task Whats_new_2021_sample_3(bool async) AssertSql( """ SELECT ( - SELECT p1."LastName" - FROM "Person" AS p1 - WHERE p1."MiddleInitial" = 'Q' AND p1."Age" = 20 AND (p."LastName" = p1."LastName" OR (p."LastName" IS NULL AND p1."LastName" IS NULL)) + SELECT p0."LastName" + FROM "Person" AS p0 + WHERE p0."MiddleInitial" = 'Q' AND p0."Age" = 20 AND (p."LastName" = p0."LastName" OR (p."LastName" IS NULL AND p0."LastName" IS NULL)) LIMIT 1) FROM "Person" AS p WHERE p."MiddleInitial" = 'Q' AND p."Age" = 20 GROUP BY p."LastName" ORDER BY length(( - SELECT p1."LastName" - FROM "Person" AS p1 - WHERE p1."MiddleInitial" = 'Q' AND p1."Age" = 20 AND (p."LastName" = p1."LastName" OR (p."LastName" IS NULL AND p1."LastName" IS NULL)) + SELECT p0."LastName" + FROM "Person" AS p0 + WHERE p0."MiddleInitial" = 'Q' AND p0."Age" = 20 AND (p."LastName" = p0."LastName" OR (p."LastName" IS NULL AND p0."LastName" IS NULL)) LIMIT 1))::int NULLS FIRST """); } @@ -40,16 +40,16 @@ public override async Task Whats_new_2021_sample_5(bool async) AssertSql( """ SELECT ( - SELECT p1."LastName" - FROM "Person" AS p1 - WHERE p."FirstName" = p1."FirstName" OR (p."FirstName" IS NULL AND p1."FirstName" IS NULL) + SELECT p0."LastName" + FROM "Person" AS p0 + WHERE p."FirstName" = p0."FirstName" OR (p."FirstName" IS NULL AND p0."FirstName" IS NULL) LIMIT 1) FROM "Person" AS p GROUP BY p."FirstName" ORDER BY ( - SELECT p1."LastName" - FROM "Person" AS p1 - WHERE p."FirstName" = p1."FirstName" OR (p."FirstName" IS NULL AND p1."FirstName" IS NULL) + SELECT p0."LastName" + FROM "Person" AS p0 + WHERE p."FirstName" = p0."FirstName" OR (p."FirstName" IS NULL AND p0."FirstName" IS NULL) LIMIT 1) NULLS FIRST """); } @@ -61,25 +61,21 @@ public override async Task Whats_new_2021_sample_6(bool async) AssertSql( """ SELECT ( - SELECT p1."MiddleInitial" - FROM "Person" AS p1 - WHERE p1."Age" = 20 AND p."Id" = p1."Id" + SELECT p0."MiddleInitial" + FROM "Person" AS p0 + WHERE p0."Age" = 20 AND p."Id" = p0."Id" LIMIT 1) FROM "Person" AS p WHERE p."Age" = 20 GROUP BY p."Id" ORDER BY ( - SELECT p1."MiddleInitial" - FROM "Person" AS p1 - WHERE p1."Age" = 20 AND p."Id" = p1."Id" + SELECT p0."MiddleInitial" + FROM "Person" AS p0 + WHERE p0."Age" = 20 AND p."Id" = p0."Id" LIMIT 1) NULLS FIRST """); } - [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/issues/27155")] - public override Task Whats_new_2021_sample_10(bool async) - => base.Whats_new_2021_sample_10(async); - private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.PG.FunctionalTests/Query/EntitySplittingQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/EntitySplittingQueryNpgsqlTest.cs index b9987f8e0..aff617fd7 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EntitySplittingQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EntitySplittingQueryNpgsqlTest.cs @@ -54,9 +54,9 @@ public override async Task Can_query_entity_which_is_split_selecting_only_part_2 AssertSql( """ -SELECT e."Id", s0."IntValue3", s0."StringValue3" +SELECT e."Id", s."IntValue3", s."StringValue3" FROM "EntityOne" AS e -INNER JOIN "SplitEntityOnePart2" AS s0 ON e."Id" = s0."Id" +INNER JOIN "SplitEntityOnePart2" AS s ON e."Id" = s."Id" """); } @@ -78,14 +78,14 @@ public override async Task Include_reference_to_split_entity(bool async) AssertSql( """ -SELECT e."Id", e."EntityOneId", e."Name", t."Id", t."EntityThreeId", t."IntValue1", t."IntValue2", t."IntValue3", t."IntValue4", t."StringValue1", t."StringValue2", t."StringValue3", t."StringValue4" +SELECT e."Id", e."EntityOneId", e."Name", s1."Id", s1."EntityThreeId", s1."IntValue1", s1."IntValue2", s1."IntValue3", s1."IntValue4", s1."StringValue1", s1."StringValue2", s1."StringValue3", s1."StringValue4" FROM "EntityTwo" AS e LEFT JOIN ( SELECT e0."Id", e0."EntityThreeId", e0."IntValue1", e0."IntValue2", s0."IntValue3", s."IntValue4", e0."StringValue1", e0."StringValue2", s0."StringValue3", s."StringValue4" FROM "EntityOne" AS e0 INNER JOIN "SplitEntityOnePart3" AS s ON e0."Id" = s."Id" INNER JOIN "SplitEntityOnePart2" AS s0 ON e0."Id" = s0."Id" -) AS t ON e."EntityOneId" = t."Id" +) AS s1 ON e."EntityOneId" = s1."Id" """); } @@ -95,14 +95,14 @@ public override async Task Include_collection_to_split_entity(bool async) AssertSql( """ -SELECT e."Id", e."Name", t."Id", t."EntityThreeId", t."IntValue1", t."IntValue2", t."IntValue3", t."IntValue4", t."StringValue1", t."StringValue2", t."StringValue3", t."StringValue4" +SELECT e."Id", e."Name", s1."Id", s1."EntityThreeId", s1."IntValue1", s1."IntValue2", s1."IntValue3", s1."IntValue4", s1."StringValue1", s1."StringValue2", s1."StringValue3", s1."StringValue4" FROM "EntityThree" AS e LEFT JOIN ( SELECT e0."Id", e0."EntityThreeId", e0."IntValue1", e0."IntValue2", s0."IntValue3", s."IntValue4", e0."StringValue1", e0."StringValue2", s0."StringValue3", s."StringValue4" FROM "EntityOne" AS e0 INNER JOIN "SplitEntityOnePart3" AS s ON e0."Id" = s."Id" INNER JOIN "SplitEntityOnePart2" AS s0 ON e0."Id" = s0."Id" -) AS t ON e."Id" = t."EntityThreeId" +) AS s1 ON e."Id" = s1."EntityThreeId" ORDER BY e."Id" NULLS FIRST """); } @@ -113,15 +113,15 @@ public override async Task Include_reference_to_split_entity_including_reference AssertSql( """ -SELECT e."Id", e."EntityOneId", e."Name", t."Id", t."EntityThreeId", t."IntValue1", t."IntValue2", t."IntValue3", t."IntValue4", t."StringValue1", t."StringValue2", t."StringValue3", t."StringValue4", e1."Id", e1."Name" +SELECT e."Id", e."EntityOneId", e."Name", s1."Id", s1."EntityThreeId", s1."IntValue1", s1."IntValue2", s1."IntValue3", s1."IntValue4", s1."StringValue1", s1."StringValue2", s1."StringValue3", s1."StringValue4", e1."Id", e1."Name" FROM "EntityTwo" AS e LEFT JOIN ( SELECT e0."Id", e0."EntityThreeId", e0."IntValue1", e0."IntValue2", s0."IntValue3", s."IntValue4", e0."StringValue1", e0."StringValue2", s0."StringValue3", s."StringValue4" FROM "EntityOne" AS e0 INNER JOIN "SplitEntityOnePart3" AS s ON e0."Id" = s."Id" INNER JOIN "SplitEntityOnePart2" AS s0 ON e0."Id" = s0."Id" -) AS t ON e."EntityOneId" = t."Id" -LEFT JOIN "EntityThree" AS e1 ON t."EntityThreeId" = e1."Id" +) AS s1 ON e."EntityOneId" = s1."Id" +LEFT JOIN "EntityThree" AS e1 ON s1."EntityThreeId" = e1."Id" """); } @@ -131,7 +131,7 @@ public override async Task Include_collection_to_split_entity_including_collecti AssertSql( """ -SELECT e."Id", e."Name", t."Id", t."EntityThreeId", t."IntValue1", t."IntValue2", t."IntValue3", t."IntValue4", t."StringValue1", t."StringValue2", t."StringValue3", t."StringValue4", t."Id0", t."EntityOneId", t."Name" +SELECT e."Id", e."Name", s1."Id", s1."EntityThreeId", s1."IntValue1", s1."IntValue2", s1."IntValue3", s1."IntValue4", s1."StringValue1", s1."StringValue2", s1."StringValue3", s1."StringValue4", s1."Id0", s1."EntityOneId", s1."Name" FROM "EntityThree" AS e LEFT JOIN ( SELECT e0."Id", e0."EntityThreeId", e0."IntValue1", e0."IntValue2", s0."IntValue3", s."IntValue4", e0."StringValue1", e0."StringValue2", s0."StringValue3", s."StringValue4", e1."Id" AS "Id0", e1."EntityOneId", e1."Name" @@ -139,8 +139,8 @@ LEFT JOIN ( INNER JOIN "SplitEntityOnePart3" AS s ON e0."Id" = s."Id" INNER JOIN "SplitEntityOnePart2" AS s0 ON e0."Id" = s0."Id" LEFT JOIN "EntityTwo" AS e1 ON e0."Id" = e1."EntityOneId" -) AS t ON e."Id" = t."EntityThreeId" -ORDER BY e."Id" NULLS FIRST, t."Id" NULLS FIRST +) AS s1 ON e."Id" = s1."EntityThreeId" +ORDER BY e."Id" NULLS FIRST, s1."Id" NULLS FIRST """); } @@ -179,9 +179,9 @@ public override async Task Custom_projection_trim_when_multiple_tables(bool asyn AssertSql( """ -SELECT e."IntValue1", s0."IntValue3", e0."Id", e0."Name" +SELECT e."IntValue1", s."IntValue3", e0."Id", e0."Name" FROM "EntityOne" AS e -INNER JOIN "SplitEntityOnePart2" AS s0 ON e."Id" = s0."Id" +INNER JOIN "SplitEntityOnePart2" AS s ON e."Id" = s."Id" LEFT JOIN "EntityThree" AS e0 ON e."EntityThreeId" = e0."Id" """); } @@ -447,7 +447,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_leaf_with_tabl AssertSql( """ -SELECT t."Id", t."BaseValue", t."MiddleValue", t."SiblingValue", t."LeafValue", t."Discriminator", l."Id", l."OwnedReference_Id", l."OwnedReference_OwnedIntValue1", l."OwnedReference_OwnedIntValue2", o0."OwnedIntValue3", o."OwnedIntValue4", l."OwnedReference_OwnedStringValue1", l."OwnedReference_OwnedStringValue2", o0."OwnedStringValue3", o."OwnedStringValue4" +SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", l0."Id", l0."OwnedReference_Id", l0."OwnedReference_OwnedIntValue1", l0."OwnedReference_OwnedIntValue2", o0."OwnedIntValue3", o."OwnedIntValue4", l0."OwnedReference_OwnedStringValue1", l0."OwnedReference_OwnedStringValue2", o0."OwnedStringValue3", o."OwnedStringValue4" FROM ( SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b @@ -458,12 +458,12 @@ UNION ALL SELECT s."Id", s."BaseValue", NULL AS "MiddleValue", s."SiblingValue", NULL AS "LeafValue", 'SiblingEntity' AS "Discriminator" FROM "SiblingEntity" AS s UNION ALL - SELECT l0."Id", l0."BaseValue", l0."MiddleValue", NULL AS "SiblingValue", l0."LeafValue", 'LeafEntity' AS "Discriminator" - FROM "LeafEntity" AS l0 -) AS t -LEFT JOIN "LeafEntity" AS l ON t."Id" = l."Id" -LEFT JOIN "OwnedReferencePart4" AS o ON l."Id" = o."LeafEntityId" -LEFT JOIN "OwnedReferencePart3" AS o0 ON l."Id" = o0."LeafEntityId" + SELECT l."Id", l."BaseValue", l."MiddleValue", NULL AS "SiblingValue", l."LeafValue", 'LeafEntity' AS "Discriminator" + FROM "LeafEntity" AS l +) AS u +LEFT JOIN "LeafEntity" AS l0 ON u."Id" = l0."Id" +LEFT JOIN "OwnedReferencePart4" AS o ON l0."Id" = o."LeafEntityId" +LEFT JOIN "OwnedReferencePart3" AS o0 ON l0."Id" = o0."LeafEntityId" """); } @@ -576,7 +576,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_base_without_t AssertSql( """ -SELECT t."Id", t."BaseValue", t."MiddleValue", t."SiblingValue", t."LeafValue", t."Discriminator", o."BaseEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4" +SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", o."BaseEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4" FROM ( SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b @@ -589,8 +589,8 @@ UNION ALL UNION ALL SELECT l."Id", l."BaseValue", l."MiddleValue", NULL AS "SiblingValue", l."LeafValue", 'LeafEntity' AS "Discriminator" FROM "LeafEntity" AS l -) AS t -LEFT JOIN "OwnedReferencePart1" AS o ON t."Id" = o."BaseEntityId" +) AS u +LEFT JOIN "OwnedReferencePart1" AS o ON u."Id" = o."BaseEntityId" LEFT JOIN "OwnedReferencePart4" AS o0 ON o."BaseEntityId" = o0."BaseEntityId" LEFT JOIN "OwnedReferencePart3" AS o1 ON o."BaseEntityId" = o1."BaseEntityId" """); @@ -618,7 +618,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_middle_without AssertSql( """ -SELECT t."Id", t."BaseValue", t."MiddleValue", t."SiblingValue", t."LeafValue", t."Discriminator", o."MiddleEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4" +SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", o."MiddleEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4" FROM ( SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b @@ -631,8 +631,8 @@ UNION ALL UNION ALL SELECT l."Id", l."BaseValue", l."MiddleValue", NULL AS "SiblingValue", l."LeafValue", 'LeafEntity' AS "Discriminator" FROM "LeafEntity" AS l -) AS t -LEFT JOIN "OwnedReferencePart1" AS o ON t."Id" = o."MiddleEntityId" +) AS u +LEFT JOIN "OwnedReferencePart1" AS o ON u."Id" = o."MiddleEntityId" LEFT JOIN "OwnedReferencePart4" AS o0 ON o."MiddleEntityId" = o0."MiddleEntityId" LEFT JOIN "OwnedReferencePart3" AS o1 ON o."MiddleEntityId" = o1."MiddleEntityId" """); @@ -684,7 +684,7 @@ public override async Task Tpc_entity_owning_a_split_collection_on_base(bool asy AssertSql( """ -SELECT t."Id", t."BaseValue", t."MiddleValue", t."SiblingValue", t."LeafValue", t."Discriminator", t0."BaseEntityId", t0."Id", t0."OwnedIntValue1", t0."OwnedIntValue2", t0."OwnedIntValue3", t0."OwnedIntValue4", t0."OwnedStringValue1", t0."OwnedStringValue2", t0."OwnedStringValue3", t0."OwnedStringValue4" +SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", s0."BaseEntityId", s0."Id", s0."OwnedIntValue1", s0."OwnedIntValue2", s0."OwnedIntValue3", s0."OwnedIntValue4", s0."OwnedStringValue1", s0."OwnedStringValue2", s0."OwnedStringValue3", s0."OwnedStringValue4" FROM ( SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b @@ -697,14 +697,14 @@ UNION ALL UNION ALL SELECT l."Id", l."BaseValue", l."MiddleValue", NULL AS "SiblingValue", l."LeafValue", 'LeafEntity' AS "Discriminator" FROM "LeafEntity" AS l -) AS t +) AS u LEFT JOIN ( SELECT o."BaseEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4" FROM "OwnedReferencePart1" AS o INNER JOIN "OwnedReferencePart4" AS o0 ON o."BaseEntityId" = o0."BaseEntityId" AND o."Id" = o0."Id" INNER JOIN "OwnedReferencePart3" AS o1 ON o."BaseEntityId" = o1."BaseEntityId" AND o."Id" = o1."Id" -) AS t0 ON t."Id" = t0."BaseEntityId" -ORDER BY t."Id" NULLS FIRST, t0."BaseEntityId" NULLS FIRST +) AS s0 ON u."Id" = s0."BaseEntityId" +ORDER BY u."Id" NULLS FIRST, s0."BaseEntityId" NULLS FIRST """); } @@ -730,7 +730,7 @@ public override async Task Tpc_entity_owning_a_split_collection_on_middle(bool a AssertSql( """ -SELECT t."Id", t."BaseValue", t."MiddleValue", t."SiblingValue", t."LeafValue", t."Discriminator", t0."MiddleEntityId", t0."Id", t0."OwnedIntValue1", t0."OwnedIntValue2", t0."OwnedIntValue3", t0."OwnedIntValue4", t0."OwnedStringValue1", t0."OwnedStringValue2", t0."OwnedStringValue3", t0."OwnedStringValue4" +SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", s0."MiddleEntityId", s0."Id", s0."OwnedIntValue1", s0."OwnedIntValue2", s0."OwnedIntValue3", s0."OwnedIntValue4", s0."OwnedStringValue1", s0."OwnedStringValue2", s0."OwnedStringValue3", s0."OwnedStringValue4" FROM ( SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b @@ -743,14 +743,14 @@ UNION ALL UNION ALL SELECT l."Id", l."BaseValue", l."MiddleValue", NULL AS "SiblingValue", l."LeafValue", 'LeafEntity' AS "Discriminator" FROM "LeafEntity" AS l -) AS t +) AS u LEFT JOIN ( SELECT o."MiddleEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4" FROM "OwnedReferencePart1" AS o INNER JOIN "OwnedReferencePart4" AS o0 ON o."MiddleEntityId" = o0."MiddleEntityId" AND o."Id" = o0."Id" INNER JOIN "OwnedReferencePart3" AS o1 ON o."MiddleEntityId" = o1."MiddleEntityId" AND o."Id" = o1."Id" -) AS t0 ON t."Id" = t0."MiddleEntityId" -ORDER BY t."Id" NULLS FIRST, t0."MiddleEntityId" NULLS FIRST +) AS s0 ON u."Id" = s0."MiddleEntityId" +ORDER BY u."Id" NULLS FIRST, s0."MiddleEntityId" NULLS FIRST """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs index 32d7971dc..0d9e16de1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs @@ -35,30 +35,6 @@ await AssertQuery( ss => ss.Set().Where(c => c.FirstName != null && c.FirstName.StartsWith(param))); } - [ConditionalTheory] // TODO: Remove, test was introduced upstream - [MemberData(nameof(IsAsyncData))] - public virtual async Task String_Contains_and_StartsWith_with_same_parameter(bool async) - { - var s = "B"; - - await AssertQuery( - async, - ss => ss.Set().Where( - c => c.FirstName.Contains(s) || c.LastName.StartsWith(s)), - ss => ss.Set().Where( - c => c.FirstName.MaybeScalar(f => f.Contains(s)) == true || c.LastName.MaybeScalar(l => l.StartsWith(s)) == true)); - - AssertSql( - """ -@__s_0_contains='%B%' -@__s_0_startswith='B%' - -SELECT f."Id", f."FirstName", f."LastName", f."NullableBool" -FROM "FunkyCustomers" AS f -WHERE f."FirstName" LIKE @__s_0_contains ESCAPE '\' OR f."LastName" LIKE @__s_0_startswith ESCAPE '\' -"""); - } - private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs index 081b7c140..0149ad5d2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs @@ -279,16 +279,16 @@ public override async Task Json_subquery_property_pushdown_length(bool async) """ @__p_0='3' -SELECT length(t0.c)::int +SELECT length(j1.c)::int FROM ( - SELECT DISTINCT t.c + SELECT DISTINCT j0.c FROM ( SELECT j."OwnedReferenceRoot" #>> '{OwnedReferenceBranch,OwnedReferenceLeaf,SomethingSomething}' AS c FROM "JsonEntitiesBasic" AS j ORDER BY j."Id" NULLS FIRST LIMIT @__p_0 - ) AS t -) AS t0 + ) AS j0 +) AS j1 """); } @@ -300,16 +300,16 @@ public override async Task Json_subquery_reference_pushdown_reference(bool async """ @__p_0='10' -SELECT t0.c -> 'OwnedReferenceBranch', t0."Id" +SELECT j1.c -> 'OwnedReferenceBranch', j1."Id" FROM ( - SELECT DISTINCT t.c AS c, t."Id" + SELECT DISTINCT j0.c AS c, j0."Id" FROM ( SELECT j."OwnedReferenceRoot" AS c, j."Id" FROM "JsonEntitiesBasic" AS j ORDER BY j."Id" NULLS FIRST LIMIT @__p_0 - ) AS t -) AS t0 + ) AS j0 +) AS j1 """); } @@ -368,24 +368,24 @@ public override async Task Json_subquery_reference_pushdown_reference_pushdown_r """ @__p_0='10' -SELECT t2.c -> 'OwnedReferenceLeaf', t2."Id" +SELECT j3.c -> 'OwnedReferenceLeaf', j3."Id" FROM ( - SELECT DISTINCT t1.c AS c, t1."Id" + SELECT DISTINCT j2.c AS c, j2."Id" FROM ( - SELECT t0.c -> 'OwnedReferenceBranch' AS c, t0."Id" + SELECT j1.c -> 'OwnedReferenceBranch' AS c, j1."Id" FROM ( - SELECT DISTINCT t.c AS c, t."Id", t.c AS c0 + SELECT DISTINCT j0.c AS c, j0."Id", j0.c AS c0 FROM ( SELECT j."OwnedReferenceRoot" AS c, j."Id" FROM "JsonEntitiesBasic" AS j ORDER BY j."Id" NULLS FIRST LIMIT @__p_0 - ) AS t - ) AS t0 - ORDER BY t0.c0 ->> 'Name' NULLS FIRST + ) AS j0 + ) AS j1 + ORDER BY j1.c0 ->> 'Name' NULLS FIRST LIMIT @__p_0 - ) AS t1 -) AS t2 + ) AS j2 +) AS j3 """); } @@ -397,24 +397,24 @@ public override async Task Json_subquery_reference_pushdown_reference_pushdown_c """ @__p_0='10' -SELECT t2.c -> 'OwnedCollectionLeaf', t2."Id" +SELECT j3.c -> 'OwnedCollectionLeaf', j3."Id" FROM ( - SELECT DISTINCT t1.c AS c, t1."Id" + SELECT DISTINCT j2.c AS c, j2."Id" FROM ( - SELECT t0.c -> 'OwnedReferenceBranch' AS c, t0."Id" + SELECT j1.c -> 'OwnedReferenceBranch' AS c, j1."Id" FROM ( - SELECT DISTINCT t.c AS c, t."Id", t.c AS c0 + SELECT DISTINCT j0.c AS c, j0."Id", j0.c AS c0 FROM ( SELECT j."OwnedReferenceRoot" AS c, j."Id" FROM "JsonEntitiesBasic" AS j ORDER BY j."Id" NULLS FIRST LIMIT @__p_0 - ) AS t - ) AS t0 - ORDER BY t0.c0 ->> 'Name' NULLS FIRST + ) AS j0 + ) AS j1 + ORDER BY j1.c0 ->> 'Name' NULLS FIRST LIMIT @__p_0 - ) AS t1 -) AS t2 + ) AS j2 +) AS j3 """); } @@ -426,16 +426,16 @@ public override async Task Json_subquery_reference_pushdown_property(bool async) """ @__p_0='10' -SELECT t0.c ->> 'SomethingSomething' +SELECT j1.c ->> 'SomethingSomething' FROM ( - SELECT DISTINCT t.c AS c, t."Id" + SELECT DISTINCT j0.c AS c, j0."Id" FROM ( SELECT j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedReferenceLeaf}' AS c, j."Id" FROM "JsonEntitiesBasic" AS j ORDER BY j."Id" NULLS FIRST LIMIT @__p_0 - ) AS t -) AS t0 + ) AS j0 +) AS j1 """); } @@ -560,14 +560,14 @@ public override async Task Project_json_entity_FirstOrDefault_subquery(bool asyn AssertSql( """ -SELECT t.c, t."Id" +SELECT j1.c, j1."Id" FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j0."OwnedReferenceRoot" -> 'OwnedReferenceBranch' AS c, j0."Id" FROM "JsonEntitiesBasic" AS j0 ORDER BY j0."Id" NULLS FIRST LIMIT 1 -) AS t ON TRUE +) AS j1 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -602,14 +602,14 @@ public override async Task Project_json_entity_FirstOrDefault_subquery_deduplica AssertSql( """ -SELECT t.c, t."Id", t.c0, t."Id0", t.c1, t.c2, t.c3, t.c4 +SELECT j1.c, j1."Id", j1.c0, j1."Id0", j1.c1, j1.c2, j1.c3, j1.c4 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."OwnedReferenceRoot" -> 'OwnedCollectionBranch' AS c, j."Id", j0."OwnedReferenceRoot" AS c0, j0."Id" AS "Id0", j0."OwnedReferenceRoot" -> 'OwnedReferenceBranch' AS c1, j0."OwnedReferenceRoot" ->> 'Name' AS c2, CAST(j."OwnedReferenceRoot" #>> '{OwnedReferenceBranch,Enum}' AS integer) AS c3, 1 AS c4 FROM "JsonEntitiesBasic" AS j0 ORDER BY j0."Id" NULLS FIRST LIMIT 1 -) AS t ON TRUE +) AS j1 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -620,14 +620,14 @@ public override async Task Project_json_entity_FirstOrDefault_subquery_deduplica AssertSql( """ -SELECT t.c, t."Id", t.c0, t."Id0", t.c1, t.c2, t.c3, t.c4 +SELECT j1.c, j1."Id", j1.c0, j1."Id0", j1.c1, j1.c2, j1.c3, j1.c4 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."OwnedReferenceRoot" -> 'OwnedCollectionBranch' AS c, j."Id", j0."OwnedReferenceRoot" AS c0, j0."Id" AS "Id0", j0."OwnedReferenceRoot" -> 'OwnedReferenceBranch' AS c1, j0."OwnedReferenceRoot" ->> 'Name' AS c2, CAST(j."OwnedReferenceRoot" #>> '{OwnedReferenceBranch,Enum}' AS integer) AS c3, 1 AS c4 FROM "JsonEntitiesBasic" AS j0 ORDER BY j0."Id" NULLS FIRST LIMIT 1 -) AS t ON TRUE +) AS j1 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -638,14 +638,14 @@ public override async Task Project_json_entity_FirstOrDefault_subquery_deduplica AssertSql( """ -SELECT t.c, t."Id", t.c0 +SELECT j1.c, j1."Id", j1.c0 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."OwnedReferenceRoot" -> 'OwnedCollectionBranch' AS c, j."Id", 1 AS c0 FROM "JsonEntitiesBasic" AS j0 ORDER BY j0."Id" NULLS FIRST LIMIT 1 -) AS t ON TRUE +) AS j1 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -1066,7 +1066,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBra "OwnedCollectionLeaf" jsonb, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o - WHERE o."Enum" = 2 + WHERE o."Enum" = -3 LIMIT 1 OFFSET 0) = 'e1_r_c2_r' """); } @@ -1080,9 +1080,9 @@ public override async Task Json_collection_Skip(bool async) SELECT j."Id", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" FROM "JsonEntitiesBasic" AS j WHERE ( - SELECT t.c + SELECT o0.c FROM ( - SELECT o."OwnedReferenceLeaf" ->> 'SomethingSomething' AS c, o.ordinality + SELECT o."OwnedReferenceLeaf" ->> 'SomethingSomething' AS c FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( "Date" timestamp without time zone, "Enum" integer, @@ -1094,7 +1094,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBra "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o OFFSET 1 - ) AS t + ) AS o0 LIMIT 1 OFFSET 0) = 'e1_r_c2_r' """); } @@ -1108,9 +1108,9 @@ public override async Task Json_collection_OrderByDescending_Skip_ElementAt(bool SELECT j."Id", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" FROM "JsonEntitiesBasic" AS j WHERE ( - SELECT t.c + SELECT o0.c FROM ( - SELECT o."OwnedReferenceLeaf" ->> 'SomethingSomething' AS c, o.ordinality, o."Date" AS c0 + SELECT o."OwnedReferenceLeaf" ->> 'SomethingSomething' AS c, o."Date" AS c0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( "Date" timestamp without time zone, "Enum" integer, @@ -1123,8 +1123,8 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBra )) WITH ORDINALITY AS o ORDER BY o."Date" DESC NULLS LAST OFFSET 1 - ) AS t - ORDER BY t.c0 DESC NULLS LAST + ) AS o0 + ORDER BY o0.c0 DESC NULLS LAST LIMIT 1 OFFSET 0) = 'e1_r_c1_r' """); } @@ -1152,7 +1152,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBra "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o WHERE (o."OwnedReferenceLeaf" ->> 'SomethingSomething') = 'e1_r_c2_r' - ) AS t) = 1 + ) AS o0) = 1 """); } @@ -1236,7 +1236,7 @@ public override async Task Json_collection_in_projection_with_composition_where_ AssertSql( """ -SELECT j."Id", t."Name", t."Number", t.ordinality +SELECT j."Id", o0."Name", o0."Number", o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o."Name", o."Number", o.ordinality @@ -1249,7 +1249,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o WHERE o."Name" = 'Foo' -) AS t ON TRUE +) AS o0 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -1260,7 +1260,7 @@ public override async Task Json_collection_in_projection_with_composition_where_ AssertSql( """ -SELECT j."Id", t."Names", t."Numbers", t.ordinality +SELECT j."Id", o0."Names", o0."Numbers", o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o."Names", o."Numbers", o.ordinality @@ -1273,7 +1273,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o WHERE o."Name" = 'Foo' -) AS t ON TRUE +) AS o0 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -1284,7 +1284,7 @@ public override async Task Json_collection_filter_in_projection(bool async) AssertSql( """ -SELECT j."Id", t."Id", t."Name", t."Names", t."Number", t."Numbers", t.c, t.c0, t.ordinality +SELECT j."Id", o0."Id", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0, o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."Id", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0, o.ordinality @@ -1297,7 +1297,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o WHERE o."Name" <> 'Foo' OR o."Name" IS NULL -) AS t ON TRUE +) AS o0 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -1308,10 +1308,10 @@ public override async Task Json_nested_collection_filter_in_projection(bool asyn AssertSql( """ -SELECT j."Id", t0.ordinality, t0."Id", t0."Date", t0."Enum", t0."Enums", t0."Fraction", t0."NullableEnum", t0."NullableEnums", t0.c, t0.c0, t0.ordinality0 +SELECT j."Id", s.ordinality, s."Id", s."Date", s."Enum", s."Enums", s."Fraction", s."NullableEnum", s."NullableEnums", s.c, s.c0, s.ordinality0 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( - SELECT o.ordinality, t."Id", t."Date", t."Enum", t."Enums", t."Fraction", t."NullableEnum", t."NullableEnums", t.c, t.c0, t.ordinality AS ordinality0 + SELECT o.ordinality, o1."Id", o1."Date", o1."Enum", o1."Enums", o1."Fraction", o1."NullableEnum", o1."NullableEnums", o1.c, o1.c0, o1.ordinality AS ordinality0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "Name" text, "Names" text[], @@ -1333,9 +1333,9 @@ FROM ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o0 WHERE o0."Date" <> TIMESTAMP '2000-01-01T00:00:00' - ) AS t ON TRUE -) AS t0 ON TRUE -ORDER BY j."Id" NULLS FIRST, t0.ordinality NULLS FIRST + ) AS o1 ON TRUE +) AS s ON TRUE +ORDER BY j."Id" NULLS FIRST, s.ordinality NULLS FIRST """); } @@ -1345,7 +1345,7 @@ public override async Task Json_nested_collection_anonymous_projection_in_projec AssertSql( """ -SELECT j."Id", t.ordinality, t.c, t.c0, t.c1, t.c2, t.c3, t."Id", t.c4, t.ordinality0 +SELECT j."Id", s.ordinality, s.c, s.c0, s.c1, s.c2, s.c3, s."Id", s.c4, s.ordinality0 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o.ordinality, o0."Date" AS c, o0."Enum" AS c0, o0."Enums" AS c1, o0."Fraction" AS c2, o0."OwnedReferenceLeaf" AS c3, j."Id", o0."OwnedCollectionLeaf" AS c4, o0.ordinality AS ordinality0 @@ -1367,8 +1367,8 @@ LEFT JOIN LATERAL ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( "OwnedCollectionLeaf" jsonb, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o0 ON TRUE -) AS t ON TRUE -ORDER BY j."Id" NULLS FIRST, t.ordinality NULLS FIRST +) AS s ON TRUE +ORDER BY j."Id" NULLS FIRST, s.ordinality NULLS FIRST """); } @@ -1378,7 +1378,7 @@ public override async Task Json_collection_skip_take_in_projection(bool async) AssertSql( """ -SELECT j."Id", t."Id", t."Name", t."Names", t."Number", t."Numbers", t.c, t.c0, t.ordinality +SELECT j."Id", o0."Id", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0, o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."Id", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0, o.ordinality, o."Name" AS c1 @@ -1392,8 +1392,8 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( )) WITH ORDINALITY AS o ORDER BY o."Name" NULLS FIRST LIMIT 5 OFFSET 1 -) AS t ON TRUE -ORDER BY j."Id" NULLS FIRST, t.c1 NULLS FIRST +) AS o0 ON TRUE +ORDER BY j."Id" NULLS FIRST, o0.c1 NULLS FIRST """); } @@ -1403,7 +1403,7 @@ public override async Task Json_collection_skip_take_in_projection_project_into_ AssertSql( """ -SELECT j."Id", t.c, t.c0, t.c1, t.c2, t.c3, t."Id", t.c4, t.ordinality +SELECT j."Id", o0.c, o0.c0, o0.c1, o0.c2, o0.c3, o0."Id", o0.c4, o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o."Name" AS c, o."Names" AS c0, o."Number" AS c1, o."Numbers" AS c2, o."OwnedCollectionBranch" AS c3, j."Id", o."OwnedReferenceBranch" AS c4, o.ordinality @@ -1417,8 +1417,8 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( )) WITH ORDINALITY AS o ORDER BY o."Name" NULLS FIRST LIMIT 5 OFFSET 1 -) AS t ON TRUE -ORDER BY j."Id" NULLS FIRST, t.c NULLS FIRST +) AS o0 ON TRUE +ORDER BY j."Id" NULLS FIRST, o0.c NULLS FIRST """); } @@ -1428,7 +1428,7 @@ public override async Task Json_collection_skip_take_in_projection_with_json_ref AssertSql( """ -SELECT j."Id", t.c, t."Id", t.ordinality +SELECT j."Id", o0.c, o0."Id", o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o."OwnedReferenceBranch" AS c, j."Id", o.ordinality, o."Name" AS c0 @@ -1442,8 +1442,8 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( )) WITH ORDINALITY AS o ORDER BY o."Name" NULLS FIRST LIMIT 5 OFFSET 1 -) AS t ON TRUE -ORDER BY j."Id" NULLS FIRST, t.c0 NULLS FIRST +) AS o0 ON TRUE +ORDER BY j."Id" NULLS FIRST, o0.c0 NULLS FIRST """); } @@ -1453,7 +1453,7 @@ public override async Task Json_collection_distinct_in_projection(bool async) AssertSql( """ -SELECT j."Id", t."Id", t."Name", t."Names", t."Number", t."Numbers", t.c, t.c0 +SELECT j."Id", o0."Id", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT DISTINCT j."Id", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0 @@ -1465,8 +1465,8 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "OwnedCollectionBranch" jsonb, "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o -) AS t ON TRUE -ORDER BY j."Id" NULLS FIRST, t."Name" NULLS FIRST, t."Names" NULLS FIRST, t."Number" NULLS FIRST +) AS o0 ON TRUE +ORDER BY j."Id" NULLS FIRST, o0."Name" NULLS FIRST, o0."Names" NULLS FIRST, o0."Number" NULLS FIRST """); } @@ -1483,13 +1483,13 @@ public override async Task Json_collection_leaf_filter_in_projection(bool async) AssertSql( """ -SELECT j."Id", t."Id", t."SomethingSomething", t.ordinality +SELECT j."Id", o0."Id", o0."SomethingSomething", o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."Id", o."SomethingSomething", o.ordinality FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedCollectionLeaf}') AS ("SomethingSomething" text)) WITH ORDINALITY AS o WHERE o."SomethingSomething" <> 'Baz' OR o."SomethingSomething" IS NULL -) AS t ON TRUE +) AS o0 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -1500,13 +1500,13 @@ public override async Task Json_multiple_collection_projections(bool async) AssertSql( """ -SELECT j."Id", t."Id", t."SomethingSomething", t.ordinality, t0."Id", t0."Name", t0."Names", t0."Number", t0."Numbers", t0.c, t0.c0, t1.ordinality, t1."Id", t1."Date", t1."Enum", t1."Enums", t1."Fraction", t1."NullableEnum", t1."NullableEnums", t1.c, t1.c0, t1.ordinality0, j0."Id", j0."Name", j0."ParentId" +SELECT j."Id", o4."Id", o4."SomethingSomething", o4.ordinality, o1."Id", o1."Name", o1."Names", o1."Number", o1."Numbers", o1.c, o1.c0, s.ordinality, s."Id", s."Date", s."Enum", s."Enums", s."Fraction", s."NullableEnum", s."NullableEnums", s.c, s.c0, s.ordinality0, j0."Id", j0."Name", j0."ParentId" FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."Id", o."SomethingSomething", o.ordinality FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedCollectionLeaf}') AS ("SomethingSomething" text)) WITH ORDINALITY AS o WHERE o."SomethingSomething" <> 'Baz' OR o."SomethingSomething" IS NULL -) AS t ON TRUE +) AS o4 ON TRUE LEFT JOIN LATERAL ( SELECT DISTINCT j."Id", o0."Name", o0."Names", o0."Number", o0."Numbers", o0."OwnedCollectionBranch" AS c, o0."OwnedReferenceBranch" AS c0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( @@ -1517,9 +1517,9 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "OwnedCollectionBranch" jsonb, "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o0 -) AS t0 ON TRUE +) AS o1 ON TRUE LEFT JOIN LATERAL ( - SELECT o1.ordinality, t2."Id", t2."Date", t2."Enum", t2."Enums", t2."Fraction", t2."NullableEnum", t2."NullableEnums", t2.c, t2.c0, t2.ordinality AS ordinality0 + SELECT o2.ordinality, o5."Id", o5."Date", o5."Enum", o5."Enums", o5."Fraction", o5."NullableEnum", o5."NullableEnums", o5.c, o5.c0, o5.ordinality AS ordinality0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "Name" text, "Names" text[], @@ -1527,10 +1527,10 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "Numbers" integer[], "OwnedCollectionBranch" jsonb, "OwnedReferenceBranch" jsonb - )) WITH ORDINALITY AS o1 + )) WITH ORDINALITY AS o2 LEFT JOIN LATERAL ( - SELECT j."Id", o2."Date", o2."Enum", o2."Enums", o2."Fraction", o2."NullableEnum", o2."NullableEnums", o2."OwnedCollectionLeaf" AS c, o2."OwnedReferenceLeaf" AS c0, o2.ordinality - FROM ROWS FROM (jsonb_to_recordset(o1."OwnedCollectionBranch") AS ( + SELECT j."Id", o3."Date", o3."Enum", o3."Enums", o3."Fraction", o3."NullableEnum", o3."NullableEnums", o3."OwnedCollectionLeaf" AS c, o3."OwnedReferenceLeaf" AS c0, o3.ordinality + FROM ROWS FROM (jsonb_to_recordset(o2."OwnedCollectionBranch") AS ( "Date" timestamp without time zone, "Enum" integer, "Enums" integer[], @@ -1539,12 +1539,12 @@ FROM ROWS FROM (jsonb_to_recordset(o1."OwnedCollectionBranch") AS ( "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, "OwnedReferenceLeaf" jsonb - )) WITH ORDINALITY AS o2 - WHERE o2."Date" <> TIMESTAMP '2000-01-01T00:00:00' - ) AS t2 ON TRUE -) AS t1 ON TRUE + )) WITH ORDINALITY AS o3 + WHERE o3."Date" <> TIMESTAMP '2000-01-01T00:00:00' + ) AS o5 ON TRUE +) AS s ON TRUE LEFT JOIN "JsonEntitiesBasicForCollection" AS j0 ON j."Id" = j0."ParentId" -ORDER BY j."Id" NULLS FIRST, t.ordinality NULLS FIRST, t0."Name" NULLS FIRST, t0."Names" NULLS FIRST, t0."Number" NULLS FIRST, t0."Numbers" NULLS FIRST, t1.ordinality NULLS FIRST, t1.ordinality0 NULLS FIRST +ORDER BY j."Id" NULLS FIRST, o4.ordinality NULLS FIRST, o1."Name" NULLS FIRST, o1."Names" NULLS FIRST, o1."Number" NULLS FIRST, o1."Numbers" NULLS FIRST, s.ordinality NULLS FIRST, s.ordinality0 NULLS FIRST """); } @@ -1554,7 +1554,7 @@ public override async Task Json_branch_collection_distinct_and_other_collection( AssertSql( """ -SELECT j."Id", t."Id", t."Date", t."Enum", t."Enums", t."Fraction", t."NullableEnum", t."NullableEnums", t.c, t.c0, j0."Id", j0."Name", j0."ParentId" +SELECT j."Id", o0."Id", o0."Date", o0."Enum", o0."Enums", o0."Fraction", o0."NullableEnum", o0."NullableEnums", o0.c, o0.c0, j0."Id", j0."Name", j0."ParentId" FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT DISTINCT j."Id", o."Date", o."Enum", o."Enums", o."Fraction", o."NullableEnum", o."NullableEnums", o."OwnedCollectionLeaf" AS c, o."OwnedReferenceLeaf" AS c0 @@ -1568,9 +1568,9 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBra "OwnedCollectionLeaf" jsonb, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o -) AS t ON TRUE +) AS o0 ON TRUE LEFT JOIN "JsonEntitiesBasicForCollection" AS j0 ON j."Id" = j0."ParentId" -ORDER BY j."Id" NULLS FIRST, t."Date" NULLS FIRST, t."Enum" NULLS FIRST, t."Enums" NULLS FIRST, t."Fraction" NULLS FIRST, t."NullableEnum" NULLS FIRST, t."NullableEnums" NULLS FIRST +ORDER BY j."Id" NULLS FIRST, o0."Date" NULLS FIRST, o0."Enum" NULLS FIRST, o0."Enums" NULLS FIRST, o0."Fraction" NULLS FIRST, o0."NullableEnum" NULLS FIRST, o0."NullableEnums" NULLS FIRST """); } @@ -1580,14 +1580,14 @@ public override async Task Json_leaf_collection_distinct_and_other_collection(bo AssertSql( """ -SELECT j."Id", t."Id", t."SomethingSomething", j0."Id", j0."Name", j0."ParentId" +SELECT j."Id", o0."Id", o0."SomethingSomething", j0."Id", j0."Name", j0."ParentId" FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT DISTINCT j."Id", o."SomethingSomething" FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedCollectionLeaf}') AS ("SomethingSomething" text)) WITH ORDINALITY AS o -) AS t ON TRUE +) AS o0 ON TRUE LEFT JOIN "JsonEntitiesBasicForCollection" AS j0 ON j."Id" = j0."ParentId" -ORDER BY j."Id" NULLS FIRST, t."SomethingSomething" NULLS FIRST +ORDER BY j."Id" NULLS FIRST, o0."SomethingSomething" NULLS FIRST """); } @@ -1752,7 +1752,7 @@ public override async Task Json_collection_Select_entity_in_anonymous_object_Ele AssertSql( """ -SELECT t.c, t."Id", t.c0 +SELECT o0.c, o0."Id", o0.c0 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o."OwnedReferenceBranch" AS c, j."Id", 1 AS c0 @@ -1765,7 +1765,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o LIMIT 1 OFFSET 0 -) AS t ON TRUE +) AS o0 ON TRUE ORDER BY j."Id" NULLS FIRST """); } @@ -1776,7 +1776,7 @@ public override async Task Json_collection_Select_entity_with_initializer_Elemen AssertSql( """ -SELECT t."Id", t.c +SELECT o0."Id", o0.c FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."Id", 1 AS c @@ -1789,7 +1789,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o LIMIT 1 OFFSET 0 -) AS t ON TRUE +) AS o0 ON TRUE """); } @@ -2030,12 +2030,12 @@ public override async Task Group_by_on_json_scalar(bool async) AssertSql( """ -SELECT t."Key", count(*)::int AS "Count" +SELECT j0."Key", count(*)::int AS "Count" FROM ( SELECT j."OwnedReferenceRoot" ->> 'Name' AS "Key" FROM "JsonEntitiesBasic" AS j -) AS t -GROUP BY t."Key" +) AS j0 +GROUP BY j0."Key" """); } @@ -2045,12 +2045,12 @@ public override async Task Group_by_on_json_scalar_using_collection_indexer(bool AssertSql( """ -SELECT t."Key", count(*)::int AS "Count" +SELECT j0."Key", count(*)::int AS "Count" FROM ( SELECT j."OwnedCollectionRoot" #>> '{0,Name}' AS "Key" FROM "JsonEntitiesBasic" AS j -) AS t -GROUP BY t."Key" +) AS j0 +GROUP BY j0."Key" """); } @@ -2060,26 +2060,26 @@ public override async Task Group_by_First_on_json_scalar(bool async) AssertSql( """ -SELECT t1."Id", t1."EntityBasicId", t1."Name", t1.c, t1.c0 +SELECT j5."Id", j5."EntityBasicId", j5."Name", j5.c, j5.c0 FROM ( - SELECT t."Key" + SELECT j0."Key" FROM ( SELECT j."OwnedReferenceRoot" ->> 'Name' AS "Key" FROM "JsonEntitiesBasic" AS j - ) AS t - GROUP BY t."Key" -) AS t0 + ) AS j0 + GROUP BY j0."Key" +) AS j3 LEFT JOIN ( - SELECT t2."Id", t2."EntityBasicId", t2."Name", t2.c AS c, t2.c0 AS c0, t2."Key" + SELECT j4."Id", j4."EntityBasicId", j4."Name", j4.c AS c, j4.c0 AS c0, j4."Key" FROM ( - SELECT t3."Id", t3."EntityBasicId", t3."Name", t3.c AS c, t3.c0 AS c0, t3."Key", ROW_NUMBER() OVER(PARTITION BY t3."Key" ORDER BY t3."Id" NULLS FIRST) AS row + SELECT j1."Id", j1."EntityBasicId", j1."Name", j1.c AS c, j1.c0 AS c0, j1."Key", ROW_NUMBER() OVER(PARTITION BY j1."Key" ORDER BY j1."Id" NULLS FIRST) AS row FROM ( - SELECT j0."Id", j0."EntityBasicId", j0."Name", j0."OwnedCollectionRoot" AS c, j0."OwnedReferenceRoot" AS c0, j0."OwnedReferenceRoot" ->> 'Name' AS "Key" - FROM "JsonEntitiesBasic" AS j0 - ) AS t3 - ) AS t2 - WHERE t2.row <= 1 -) AS t1 ON t0."Key" = t1."Key" + SELECT j2."Id", j2."EntityBasicId", j2."Name", j2."OwnedCollectionRoot" AS c, j2."OwnedReferenceRoot" AS c0, j2."OwnedReferenceRoot" ->> 'Name' AS "Key" + FROM "JsonEntitiesBasic" AS j2 + ) AS j1 + ) AS j4 + WHERE j4.row <= 1 +) AS j5 ON j3."Key" = j5."Key" """); } @@ -2089,26 +2089,26 @@ public override async Task Group_by_FirstOrDefault_on_json_scalar(bool async) AssertSql( """ -SELECT t1."Id", t1."EntityBasicId", t1."Name", t1.c, t1.c0 +SELECT j5."Id", j5."EntityBasicId", j5."Name", j5.c, j5.c0 FROM ( - SELECT t."Key" + SELECT j0."Key" FROM ( SELECT j."OwnedReferenceRoot" ->> 'Name' AS "Key" FROM "JsonEntitiesBasic" AS j - ) AS t - GROUP BY t."Key" -) AS t0 + ) AS j0 + GROUP BY j0."Key" +) AS j3 LEFT JOIN ( - SELECT t2."Id", t2."EntityBasicId", t2."Name", t2.c AS c, t2.c0 AS c0, t2."Key" + SELECT j4."Id", j4."EntityBasicId", j4."Name", j4.c AS c, j4.c0 AS c0, j4."Key" FROM ( - SELECT t3."Id", t3."EntityBasicId", t3."Name", t3.c AS c, t3.c0 AS c0, t3."Key", ROW_NUMBER() OVER(PARTITION BY t3."Key" ORDER BY t3."Id" NULLS FIRST) AS row + SELECT j1."Id", j1."EntityBasicId", j1."Name", j1.c AS c, j1.c0 AS c0, j1."Key", ROW_NUMBER() OVER(PARTITION BY j1."Key" ORDER BY j1."Id" NULLS FIRST) AS row FROM ( - SELECT j0."Id", j0."EntityBasicId", j0."Name", j0."OwnedCollectionRoot" AS c, j0."OwnedReferenceRoot" AS c0, j0."OwnedReferenceRoot" ->> 'Name' AS "Key" - FROM "JsonEntitiesBasic" AS j0 - ) AS t3 - ) AS t2 - WHERE t2.row <= 1 -) AS t1 ON t0."Key" = t1."Key" + SELECT j2."Id", j2."EntityBasicId", j2."Name", j2."OwnedCollectionRoot" AS c, j2."OwnedReferenceRoot" AS c0, j2."OwnedReferenceRoot" ->> 'Name' AS "Key" + FROM "JsonEntitiesBasic" AS j2 + ) AS j1 + ) AS j4 + WHERE j4.row <= 1 +) AS j5 ON j3."Key" = j5."Key" """); } @@ -2118,27 +2118,27 @@ public override async Task Group_by_Skip_Take_on_json_scalar(bool async) AssertSql( """ -SELECT t0."Key", t1."Id", t1."EntityBasicId", t1."Name", t1.c, t1.c0 +SELECT j3."Key", j5."Id", j5."EntityBasicId", j5."Name", j5.c, j5.c0 FROM ( - SELECT t."Key" + SELECT j0."Key" FROM ( SELECT j."OwnedReferenceRoot" ->> 'Name' AS "Key" FROM "JsonEntitiesBasic" AS j - ) AS t - GROUP BY t."Key" -) AS t0 + ) AS j0 + GROUP BY j0."Key" +) AS j3 LEFT JOIN ( - SELECT t2."Id", t2."EntityBasicId", t2."Name", t2.c, t2.c0, t2."Key" + SELECT j4."Id", j4."EntityBasicId", j4."Name", j4.c, j4.c0, j4."Key" FROM ( - SELECT t3."Id", t3."EntityBasicId", t3."Name", t3.c AS c, t3.c0 AS c0, t3."Key", ROW_NUMBER() OVER(PARTITION BY t3."Key" ORDER BY t3."Id" NULLS FIRST) AS row + SELECT j1."Id", j1."EntityBasicId", j1."Name", j1.c AS c, j1.c0 AS c0, j1."Key", ROW_NUMBER() OVER(PARTITION BY j1."Key" ORDER BY j1."Id" NULLS FIRST) AS row FROM ( - SELECT j0."Id", j0."EntityBasicId", j0."Name", j0."OwnedCollectionRoot" AS c, j0."OwnedReferenceRoot" AS c0, j0."OwnedReferenceRoot" ->> 'Name' AS "Key" - FROM "JsonEntitiesBasic" AS j0 - ) AS t3 - ) AS t2 - WHERE 1 < t2.row AND t2.row <= 6 -) AS t1 ON t0."Key" = t1."Key" -ORDER BY t0."Key" NULLS FIRST, t1."Key" NULLS FIRST, t1."Id" NULLS FIRST + SELECT j2."Id", j2."EntityBasicId", j2."Name", j2."OwnedCollectionRoot" AS c, j2."OwnedReferenceRoot" AS c0, j2."OwnedReferenceRoot" ->> 'Name' AS "Key" + FROM "JsonEntitiesBasic" AS j2 + ) AS j1 + ) AS j4 + WHERE 1 < j4.row AND j4.row <= 6 +) AS j5 ON j3."Key" = j5."Key" +ORDER BY j3."Key" NULLS FIRST, j5."Key" NULLS FIRST, j5."Id" NULLS FIRST """); } @@ -2157,18 +2157,18 @@ public override async Task Group_by_json_scalar_Skip_First_project_json_scalar(b AssertSql( """ SELECT ( - SELECT CAST(t0.c0 #>> '{OwnedReferenceBranch,Enum}' AS integer) + SELECT CAST(j1.c0 #>> '{OwnedReferenceBranch,Enum}' AS integer) FROM ( - SELECT j0."Id", j0."EntityBasicId", j0."Name", j0."OwnedCollectionRoot" AS c, j0."OwnedReferenceRoot" AS c0, j0."OwnedReferenceRoot" ->> 'Name' AS "Key" - FROM "JsonEntitiesBasic" AS j0 - ) AS t0 - WHERE t."Key" = t0."Key" OR (t."Key" IS NULL AND t0."Key" IS NULL) + SELECT j2."OwnedReferenceRoot" AS c0, j2."OwnedReferenceRoot" ->> 'Name' AS "Key" + FROM "JsonEntitiesBasic" AS j2 + ) AS j1 + WHERE j0."Key" = j1."Key" OR (j0."Key" IS NULL AND j1."Key" IS NULL) LIMIT 1) FROM ( SELECT j."OwnedReferenceRoot" ->> 'Name' AS "Key" FROM "JsonEntitiesBasic" AS j -) AS t -GROUP BY t."Key" +) AS j0 +GROUP BY j0."Key" """); } @@ -2535,7 +2535,7 @@ public override async Task Json_predicate_on_enum(bool async) """ SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j -WHERE (CAST(j."Reference" ->> 'TestEnum' AS integer)) <> 1 OR (CAST(j."Reference" ->> 'TestEnum' AS integer)) IS NULL +WHERE (CAST(j."Reference" ->> 'TestEnum' AS integer)) <> 2 OR (CAST(j."Reference" ->> 'TestEnum' AS integer)) IS NULL """); } @@ -2547,7 +2547,7 @@ public override async Task Json_predicate_on_enumwithintconverter(bool async) """ SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j -WHERE (CAST(j."Reference" ->> 'TestEnumWithIntConverter' AS integer)) <> 2 OR (CAST(j."Reference" ->> 'TestEnumWithIntConverter' AS integer)) IS NULL +WHERE (CAST(j."Reference" ->> 'TestEnumWithIntConverter' AS integer)) <> -3 OR (CAST(j."Reference" ->> 'TestEnumWithIntConverter' AS integer)) IS NULL """); } @@ -2607,7 +2607,7 @@ public override async Task Json_predicate_on_nullableenum1(bool async) """ SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j -WHERE (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) <> 0 OR (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) IS NULL +WHERE (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) <> -1 OR (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) IS NULL """); } @@ -2631,7 +2631,7 @@ public override async Task Json_predicate_on_nullableenumwithconverter1(bool asy """ SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j -WHERE (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) <> 1 OR (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) IS NULL +WHERE (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) <> 2 OR (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) IS NULL """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs index 52bf83e88..1f497fa40 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs @@ -252,12 +252,12 @@ public override async Task GroupBy_Property_Select_Key_with_constant(bool async) AssertSql( """ -SELECT t."Name", t."CustomerID" AS "Value", count(*)::int AS "Count" +SELECT o0."Name", o0."CustomerID" AS "Value", count(*)::int AS "Count" FROM ( SELECT o."CustomerID", 'CustomerID' AS "Name" FROM "Orders" AS o -) AS t -GROUP BY t."Name", t."CustomerID" +) AS o0 +GROUP BY o0."Name", o0."CustomerID" """); } @@ -636,12 +636,12 @@ public override async Task GroupBy_Constant_Select_Sum_Min_Key_Max_Avg(bool asyn AssertSql( """ -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum", min(t."OrderID") AS "Min", t."Key", max(t."OrderID") AS "Max", avg(t."OrderID"::double precision) AS "Avg" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum", min(o0."OrderID") AS "Min", o0."Key", max(o0."OrderID") AS "Max", avg(o0."OrderID"::double precision) AS "Avg" FROM ( SELECT o."OrderID", 2 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -651,12 +651,12 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum(boo AssertSql( """ -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum" FROM ( SELECT o."OrderID", 2 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -666,12 +666,12 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum2(bo AssertSql( """ -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum" FROM ( SELECT o."OrderID", 2 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -681,12 +681,12 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum3(bo AssertSql( """ -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum" FROM ( SELECT o."OrderID", 2 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -696,13 +696,13 @@ public override async Task GroupBy_after_predicate_Constant_Select_Sum_Min_Key_M AssertSql( """ -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum", min(t."OrderID") AS "Min", t."Key" AS "Random", max(t."OrderID") AS "Max", avg(t."OrderID"::double precision) AS "Avg" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum", min(o0."OrderID") AS "Min", o0."Key" AS "Random", max(o0."OrderID") AS "Max", avg(o0."OrderID"::double precision) AS "Avg" FROM ( SELECT o."OrderID", 2 AS "Key" FROM "Orders" AS o WHERE o."OrderID" > 10500 -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -712,12 +712,12 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum_Min AssertSql( """ -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum", t."Key" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum", o0."Key" FROM ( SELECT o."OrderID", 2 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -727,13 +727,13 @@ public override async Task GroupBy_constant_with_where_on_grouping_with_aggregat AssertSql( """ -SELECT min(t."OrderDate") FILTER (WHERE 1 = t."Key") AS "Min", max(t."OrderDate") FILTER (WHERE 1 = t."Key") AS "Max", COALESCE(sum(t."OrderID") FILTER (WHERE 1 = t."Key"), 0)::int AS "Sum", avg(t."OrderID"::double precision) FILTER (WHERE 1 = t."Key") AS "Average" +SELECT min(o0."OrderDate") FILTER (WHERE 1 = o0."Key") AS "Min", max(o0."OrderDate") FILTER (WHERE 1 = o0."Key") AS "Max", COALESCE(sum(o0."OrderID") FILTER (WHERE 1 = o0."Key"), 0)::int AS "Sum", avg(o0."OrderID"::double precision) FILTER (WHERE 1 = o0."Key") AS "Average" FROM ( SELECT o."OrderID", o."OrderDate", 1 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" -ORDER BY t."Key" NULLS FIRST +) AS o0 +GROUP BY o0."Key" +ORDER BY o0."Key" NULLS FIRST """); } @@ -745,12 +745,12 @@ public override async Task GroupBy_param_Select_Sum_Min_Key_Max_Avg(bool async) """ @__a_0='2' -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum", min(t."OrderID") AS "Min", t."Key", max(t."OrderID") AS "Max", avg(t."OrderID"::double precision) AS "Avg" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum", min(o0."OrderID") AS "Min", o0."Key", max(o0."OrderID") AS "Max", avg(o0."OrderID"::double precision) AS "Avg" FROM ( SELECT o."OrderID", @__a_0 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -762,12 +762,12 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum(bool a """ @__a_0='2' -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum" FROM ( SELECT o."OrderID", @__a_0 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -779,12 +779,12 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum2(bool """ @__a_0='2' -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum" FROM ( SELECT o."OrderID", @__a_0 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -796,12 +796,12 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum3(bool """ @__a_0='2' -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum" FROM ( SELECT o."OrderID", @__a_0 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -813,12 +813,12 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum_Min_Ke """ @__a_0='2' -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum", t."Key" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum", o0."Key" FROM ( SELECT o."OrderID", @__a_0 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -828,13 +828,13 @@ public override async Task GroupBy_anonymous_key_type_mismatch_with_aggregate(bo AssertSql( """ -SELECT count(*)::int AS "I0", t."I0" AS "I1" +SELECT count(*)::int AS "I0", o0."I0" AS "I1" FROM ( SELECT date_part('year', o."OrderDate")::int AS "I0" FROM "Orders" AS o -) AS t -GROUP BY t."I0" -ORDER BY t."I0" NULLS FIRST +) AS o0 +GROUP BY o0."I0" +ORDER BY o0."I0" NULLS FIRST """); } @@ -1081,12 +1081,12 @@ public override async Task GroupBy_conditional_properties(bool async) AssertSql( """ -SELECT t."OrderMonth", t."CustomerID" AS "Customer", count(*)::int AS "Count" +SELECT o0."OrderMonth", o0."CustomerID" AS "Customer", count(*)::int AS "Count" FROM ( SELECT o."CustomerID", NULL AS "OrderMonth" FROM "Orders" AS o -) AS t -GROUP BY t."OrderMonth", t."CustomerID" +) AS o0 +GROUP BY o0."OrderMonth", o0."CustomerID" """); } @@ -1096,12 +1096,12 @@ public override async Task GroupBy_empty_key_Aggregate(bool async) AssertSql( """ -SELECT COALESCE(sum(t."OrderID"), 0)::int +SELECT COALESCE(sum(o0."OrderID"), 0)::int FROM ( SELECT o."OrderID", 1 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -1111,12 +1111,12 @@ public override async Task GroupBy_empty_key_Aggregate_Key(bool async) AssertSql( """ -SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum" +SELECT COALESCE(sum(o0."OrderID"), 0)::int AS "Sum" FROM ( SELECT o."OrderID", 1 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -1140,14 +1140,14 @@ public override async Task OrderBy_Skip_GroupBy_Aggregate(bool async) """ @__p_0='80' -SELECT avg(t."OrderID"::double precision) +SELECT avg(o0."OrderID"::double precision) FROM ( SELECT o."OrderID", o."CustomerID" FROM "Orders" AS o ORDER BY o."OrderID" NULLS FIRST OFFSET @__p_0 -) AS t -GROUP BY t."CustomerID" +) AS o0 +GROUP BY o0."CustomerID" """); } @@ -1159,14 +1159,14 @@ public override async Task OrderBy_Take_GroupBy_Aggregate(bool async) """ @__p_0='500' -SELECT min(t."OrderID") +SELECT min(o0."OrderID") FROM ( SELECT o."OrderID", o."CustomerID" FROM "Orders" AS o ORDER BY o."OrderID" NULLS FIRST LIMIT @__p_0 -) AS t -GROUP BY t."CustomerID" +) AS o0 +GROUP BY o0."CustomerID" """); } @@ -1179,14 +1179,14 @@ public override async Task OrderBy_Skip_Take_GroupBy_Aggregate(bool async) @__p_1='500' @__p_0='80' -SELECT max(t."OrderID") +SELECT max(o0."OrderID") FROM ( SELECT o."OrderID", o."CustomerID" FROM "Orders" AS o ORDER BY o."OrderID" NULLS FIRST LIMIT @__p_1 OFFSET @__p_0 -) AS t -GROUP BY t."CustomerID" +) AS o0 +GROUP BY o0."CustomerID" """); } @@ -1196,12 +1196,12 @@ public override async Task Distinct_GroupBy_Aggregate(bool async) AssertSql( """ -SELECT t."CustomerID" AS "Key", count(*)::int AS c +SELECT o0."CustomerID" AS "Key", count(*)::int AS c FROM ( SELECT DISTINCT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" FROM "Orders" AS o -) AS t -GROUP BY t."CustomerID" +) AS o0 +GROUP BY o0."CustomerID" """); } @@ -1211,12 +1211,12 @@ public override async Task Anonymous_projection_Distinct_GroupBy_Aggregate(bool AssertSql( """ -SELECT t."EmployeeID" AS "Key", count(*)::int AS c +SELECT o0."EmployeeID" AS "Key", count(*)::int AS c FROM ( SELECT DISTINCT o."OrderID", o."EmployeeID" FROM "Orders" AS o -) AS t -GROUP BY t."EmployeeID" +) AS o0 +GROUP BY o0."EmployeeID" """); } @@ -1269,22 +1269,22 @@ public override async Task Join_complex_GroupBy_Aggregate(bool async) @__p_2='50' @__p_1='10' -SELECT t0."CustomerID" AS "Key", avg(t."OrderID"::double precision) AS "Count" +SELECT c0."CustomerID" AS "Key", avg(o0."OrderID"::double precision) AS "Count" FROM ( SELECT o."OrderID", o."CustomerID" FROM "Orders" AS o WHERE o."OrderID" < 10400 ORDER BY o."OrderDate" NULLS FIRST LIMIT @__p_0 -) AS t +) AS o0 INNER JOIN ( SELECT c."CustomerID" FROM "Customers" AS c WHERE c."CustomerID" NOT IN ('DRACD', 'FOLKO') ORDER BY c."City" NULLS FIRST LIMIT @__p_2 OFFSET @__p_1 -) AS t0 ON t."CustomerID" = t0."CustomerID" -GROUP BY t0."CustomerID" +) AS c0 ON o0."CustomerID" = c0."CustomerID" +GROUP BY c0."CustomerID" """); } @@ -1377,23 +1377,23 @@ public override async Task GroupJoin_complex_GroupBy_Aggregate(bool async) @__p_0='10' @__p_2='100' -SELECT t0."CustomerID" AS "Key", avg(t0."OrderID"::double precision) AS "Count" +SELECT o0."CustomerID" AS "Key", avg(o0."OrderID"::double precision) AS "Count" FROM ( SELECT c."CustomerID" FROM "Customers" AS c WHERE c."CustomerID" NOT IN ('DRACD', 'FOLKO') ORDER BY c."City" NULLS FIRST LIMIT @__p_1 OFFSET @__p_0 -) AS t +) AS c0 INNER JOIN ( SELECT o."OrderID", o."CustomerID" FROM "Orders" AS o WHERE o."OrderID" < 10400 ORDER BY o."OrderDate" NULLS FIRST LIMIT @__p_2 -) AS t0 ON t."CustomerID" = t0."CustomerID" -WHERE t0."OrderID" > 10300 -GROUP BY t0."CustomerID" +) AS o0 ON c0."CustomerID" = o0."CustomerID" +WHERE o0."OrderID" > 10300 +GROUP BY o0."CustomerID" """); } @@ -1431,7 +1431,7 @@ public override async Task Union_simple_groupby(bool async) AssertSql( """ -SELECT t."City" AS "Key", count(*)::int AS "Total" +SELECT u."City" AS "Key", count(*)::int AS "Total" FROM ( 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 @@ -1440,8 +1440,8 @@ public override async Task Union_simple_groupby(bool async) SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM "Customers" AS c0 WHERE c0."City" = 'México D.F.' -) AS t -GROUP BY t."City" +) AS u +GROUP BY u."City" """); } @@ -1477,12 +1477,12 @@ public override async Task GroupBy_after_anonymous_projection_and_distinct_follo AssertSql( """ -SELECT t."CustomerID" AS "Key", count(*)::int AS "Count" +SELECT o0."CustomerID" AS "Key", count(*)::int AS "Count" FROM ( SELECT DISTINCT o."CustomerID", o."OrderID" FROM "Orders" AS o -) AS t -GROUP BY t."CustomerID" +) AS o0 +GROUP BY o0."CustomerID" """); } @@ -1492,13 +1492,13 @@ public override async Task GroupBy_complex_key_aggregate(bool async) AssertSql( """ -SELECT t."Key", count(*)::int AS "Count" +SELECT s."Key", count(*)::int AS "Count" FROM ( SELECT substring(c."CustomerID", 1, 1) AS "Key" FROM "Orders" AS o LEFT JOIN "Customers" AS c ON o."CustomerID" = c."CustomerID" -) AS t -GROUP BY t."Key" +) AS s +GROUP BY s."Key" """); } @@ -1508,15 +1508,15 @@ public override async Task GroupBy_complex_key_aggregate_2(bool async) AssertSql( """ -SELECT t."Key" AS "Month", COALESCE(sum(t."OrderID"), 0)::int AS "Total", ( - SELECT COALESCE(sum(o0."OrderID"), 0)::int - FROM "Orders" AS o0 - WHERE date_part('month', o0."OrderDate")::int = t."Key" OR (o0."OrderDate" IS NULL AND t."Key" IS NULL)) AS "Payment" +SELECT o0."Key" AS "Month", COALESCE(sum(o0."OrderID"), 0)::int AS "Total", ( + SELECT COALESCE(sum(o1."OrderID"), 0)::int + FROM "Orders" AS o1 + WHERE date_part('month', o1."OrderDate")::int = o0."Key" OR (o1."OrderDate" IS NULL AND o0."Key" IS NULL)) AS "Payment" FROM ( SELECT o."OrderID", date_part('month', o."OrderDate")::int AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -1596,7 +1596,7 @@ public override async Task GroupBy_aggregate_Pushdown(bool async) @__p_0='20' @__p_1='4' -SELECT t."CustomerID" +SELECT o0."CustomerID" FROM ( SELECT o."CustomerID" FROM "Orders" AS o @@ -1604,8 +1604,8 @@ GROUP BY o."CustomerID" HAVING count(*)::int > 10 ORDER BY o."CustomerID" NULLS FIRST LIMIT @__p_0 -) AS t -ORDER BY t."CustomerID" NULLS FIRST +) AS o0 +ORDER BY o0."CustomerID" NULLS FIRST OFFSET @__p_1 """); } @@ -1619,7 +1619,7 @@ public override async Task GroupBy_aggregate_using_grouping_key_Pushdown(bool as @__p_0='20' @__p_1='4' -SELECT t."Key", t."Max" +SELECT o0."Key", o0."Max" FROM ( SELECT o."CustomerID" AS "Key", max(o."CustomerID") AS "Max" FROM "Orders" AS o @@ -1627,8 +1627,8 @@ GROUP BY o."CustomerID" HAVING count(*)::int > 10 ORDER BY o."CustomerID" NULLS FIRST LIMIT @__p_0 -) AS t -ORDER BY t."Key" NULLS FIRST +) AS o0 +ORDER BY o0."Key" NULLS FIRST OFFSET @__p_1 """); } @@ -1642,7 +1642,7 @@ public override async Task GroupBy_aggregate_Pushdown_followed_by_projecting_Len @__p_0='20' @__p_1='4' -SELECT length(t."CustomerID")::int +SELECT length(o0."CustomerID")::int FROM ( SELECT o."CustomerID" FROM "Orders" AS o @@ -1650,8 +1650,8 @@ GROUP BY o."CustomerID" HAVING count(*)::int > 10 ORDER BY o."CustomerID" NULLS FIRST LIMIT @__p_0 -) AS t -ORDER BY t."CustomerID" NULLS FIRST +) AS o0 +ORDER BY o0."CustomerID" NULLS FIRST OFFSET @__p_1 """); } @@ -1673,8 +1673,8 @@ GROUP BY o."CustomerID" HAVING count(*)::int > 10 ORDER BY o."CustomerID" NULLS FIRST LIMIT @__p_0 -) AS t -ORDER BY t."CustomerID" NULLS FIRST +) AS o0 +ORDER BY o0."CustomerID" NULLS FIRST OFFSET @__p_1 """); } @@ -1711,12 +1711,12 @@ public override async Task GroupBy_count_filter(bool async) AssertSql( """ -SELECT t."Key" AS "Name", count(*)::int AS "Count" +SELECT o0."Key" AS "Name", count(*)::int AS "Count" FROM ( SELECT 'Order' AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" HAVING count(*)::int > 0 """); } @@ -1741,15 +1741,15 @@ public override async Task GroupBy_Aggregate_Join(bool async) AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o1."OrderID", o1."CustomerID", o1."EmployeeID", o1."OrderDate" FROM ( SELECT o."CustomerID", max(o."OrderID") AS "LastOrderID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 5 -) AS t -INNER JOIN "Customers" AS c ON t."CustomerID" = c."CustomerID" -INNER JOIN "Orders" AS o0 ON t."LastOrderID" = o0."OrderID" +) AS o0 +INNER JOIN "Customers" AS c ON o0."CustomerID" = c."CustomerID" +INNER JOIN "Orders" AS o1 ON o0."LastOrderID" = o1."OrderID" """); } @@ -1762,11 +1762,11 @@ public override async Task GroupBy_Aggregate_Join_converted_from_SelectMany(bool 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 INNER JOIN ( - SELECT o."CustomerID", max(o."OrderID") AS "LastOrderID" + SELECT o."CustomerID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 5 -) AS t ON c."CustomerID" = t."CustomerID" +) AS o0 ON c."CustomerID" = o0."CustomerID" """); } @@ -1779,11 +1779,11 @@ public override async Task GroupBy_Aggregate_LeftJoin_converted_from_SelectMany( 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 LEFT JOIN ( - SELECT o."CustomerID", max(o."OrderID") AS "LastOrderID" + SELECT o."CustomerID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 5 -) AS t ON c."CustomerID" = t."CustomerID" +) AS o0 ON c."CustomerID" = o0."CustomerID" """); } @@ -1793,15 +1793,15 @@ public override async Task Join_GroupBy_Aggregate_multijoins(bool async) AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o1."OrderID", o1."CustomerID", o1."EmployeeID", o1."OrderDate" FROM "Customers" AS c INNER JOIN ( SELECT o."CustomerID", max(o."OrderID") AS "LastOrderID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 5 -) AS t ON c."CustomerID" = t."CustomerID" -INNER JOIN "Orders" AS o0 ON t."LastOrderID" = o0."OrderID" +) AS o0 ON c."CustomerID" = o0."CustomerID" +INNER JOIN "Orders" AS o1 ON o0."LastOrderID" = o1."OrderID" """); } @@ -1811,14 +1811,14 @@ public override async Task Join_GroupBy_Aggregate_single_join(bool async) AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", t."LastOrderID" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o0."LastOrderID" FROM "Customers" AS c INNER JOIN ( SELECT o."CustomerID", max(o."OrderID") AS "LastOrderID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 5 -) AS t ON c."CustomerID" = t."CustomerID" +) AS o0 ON c."CustomerID" = o0."CustomerID" """); } @@ -1828,15 +1828,15 @@ public override async Task Join_GroupBy_Aggregate_with_another_join(bool async) AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", t."LastOrderID", o0."OrderID" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o0."LastOrderID", o1."OrderID" FROM "Customers" AS c INNER JOIN ( SELECT o."CustomerID", max(o."OrderID") AS "LastOrderID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 5 -) AS t ON c."CustomerID" = t."CustomerID" -INNER JOIN "Orders" AS o0 ON c."CustomerID" = o0."CustomerID" +) AS o0 ON c."CustomerID" = o0."CustomerID" +INNER JOIN "Orders" AS o1 ON c."CustomerID" = o1."CustomerID" """); } @@ -1846,17 +1846,17 @@ public override async Task Join_GroupBy_Aggregate_distinct_single_join(bool asyn AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", t0."LastOrderID" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o1."LastOrderID" FROM "Customers" AS c INNER JOIN ( - SELECT DISTINCT t."CustomerID", max(t."OrderID") AS "LastOrderID" + SELECT DISTINCT o0."CustomerID", max(o0."OrderID") AS "LastOrderID" FROM ( SELECT o."OrderID", o."CustomerID", date_part('year', o."OrderDate")::int AS "Year" FROM "Orders" AS o - ) AS t - GROUP BY t."CustomerID", t."Year" + ) AS o0 + GROUP BY o0."CustomerID", o0."Year" HAVING count(*)::int > 5 -) AS t0 ON c."CustomerID" = t0."CustomerID" +) AS o1 ON c."CustomerID" = o1."CustomerID" """); } @@ -1866,14 +1866,14 @@ public override async Task Join_GroupBy_Aggregate_with_left_join(bool async) AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", t."LastOrderID" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o0."LastOrderID" FROM "Customers" AS c LEFT JOIN ( SELECT o."CustomerID", max(o."OrderID") AS "LastOrderID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 5 -) AS t ON c."CustomerID" = t."CustomerID" +) AS o0 ON c."CustomerID" = o0."CustomerID" WHERE c."CustomerID" LIKE 'A%' """); } @@ -1884,18 +1884,18 @@ public override async Task Join_GroupBy_Aggregate_in_subquery(bool async) AssertSql( """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate", t0."CustomerID", t0."Address", t0."City", t0."CompanyName", t0."ContactName", t0."ContactTitle", t0."Country", t0."Fax", t0."Phone", t0."PostalCode", t0."Region" +SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate", s."CustomerID", s."Address", s."City", s."CompanyName", s."ContactName", s."ContactTitle", s."Country", s."Fax", s."Phone", s."PostalCode", s."Region" FROM "Orders" AS o INNER JOIN ( 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 INNER JOIN ( - SELECT o0."CustomerID", max(o0."OrderID") AS "LastOrderID" + SELECT o0."CustomerID" FROM "Orders" AS o0 GROUP BY o0."CustomerID" HAVING count(*)::int > 5 - ) AS t ON c."CustomerID" = t."CustomerID" -) AS t0 ON o."CustomerID" = t0."CustomerID" + ) AS o1 ON c."CustomerID" = o1."CustomerID" +) AS s ON o."CustomerID" = s."CustomerID" WHERE o."OrderID" < 10400 """); } @@ -1906,14 +1906,14 @@ public override async Task Join_GroupBy_Aggregate_on_key(bool async) AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", t."LastOrderID" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o0."LastOrderID" FROM "Customers" AS c INNER JOIN ( SELECT o."CustomerID" AS "Key", max(o."OrderID") AS "LastOrderID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 5 -) AS t ON c."CustomerID" = t."Key" +) AS o0 ON c."CustomerID" = o0."Key" """); } @@ -1959,13 +1959,13 @@ public override async Task Distinct_GroupBy_OrderBy_key(bool async) AssertSql( """ -SELECT t."CustomerID" AS "Key", count(*)::int AS c +SELECT o0."CustomerID" AS "Key", count(*)::int AS c FROM ( SELECT DISTINCT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" FROM "Orders" AS o -) AS t -GROUP BY t."CustomerID" -ORDER BY t."CustomerID" NULLS FIRST +) AS o0 +GROUP BY o0."CustomerID" +ORDER BY o0."CustomerID" NULLS FIRST """); } @@ -1998,13 +1998,13 @@ public override async Task Select_uncorrelated_collection_with_groupby_works(boo AssertSql( """ -SELECT c."CustomerID", t."OrderID" +SELECT c."CustomerID", o0."OrderID" FROM "Customers" AS c LEFT JOIN LATERAL ( SELECT o."OrderID" FROM "Orders" AS o GROUP BY o."OrderID" -) AS t ON TRUE +) AS o0 ON TRUE WHERE c."CustomerID" LIKE 'A%' ORDER BY c."CustomerID" NULLS FIRST """); @@ -2016,20 +2016,20 @@ public override async Task Select_uncorrelated_collection_with_groupby_multiple_ AssertSql( """ -SELECT o."OrderID", t."ProductID", t0.c, t0."ProductID" +SELECT o."OrderID", p1."ProductID", p2.c, p2."ProductID" FROM "Orders" AS o LEFT JOIN LATERAL ( SELECT p."ProductID" FROM "Products" AS p GROUP BY p."ProductID" -) AS t ON TRUE +) AS p1 ON TRUE LEFT JOIN LATERAL ( SELECT count(*)::int AS c, p0."ProductID" FROM "Products" AS p0 GROUP BY p0."ProductID" -) AS t0 ON TRUE +) AS p2 ON TRUE WHERE o."CustomerID" LIKE 'A%' -ORDER BY o."OrderID" NULLS FIRST, t."ProductID" NULLS FIRST +ORDER BY o."OrderID" NULLS FIRST, p1."ProductID" NULLS FIRST """); } @@ -2293,22 +2293,22 @@ public override async Task GroupBy_Shadow2(bool async) AssertSql( """ -SELECT t0."EmployeeID", t0."City", t0."Country", t0."FirstName", t0."ReportsTo", t0."Title" +SELECT e3."EmployeeID", e3."City", e3."Country", e3."FirstName", e3."ReportsTo", e3."Title" FROM ( SELECT e."Title" FROM "Employees" AS e WHERE e."Title" = 'Sales Representative' AND e."EmployeeID" = 1 GROUP BY e."Title" -) AS t +) AS e1 LEFT JOIN ( - SELECT t1."EmployeeID", t1."City", t1."Country", t1."FirstName", t1."ReportsTo", t1."Title" + SELECT e2."EmployeeID", e2."City", e2."Country", e2."FirstName", e2."ReportsTo", e2."Title" FROM ( SELECT e0."EmployeeID", e0."City", e0."Country", e0."FirstName", e0."ReportsTo", e0."Title", ROW_NUMBER() OVER(PARTITION BY e0."Title" ORDER BY e0."EmployeeID" NULLS FIRST) AS row FROM "Employees" AS e0 WHERE e0."Title" = 'Sales Representative' AND e0."EmployeeID" = 1 - ) AS t1 - WHERE t1.row <= 1 -) AS t0 ON t."Title" = t0."Title" + ) AS e2 + WHERE e2.row <= 1 +) AS e3 ON e1."Title" = e3."Title" """); } @@ -2335,14 +2335,14 @@ public override async Task GroupBy_select_grouping_list(bool async) AssertSql( """ -SELECT t."City", c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" +SELECT c1."City", c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM ( SELECT c."City" FROM "Customers" AS c GROUP BY c."City" -) AS t -LEFT JOIN "Customers" AS c0 ON t."City" = c0."City" -ORDER BY t."City" NULLS FIRST +) AS c1 +LEFT JOIN "Customers" AS c0 ON c1."City" = c0."City" +ORDER BY c1."City" NULLS FIRST """); } @@ -2352,14 +2352,14 @@ public override async Task GroupBy_select_grouping_array(bool async) AssertSql( """ -SELECT t."City", c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" +SELECT c1."City", c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM ( SELECT c."City" FROM "Customers" AS c GROUP BY c."City" -) AS t -LEFT JOIN "Customers" AS c0 ON t."City" = c0."City" -ORDER BY t."City" NULLS FIRST +) AS c1 +LEFT JOIN "Customers" AS c0 ON c1."City" = c0."City" +ORDER BY c1."City" NULLS FIRST """); } @@ -2369,18 +2369,18 @@ public override async Task GroupBy_select_grouping_composed_list(bool async) AssertSql( """ -SELECT t."City", t0."CustomerID", t0."Address", t0."City", t0."CompanyName", t0."ContactName", t0."ContactTitle", t0."Country", t0."Fax", t0."Phone", t0."PostalCode", t0."Region" +SELECT c1."City", c2."CustomerID", c2."Address", c2."City", c2."CompanyName", c2."ContactName", c2."ContactTitle", c2."Country", c2."Fax", c2."Phone", c2."PostalCode", c2."Region" FROM ( SELECT c."City" FROM "Customers" AS c GROUP BY c."City" -) AS t +) AS c1 LEFT JOIN ( SELECT c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM "Customers" AS c0 WHERE c0."CustomerID" LIKE 'A%' -) AS t0 ON t."City" = t0."City" -ORDER BY t."City" NULLS FIRST +) AS c2 ON c1."City" = c2."City" +ORDER BY c1."City" NULLS FIRST """); } @@ -2390,14 +2390,14 @@ public override async Task GroupBy_select_grouping_composed_list_2(bool async) AssertSql( """ -SELECT t."City", c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" +SELECT c1."City", c0."CustomerID", c0."Address", c0."City", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM ( SELECT c."City" FROM "Customers" AS c GROUP BY c."City" -) AS t -LEFT JOIN "Customers" AS c0 ON t."City" = c0."City" -ORDER BY t."City" NULLS FIRST, c0."CustomerID" NULLS FIRST +) AS c1 +LEFT JOIN "Customers" AS c0 ON c1."City" = c0."City" +ORDER BY c1."City" NULLS FIRST, c0."CustomerID" NULLS FIRST """); } @@ -2416,10 +2416,10 @@ public override async Task Count_after_GroupBy_aggregate(bool async) """ SELECT count(*)::int FROM ( - SELECT o."CustomerID" + SELECT 1 FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o0 """); } @@ -2431,10 +2431,10 @@ public override async Task LongCount_after_GroupBy_aggregate(bool async) """ SELECT count(*) FROM ( - SELECT o."CustomerID" + SELECT 1 FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o0 """); } @@ -2480,21 +2480,21 @@ public override async Task MinMax_after_GroupBy_aggregate(bool async) AssertSql( """ -SELECT min(t.c) +SELECT min(o0.c) FROM ( SELECT COALESCE(sum(o."OrderID"), 0)::int AS c FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o0 """, // """ -SELECT max(t.c) +SELECT max(o0.c) FROM ( SELECT COALESCE(sum(o."OrderID"), 0)::int AS c FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o0 """); } @@ -2547,10 +2547,10 @@ public override async Task Count_after_GroupBy_without_aggregate(bool async) """ SELECT count(*)::int FROM ( - SELECT o."CustomerID" + SELECT 1 FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o0 """); } @@ -2562,11 +2562,11 @@ public override async Task Count_with_predicate_after_GroupBy_without_aggregate( """ SELECT count(*)::int FROM ( - SELECT o."CustomerID" + SELECT 1 FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 1 -) AS t +) AS o0 """); } @@ -2578,10 +2578,10 @@ public override async Task LongCount_after_GroupBy_without_aggregate(bool async) """ SELECT count(*) FROM ( - SELECT o."CustomerID" + SELECT 1 FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o0 """); } @@ -2593,11 +2593,11 @@ public override async Task LongCount_with_predicate_after_GroupBy_without_aggreg """ SELECT count(*) FROM ( - SELECT o."CustomerID" + SELECT 1 FROM "Orders" AS o GROUP BY o."CustomerID" HAVING count(*)::int > 1 -) AS t +) AS o0 """); } @@ -2648,16 +2648,16 @@ public override async Task GroupBy_aggregate_followed_by_another_GroupBy_aggrega AssertSql( """ -SELECT t0."Key0" AS "Key", COALESCE(sum(t0."Count"), 0)::int AS "Count" +SELECT o1."Key0" AS "Key", COALESCE(sum(o1."Count"), 0)::int AS "Count" FROM ( - SELECT t."Count", 1 AS "Key0" + SELECT o0."Count", 1 AS "Key0" FROM ( SELECT count(*)::int AS "Count" FROM "Orders" AS o GROUP BY o."CustomerID" - ) AS t -) AS t0 -GROUP BY t0."Key0" + ) AS o0 +) AS o1 +GROUP BY o1."Key0" """); } @@ -2673,12 +2673,12 @@ SELECT 1 WHERE o."OrderID" = o0."OrderID" AND o0."ProductID" < 25) AS "HasOrderDetails", ( SELECT count(*)::int FROM ( - SELECT p."ProductName" + SELECT 1 FROM "Order Details" AS o1 INNER JOIN "Products" AS p ON o1."ProductID" = p."ProductID" WHERE o."OrderID" = o1."OrderID" AND o1."ProductID" < 25 GROUP BY p."ProductName" - ) AS t) > 1 AS "HasMultipleProducts" + ) AS s) > 1 AS "HasMultipleProducts" FROM "Orders" AS o WHERE o."OrderDate" IS NOT NULL """); @@ -2692,10 +2692,10 @@ public override async Task GroupBy_nominal_type_count(bool async) """ SELECT count(*)::int FROM ( - SELECT o."CustomerID" + SELECT 1 FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o0 """); } @@ -2717,12 +2717,12 @@ public override async Task GroupBy_based_on_renamed_property_complex(bool async) AssertSql( """ -SELECT t."Renamed" AS "Key", count(*)::int AS "Count" +SELECT c0."Renamed" AS "Key", count(*)::int AS "Count" FROM ( SELECT DISTINCT c."City" AS "Renamed", c."CustomerID" FROM "Customers" AS c -) AS t -GROUP BY t."Renamed" +) AS c0 +GROUP BY c0."Renamed" """); } @@ -2746,12 +2746,12 @@ public override async Task Odata_groupby_empty_key(bool async) AssertSql( """ -SELECT 'TotalAmount' AS "Name", COALESCE(sum(t."OrderID"::numeric), 0.0) AS "Value" +SELECT 'TotalAmount' AS "Name", COALESCE(sum(o0."OrderID"::numeric), 0.0) AS "Value" FROM ( SELECT o."OrderID", 1 AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -2837,14 +2837,14 @@ public override async Task GroupBy_with_order_by_skip_and_another_order_by(bool """ @__p_0='80' -SELECT COALESCE(sum(t."OrderID"), 0)::int +SELECT COALESCE(sum(o0."OrderID"), 0)::int FROM ( SELECT o."OrderID", o."CustomerID" FROM "Orders" AS o ORDER BY o."CustomerID" NULLS FIRST, o."OrderID" NULLS FIRST OFFSET @__p_0 -) AS t -GROUP BY t."CustomerID" +) AS o0 +GROUP BY o0."CustomerID" """); } @@ -2941,13 +2941,13 @@ public override async Task GroupBy_aggregate_join_with_grouping_key(bool async) AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", t."Count" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o0."Count" FROM ( SELECT o."CustomerID" AS "Key", count(*)::int AS "Count" FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t -INNER JOIN "Customers" AS c ON t."Key" = c."CustomerID" +) AS o0 +INNER JOIN "Customers" AS c ON o0."Key" = c."CustomerID" """); } @@ -2962,8 +2962,8 @@ public override async Task GroupBy_aggregate_join_with_group_result(bool async) SELECT o."CustomerID" AS "Key", max(o."OrderDate") AS "LastOrderDate" FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t -INNER JOIN "Orders" AS o0 ON (t."Key" = o0."CustomerID" OR (t."Key" IS NULL AND o0."CustomerID" IS NULL)) AND (t."LastOrderDate" = o0."OrderDate" OR (t."LastOrderDate" IS NULL AND o0."OrderDate" IS NULL)) +) AS o1 +INNER JOIN "Orders" AS o0 ON (o1."Key" = o0."CustomerID" OR (o1."Key" IS NULL AND o0."CustomerID" IS NULL)) AND (o1."LastOrderDate" = o0."OrderDate" OR (o1."LastOrderDate" IS NULL AND o0."OrderDate" IS NULL)) """); } @@ -2975,14 +2975,14 @@ public override async Task GroupBy_aggregate_from_right_side_of_join(bool async) """ @__p_0='10' -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", t."Max" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", o0."Max" FROM "Customers" AS c INNER JOIN ( SELECT o."CustomerID" AS "Key", max(o."OrderDate") AS "Max" FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t ON c."CustomerID" = t."Key" -ORDER BY t."Max" NULLS FIRST, c."CustomerID" NULLS FIRST +) AS o0 ON c."CustomerID" = o0."Key" +ORDER BY o0."Max" NULLS FIRST, c."CustomerID" NULLS FIRST LIMIT @__p_0 OFFSET @__p_0 """); } @@ -2993,18 +2993,18 @@ public override async Task GroupBy_aggregate_join_another_GroupBy_aggregate(bool AssertSql( """ -SELECT t."Key", t."Total", t0."ThatYear" +SELECT o1."Key", o1."Total", o2."ThatYear" FROM ( SELECT o."CustomerID" AS "Key", count(*)::int AS "Total" FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o1 INNER JOIN ( SELECT o0."CustomerID" AS "Key", count(*)::int AS "ThatYear" FROM "Orders" AS o0 WHERE date_part('year', o0."OrderDate")::int = 1997 GROUP BY o0."CustomerID" -) AS t0 ON t."Key" = t0."Key" +) AS o2 ON o1."Key" = o2."Key" """); } @@ -3016,13 +3016,13 @@ public override async Task GroupBy_aggregate_after_skip_0_take_0(bool async) """ @__p_0='0' -SELECT t."CustomerID" AS "Key", count(*)::int AS "Total" +SELECT o0."CustomerID" AS "Key", count(*)::int AS "Total" FROM ( SELECT o."CustomerID" FROM "Orders" AS o LIMIT @__p_0 OFFSET @__p_0 -) AS t -GROUP BY t."CustomerID" +) AS o0 +GROUP BY o0."CustomerID" """); } @@ -3048,16 +3048,16 @@ public override async Task GroupBy_aggregate_followed_another_GroupBy_aggregate( AssertSql( """ -SELECT t0."CustomerID" AS "Key", count(*)::int AS "Count" +SELECT o1."CustomerID" AS "Key", count(*)::int AS "Count" FROM ( - SELECT t."CustomerID" + SELECT o0."CustomerID" FROM ( SELECT o."CustomerID", date_part('year', o."OrderDate")::int AS "Year" FROM "Orders" AS o - ) AS t - GROUP BY t."CustomerID", t."Year" -) AS t0 -GROUP BY t0."CustomerID" + ) AS o0 + GROUP BY o0."CustomerID", o0."Year" +) AS o1 +GROUP BY o1."CustomerID" """); } @@ -3072,9 +3072,9 @@ public override async Task GroupBy_aggregate_without_selectMany_selecting_first( SELECT min(o."OrderID") AS c FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t +) AS o1 CROSS JOIN "Orders" AS o0 -WHERE o0."OrderID" = t.c +WHERE o0."OrderID" = o1.c """); } @@ -3101,14 +3101,14 @@ public override async Task GroupBy_selecting_grouping_key_list(bool async) AssertSql( """ -SELECT t."CustomerID", o0."CustomerID", o0."OrderID" +SELECT o1."CustomerID", o0."CustomerID", o0."OrderID" FROM ( SELECT o."CustomerID" FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t -LEFT JOIN "Orders" AS o0 ON t."CustomerID" = o0."CustomerID" -ORDER BY t."CustomerID" NULLS FIRST +) AS o1 +LEFT JOIN "Orders" AS o0 ON o1."CustomerID" = o0."CustomerID" +ORDER BY o1."CustomerID" NULLS FIRST """); } @@ -3118,12 +3118,12 @@ public override async Task GroupBy_with_grouping_key_using_Like(bool async) AssertSql( """ -SELECT t."Key", count(*)::int AS "Count" +SELECT o0."Key", count(*)::int AS "Count" FROM ( SELECT o."CustomerID" LIKE 'A%' AND o."CustomerID" IS NOT NULL AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -3133,12 +3133,12 @@ public override async Task GroupBy_with_grouping_key_DateTime_Day(bool async) AssertSql( """ -SELECT t."Key", count(*)::int AS "Count" +SELECT o0."Key", count(*)::int AS "Count" FROM ( SELECT date_part('day', o."OrderDate")::int AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -3160,14 +3160,14 @@ public override async Task Complex_query_with_groupBy_in_subquery1(bool async) AssertSql( """ -SELECT c."CustomerID", t."Sum", t."CustomerID" +SELECT c."CustomerID", o0."Sum", o0."CustomerID" FROM "Customers" AS c LEFT JOIN LATERAL ( SELECT COALESCE(sum(o."OrderID"), 0)::int AS "Sum", o."CustomerID" FROM "Orders" AS o WHERE c."CustomerID" = o."CustomerID" GROUP BY o."CustomerID" -) AS t ON TRUE +) AS o0 ON TRUE ORDER BY c."CustomerID" NULLS FIRST """); } @@ -3178,14 +3178,14 @@ public override async Task Complex_query_with_groupBy_in_subquery2(bool async) AssertSql( """ -SELECT c."CustomerID", t."Max", t."Sum", t."CustomerID" +SELECT c."CustomerID", o0."Max", o0."Sum", o0."CustomerID" FROM "Customers" AS c LEFT JOIN LATERAL ( SELECT max(length(o."CustomerID")::int) AS "Max", COALESCE(sum(o."OrderID"), 0)::int AS "Sum", o."CustomerID" FROM "Orders" AS o WHERE c."CustomerID" = o."CustomerID" GROUP BY o."CustomerID" -) AS t ON TRUE +) AS o0 ON TRUE ORDER BY c."CustomerID" NULLS FIRST """); } @@ -3196,13 +3196,13 @@ public override async Task Complex_query_with_groupBy_in_subquery3(bool async) AssertSql( """ -SELECT c."CustomerID", t."Max", t."Sum", t."CustomerID" +SELECT c."CustomerID", o0."Max", o0."Sum", o0."CustomerID" FROM "Customers" AS c LEFT JOIN LATERAL ( SELECT max(length(o."CustomerID")::int) AS "Max", COALESCE(sum(o."OrderID"), 0)::int AS "Sum", o."CustomerID" FROM "Orders" AS o GROUP BY o."CustomerID" -) AS t ON TRUE +) AS o0 ON TRUE ORDER BY c."CustomerID" NULLS FIRST """); } @@ -3282,7 +3282,7 @@ public override async Task GroupBy_scalar_subquery(bool async) AssertSql( """ -SELECT t."Key", count(*)::int AS "Count" +SELECT o0."Key", count(*)::int AS "Count" FROM ( SELECT ( SELECT c."ContactName" @@ -3290,8 +3290,8 @@ SELECT c."ContactName" WHERE c."CustomerID" = o."CustomerID" LIMIT 1) AS "Key" FROM "Orders" AS o -) AS t -GROUP BY t."Key" +) AS o0 +GROUP BY o0."Key" """); } @@ -3301,28 +3301,28 @@ public override async Task AsEnumerable_in_subquery_for_GroupBy(bool async) AssertSql( """ -SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", t2."OrderID", t2."CustomerID", t2."EmployeeID", t2."OrderDate", t2."CustomerID0" +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", s."OrderID", s."CustomerID", s."EmployeeID", s."OrderDate", s."CustomerID0" FROM "Customers" AS c LEFT JOIN LATERAL ( - SELECT t0."OrderID", t0."CustomerID", t0."EmployeeID", t0."OrderDate", t."CustomerID" AS "CustomerID0" + SELECT o3."OrderID", o3."CustomerID", o3."EmployeeID", o3."OrderDate", o1."CustomerID" AS "CustomerID0" FROM ( SELECT o."CustomerID" FROM "Orders" AS o WHERE o."CustomerID" = c."CustomerID" GROUP BY o."CustomerID" - ) AS t + ) AS o1 LEFT JOIN ( - SELECT t1."OrderID", t1."CustomerID", t1."EmployeeID", t1."OrderDate" + SELECT o2."OrderID", o2."CustomerID", o2."EmployeeID", o2."OrderDate" FROM ( SELECT o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate", ROW_NUMBER() OVER(PARTITION BY o0."CustomerID" ORDER BY o0."OrderDate" DESC NULLS LAST) AS row FROM "Orders" AS o0 WHERE o0."CustomerID" = c."CustomerID" - ) AS t1 - WHERE t1.row <= 1 - ) AS t0 ON t."CustomerID" = t0."CustomerID" -) AS t2 ON TRUE + ) AS o2 + WHERE o2.row <= 1 + ) AS o3 ON o1."CustomerID" = o3."CustomerID" +) AS s ON TRUE WHERE c."CustomerID" LIKE 'F%' -ORDER BY c."CustomerID" NULLS FIRST, t2."CustomerID0" NULLS FIRST +ORDER BY c."CustomerID" NULLS FIRST, s."CustomerID0" NULLS FIRST """); } @@ -3414,24 +3414,24 @@ public override async Task Select_uncorrelated_collection_with_groupby_when_oute AssertSql( """ -SELECT t."City", t0."ProductID", t1.c, t1."ProductID" +SELECT s."City", p1."ProductID", p2.c, p2."ProductID" FROM ( SELECT DISTINCT c."City" FROM "Orders" AS o LEFT JOIN "Customers" AS c ON o."CustomerID" = c."CustomerID" WHERE o."CustomerID" LIKE 'A%' -) AS t +) AS s LEFT JOIN LATERAL ( SELECT p."ProductID" FROM "Products" AS p GROUP BY p."ProductID" -) AS t0 ON TRUE +) AS p1 ON TRUE LEFT JOIN LATERAL ( SELECT count(*)::int AS c, p0."ProductID" FROM "Products" AS p0 GROUP BY p0."ProductID" -) AS t1 ON TRUE -ORDER BY t."City" NULLS FIRST, t0."ProductID" NULLS FIRST +) AS p2 ON TRUE +ORDER BY s."City" NULLS FIRST, p1."ProductID" NULLS FIRST """); } @@ -3441,15 +3441,15 @@ public override async Task Select_correlated_collection_after_GroupBy_aggregate_ AssertSql( """ -SELECT t."CustomerID", o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" +SELECT c0."CustomerID", o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" FROM ( SELECT c."CustomerID" FROM "Customers" AS c GROUP BY c."CustomerID" HAVING c."CustomerID" LIKE 'F%' -) AS t -LEFT JOIN "Orders" AS o ON t."CustomerID" = o."CustomerID" -ORDER BY t."CustomerID" NULLS FIRST +) AS c0 +LEFT JOIN "Orders" AS o ON c0."CustomerID" = o."CustomerID" +ORDER BY c0."CustomerID" NULLS FIRST """); } @@ -3459,15 +3459,15 @@ public override async Task Select_correlated_collection_after_GroupBy_aggregate_ AssertSql( """ -SELECT t."CustomerID", o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate" +SELECT o1."CustomerID", o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate" FROM ( SELECT o."CustomerID" FROM "Orders" AS o GROUP BY o."CustomerID" HAVING o."CustomerID" LIKE 'F%' -) AS t -LEFT JOIN "Orders" AS o0 ON t."CustomerID" = o0."CustomerID" -ORDER BY t."CustomerID" NULLS FIRST +) AS o1 +LEFT JOIN "Orders" AS o0 ON o1."CustomerID" = o0."CustomerID" +ORDER BY o1."CustomerID" NULLS FIRST """); } @@ -3481,7 +3481,7 @@ public override async Task Complex_query_with_group_by_in_subquery5(bool async) AssertSql( """ -SELECT t.c, t."ProductID", t0."CustomerID", t0."City" +SELECT s.c, s."ProductID", c1."CustomerID", c1."City" FROM ( SELECT COALESCE(sum(o."ProductID" + o."OrderID" * 1000), 0)::int AS c, o."ProductID", min(o."OrderID" / 100) AS c0 FROM "Order Details" AS o @@ -3489,13 +3489,13 @@ SELECT COALESCE(sum(o."ProductID" + o."OrderID" * 1000), 0)::int AS c, o."Produc LEFT JOIN "Customers" AS c ON o0."CustomerID" = c."CustomerID" WHERE c."CustomerID" = 'ALFKI' GROUP BY o."ProductID" -) AS t +) AS s LEFT JOIN LATERAL ( SELECT c0."CustomerID", c0."City" FROM "Customers" AS c0 - WHERE length(c0."CustomerID")::int < t.c0 -) AS t0 ON TRUE -ORDER BY t."ProductID" NULLS FIRST, t0."CustomerID" NULLS FIRST + WHERE length(c0."CustomerID")::int < s.c0 +) AS c1 ON TRUE +ORDER BY s."ProductID" NULLS FIRST, c1."CustomerID" NULLS FIRST """); } @@ -3505,27 +3505,27 @@ public override async Task Complex_query_with_groupBy_in_subquery4(bool async) AssertSql( """ -SELECT c."CustomerID", t1."Sum", t1."Count", t1."Key" +SELECT c."CustomerID", s1."Sum", s1."Count", s1."Key" FROM "Customers" AS c LEFT JOIN LATERAL ( - SELECT COALESCE(sum(t."OrderID"), 0)::int AS "Sum", ( + SELECT COALESCE(sum(s."OrderID"), 0)::int AS "Sum", ( SELECT count(*)::int FROM ( - SELECT o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate", c1."CustomerID" AS "CustomerID0", c1."Address", c1."City", c1."CompanyName", c1."ContactName", c1."ContactTitle", c1."Country", c1."Fax", c1."Phone", c1."PostalCode", c1."Region", COALESCE(c1."City", '') || COALESCE(o0."CustomerID", '') AS "Key" + SELECT o0."CustomerID", COALESCE(c1."City", '') || COALESCE(o0."CustomerID", '') AS "Key" FROM "Orders" AS o0 LEFT JOIN "Customers" AS c1 ON o0."CustomerID" = c1."CustomerID" WHERE c."CustomerID" = o0."CustomerID" - ) AS t0 - LEFT JOIN "Customers" AS c0 ON t0."CustomerID" = c0."CustomerID" - WHERE (t."Key" = t0."Key" OR (t."Key" IS NULL AND t0."Key" IS NULL)) AND COALESCE(c0."City", '') || COALESCE(t0."CustomerID", '') LIKE 'Lon%') AS "Count", t."Key" + ) AS s0 + LEFT JOIN "Customers" AS c2 ON s0."CustomerID" = c2."CustomerID" + WHERE (s."Key" = s0."Key" OR (s."Key" IS NULL AND s0."Key" IS NULL)) AND COALESCE(c2."City", '') || COALESCE(s0."CustomerID", '') LIKE 'Lon%') AS "Count", s."Key" FROM ( - SELECT o."OrderID", COALESCE(c2."City", '') || COALESCE(o."CustomerID", '') AS "Key" + SELECT o."OrderID", COALESCE(c0."City", '') || COALESCE(o."CustomerID", '') AS "Key" FROM "Orders" AS o - LEFT JOIN "Customers" AS c2 ON o."CustomerID" = c2."CustomerID" + LEFT JOIN "Customers" AS c0 ON o."CustomerID" = c0."CustomerID" WHERE c."CustomerID" = o."CustomerID" - ) AS t - GROUP BY t."Key" -) AS t1 ON TRUE + ) AS s + GROUP BY s."Key" +) AS s1 ON TRUE ORDER BY c."CustomerID" NULLS FIRST """); } @@ -3606,12 +3606,12 @@ public override async Task Final_GroupBy_complex_key_entity(bool async) AssertSql( """ -SELECT t."City", t."Region", t."Constant", t."CustomerID", t."Address", t."CompanyName", t."ContactName", t."ContactTitle", t."Country", t."Fax", t."Phone", t."PostalCode" +SELECT c0."City", c0."Region", c0."Constant", c0."CustomerID", c0."Address", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode" FROM ( SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", 1 AS "Constant" FROM "Customers" AS c -) AS t -ORDER BY t."City" NULLS FIRST, t."Region" NULLS FIRST, t."Constant" NULLS FIRST +) AS c0 +ORDER BY c0."City" NULLS FIRST, c0."Region" NULLS FIRST, c0."Constant" NULLS FIRST """); } @@ -3621,12 +3621,12 @@ public override async Task Final_GroupBy_nominal_type_entity(bool async) AssertSql( """ -SELECT t."City", t."Constant", t."CustomerID", t."Address", t."CompanyName", t."ContactName", t."ContactTitle", t."Country", t."Fax", t."Phone", t."PostalCode", t."Region" +SELECT c0."City", c0."Constant", c0."CustomerID", c0."Address", c0."CompanyName", c0."ContactName", c0."ContactTitle", c0."Country", c0."Fax", c0."Phone", c0."PostalCode", c0."Region" FROM ( SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region", 1 AS "Constant" FROM "Customers" AS c -) AS t -ORDER BY t."City" NULLS FIRST, t."Constant" NULLS FIRST +) AS c0 +ORDER BY c0."City" NULLS FIRST, c0."Constant" NULLS FIRST """); } @@ -3676,13 +3676,13 @@ public override async Task Final_GroupBy_property_entity_projecting_collection_c AssertSql( """ -SELECT c."City", c."CustomerID", t."OrderID", t."CustomerID", t."EmployeeID", t."OrderDate" +SELECT c."City", c."CustomerID", o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate" FROM "Customers" AS c LEFT JOIN ( SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" FROM "Orders" AS o WHERE o."OrderID" < 11000 -) AS t ON c."CustomerID" = t."CustomerID" +) AS o0 ON c."CustomerID" = o0."CustomerID" WHERE c."Country" = 'USA' ORDER BY c."City" NULLS FIRST, c."CustomerID" NULLS FIRST """); @@ -3694,21 +3694,21 @@ public override async Task Final_GroupBy_property_entity_projecting_collection_a AssertSql( """ -SELECT c."City", c."CustomerID", t."OrderID", t."CustomerID", t."EmployeeID", t."OrderDate", t0."OrderID", t0."CustomerID", t0."EmployeeID", t0."OrderDate" +SELECT c."City", c."CustomerID", o1."OrderID", o1."CustomerID", o1."EmployeeID", o1."OrderDate", o3."OrderID", o3."CustomerID", o3."EmployeeID", o3."OrderDate" FROM "Customers" AS c LEFT JOIN ( SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" FROM "Orders" AS o WHERE o."OrderID" < 11000 -) AS t ON c."CustomerID" = t."CustomerID" +) AS o1 ON c."CustomerID" = o1."CustomerID" LEFT JOIN ( - SELECT t1."OrderID", t1."CustomerID", t1."EmployeeID", t1."OrderDate" + SELECT o2."OrderID", o2."CustomerID", o2."EmployeeID", o2."OrderDate" FROM ( SELECT o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate", ROW_NUMBER() OVER(PARTITION BY o0."CustomerID" ORDER BY o0."OrderDate" DESC NULLS LAST) AS row FROM "Orders" AS o0 - ) AS t1 - WHERE t1.row <= 1 -) AS t0 ON c."CustomerID" = t0."CustomerID" + ) AS o2 + WHERE o2.row <= 1 +) AS o3 ON c."CustomerID" = o3."CustomerID" WHERE c."Country" = 'USA' ORDER BY c."City" NULLS FIRST, c."CustomerID" NULLS FIRST """); @@ -3769,29 +3769,29 @@ public override async Task GroupBy_complex_key_without_aggregate(bool async) AssertSql( """ -SELECT t0."Key", t1."OrderID", t1."CustomerID", t1."EmployeeID", t1."OrderDate", t1."CustomerID0" +SELECT s1."Key", s3."OrderID", s3."CustomerID", s3."EmployeeID", s3."OrderDate", s3."CustomerID0" FROM ( - SELECT t."Key" + SELECT s."Key" FROM ( SELECT substring(c."CustomerID", 1, 1) AS "Key" FROM "Orders" AS o LEFT JOIN "Customers" AS c ON o."CustomerID" = c."CustomerID" - ) AS t - GROUP BY t."Key" -) AS t0 + ) AS s + GROUP BY s."Key" +) AS s1 LEFT JOIN ( - SELECT t2."OrderID", t2."CustomerID", t2."EmployeeID", t2."OrderDate", t2."CustomerID0", t2."Key" + SELECT s2."OrderID", s2."CustomerID", s2."EmployeeID", s2."OrderDate", s2."CustomerID0", s2."Key" FROM ( - SELECT t3."OrderID", t3."CustomerID", t3."EmployeeID", t3."OrderDate", t3."CustomerID0", t3."Key", ROW_NUMBER() OVER(PARTITION BY t3."Key" ORDER BY t3."OrderID" NULLS FIRST, t3."CustomerID0" NULLS FIRST) AS row + SELECT s0."OrderID", s0."CustomerID", s0."EmployeeID", s0."OrderDate", s0."CustomerID0", s0."Key", ROW_NUMBER() OVER(PARTITION BY s0."Key" ORDER BY s0."OrderID" NULLS FIRST, s0."CustomerID0" NULLS FIRST) AS row FROM ( SELECT o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate", c0."CustomerID" AS "CustomerID0", substring(c0."CustomerID", 1, 1) AS "Key" FROM "Orders" AS o0 LEFT JOIN "Customers" AS c0 ON o0."CustomerID" = c0."CustomerID" - ) AS t3 - ) AS t2 - WHERE 1 < t2.row AND t2.row <= 3 -) AS t1 ON t0."Key" = t1."Key" -ORDER BY t0."Key" NULLS FIRST, t1."OrderID" NULLS FIRST + ) AS s0 + ) AS s2 + WHERE 1 < s2.row AND s2.row <= 3 +) AS s3 ON s1."Key" = s3."Key" +ORDER BY s1."Key" NULLS FIRST, s3."OrderID" NULLS FIRST """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs index 6fec39611..188c1a88a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs @@ -188,14 +188,14 @@ await Assert.ThrowsAsync( AssertSql( """ -SELECT t."CustomerID", o0."OrderID", o0."CustomerID", o0."EmployeeID", o0."OrderDate" +SELECT o0."CustomerID", o1."OrderID", o1."CustomerID", o1."EmployeeID", o1."OrderDate" FROM ( SELECT DISTINCT o."CustomerID" FROM "Orders" AS o WHERE o."OrderID" < 10300 -) AS t -LEFT JOIN "Orders" AS o0 ON t."CustomerID" = o0."CustomerID" -ORDER BY t."CustomerID" NULLS FIRST +) AS o0 +LEFT JOIN "Orders" AS o1 ON o0."CustomerID" = o1."CustomerID" +ORDER BY o0."CustomerID" NULLS FIRST """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindSetOperationsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindSetOperationsQueryNpgsqlTest.cs index 543082d04..316ffb4a9 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindSetOperationsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindSetOperationsQueryNpgsqlTest.cs @@ -135,7 +135,7 @@ await AssertQuery( AssertSql( """ -SELECT t0."OrderID", t0."CustomerID" +SELECT u0."OrderID", u0."CustomerID" FROM ( SELECT NULL::int AS "OrderID", o."CustomerID" FROM "Orders" AS o @@ -145,9 +145,9 @@ await AssertQuery( UNION SELECT o1."OrderID", o1."CustomerID" FROM "Orders" AS o1 -) AS t0 -WHERE t0."CustomerID" = ( - SELECT t1."CustomerID" +) AS u0 +WHERE u0."CustomerID" = ( + SELECT u2."CustomerID" FROM ( SELECT NULL::int AS "OrderID", o2."CustomerID" FROM "Orders" AS o2 @@ -157,10 +157,10 @@ SELECT t1."CustomerID" 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" + ) AS u2 + ORDER BY u2."CustomerID" NULLS FIRST + LIMIT 1) OR (u0."CustomerID" IS NULL AND ( + SELECT u2."CustomerID" FROM ( SELECT NULL::int AS "OrderID", o2."CustomerID" FROM "Orders" AS o2 @@ -170,8 +170,8 @@ SELECT t1."CustomerID" UNION SELECT o4."OrderID", o4."CustomerID" FROM "Orders" AS o4 - ) AS t1 - ORDER BY t1."CustomerID" NULLS FIRST + ) AS u2 + ORDER BY u2."CustomerID" NULLS FIRST LIMIT 1) IS NULL) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindSqlQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindSqlQueryNpgsqlTest.cs index 122773d93..d9f1bdd92 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindSqlQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindSqlQueryNpgsqlTest.cs @@ -29,10 +29,10 @@ public override async Task SqlQuery_composed_Contains(bool async) SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" FROM "Orders" AS o WHERE o."OrderID" IN ( - SELECT t."Value" + SELECT s."Value" FROM ( SELECT "ProductID" AS "Value" FROM "Products" - ) AS t + ) AS s ) """); } @@ -43,11 +43,11 @@ public override async Task SqlQuery_composed_Join(bool async) AssertSql( """ -SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate", t."Value"::int AS p +SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate", s."Value"::int AS p FROM "Orders" AS o INNER JOIN ( SELECT "ProductID" AS "Value" FROM "Products" -) AS t ON o."OrderID" = t."Value"::int +) AS s ON o."OrderID" = s."Value"::int """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 7700cb224..145973cee 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -48,10 +48,12 @@ public override async Task Inline_collection_of_nullable_ints_Contains_null(bool """); } - public override Task Inline_collection_Count_with_zero_values(bool async) - => AssertTranslationFailedWithDetails( - () => base.Inline_collection_Count_with_zero_values(async), - RelationalStrings.EmptyCollectionNotSupportedAsInlineQueryRoot); + public override async Task Inline_collection_Count_with_zero_values(bool async) + { + await base.Inline_collection_Count_with_zero_values(async); + + AssertSql(); + } public override async Task Inline_collection_Count_with_one_value(bool async) { @@ -98,10 +100,17 @@ SELECT count(*)::int """); } - public override Task Inline_collection_Contains_with_zero_values(bool async) - => AssertTranslationFailedWithDetails( - () => base.Inline_collection_Contains_with_zero_values(async), - RelationalStrings.EmptyCollectionNotSupportedAsInlineQueryRoot); + public override async Task Inline_collection_Contains_with_zero_values(bool async) + { + await base.Inline_collection_Contains_with_zero_values(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE FALSE +"""); + } public override async Task Inline_collection_Contains_with_one_value(bool async) { @@ -139,6 +148,18 @@ public override async Task Inline_collection_Contains_with_three_values(bool asy """); } + public override async Task Inline_collection_Contains_with_EF_Constant(bool async) + { + await base.Inline_collection_Contains_with_EF_Constant(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Id" IN (2, 999, 1000) +"""); + } + public override async Task Inline_collection_Contains_with_all_parameters(bool async) { await base.Inline_collection_Contains_with_all_parameters(async); @@ -206,6 +227,58 @@ public override async Task Inline_collection_negated_Contains_as_All(bool async) """); } + public override async Task Inline_collection_Min_with_two_values(bool async) + { + await base.Inline_collection_Min_with_two_values(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."Int") = 30 +"""); + } + + public override async Task Inline_collection_Max_with_two_values(bool async) + { + await base.Inline_collection_Max_with_two_values(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."Int") = 30 +"""); + } + + public override async Task Inline_collection_Min_with_three_values(bool async) + { + await base.Inline_collection_Min_with_three_values(async); + + AssertSql( + """ +@__i_0='25' + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."Int", @__i_0) = 25 +"""); + } + + public override async Task Inline_collection_Max_with_three_values(bool async) + { + await base.Inline_collection_Max_with_three_values(async); + + AssertSql( + """ +@__i_0='35' + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."Int", @__i_0) = 35 +"""); + } + public override async Task Parameter_collection_Count(bool async) { await base.Parameter_collection_Count(async); @@ -223,9 +296,9 @@ FROM unnest(@__ids_0) AS i(value) """); } - public override async Task Parameter_collection_of_ints_Contains(bool async) + public override async Task Parameter_collection_of_ints_Contains_int(bool async) { - await base.Parameter_collection_of_ints_Contains(async); + await base.Parameter_collection_of_ints_Contains_int(async); AssertSql( """ @@ -234,6 +307,36 @@ public override async Task Parameter_collection_of_ints_Contains(bool async) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE p."Int" = ANY (@__ints_0) +""", + // + """ +@__ints_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."Int" = ANY (@__ints_0) AND p."Int" = ANY (@__ints_0) IS NOT NULL) +"""); + } + + public override async Task Parameter_collection_of_ints_Contains_nullable_int(bool async) + { + await base.Parameter_collection_of_ints_Contains_nullable_int(async); + + AssertSql( + """ +@__ints_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."NullableInt" = ANY (@__ints_0) OR (p."NullableInt" IS NULL AND array_position(@__ints_0, NULL) IS NOT NULL) +""", + // + """ +@__ints_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."NullableInt" = ANY (@__ints_0) AND p."NullableInt" = ANY (@__ints_0) IS NOT NULL) AND (p."NullableInt" IS NOT NULL OR array_position(@__ints_0, NULL) IS NULL) """); } @@ -248,6 +351,14 @@ public override async Task Parameter_collection_of_nullable_ints_Contains_int(bo SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE p."Int" = ANY (@__nullableInts_0) +""", + // + """ +@__nullableInts_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."Int" = ANY (@__nullableInts_0) AND p."Int" = ANY (@__nullableInts_0) IS NOT NULL) """); } @@ -262,6 +373,36 @@ public override async Task Parameter_collection_of_nullable_ints_Contains_nullab SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE p."NullableInt" = ANY (@__nullableInts_0) OR (p."NullableInt" IS NULL AND array_position(@__nullableInts_0, NULL) IS NOT NULL) +""", + // + """ +@__nullableInts_0={ NULL, '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."NullableInt" = ANY (@__nullableInts_0) AND p."NullableInt" = ANY (@__nullableInts_0) IS NOT NULL) AND (p."NullableInt" IS NOT NULL OR array_position(@__nullableInts_0, NULL) IS NULL) +"""); + } + + public override async Task Parameter_collection_of_strings_Contains_string(bool async) + { + await base.Parameter_collection_of_strings_Contains_string(async); + + AssertSql( + """ +@__strings_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."String" = ANY (@__strings_0) +""", + // + """ +@__strings_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."String" = ANY (@__strings_0) AND p."String" = ANY (@__strings_0) IS NOT NULL) """); } @@ -271,25 +412,63 @@ public override async Task Parameter_collection_of_strings_Contains_nullable_str AssertSql( """ -@__strings_0={ '999', NULL } (DbType = Object) +@__strings_0={ '10', '999' } (DbType = Object) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE p."NullableString" = ANY (@__strings_0) OR (p."NullableString" IS NULL AND array_position(@__strings_0, NULL) IS NOT NULL) +""", + // + """ +@__strings_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."NullableString" = ANY (@__strings_0) AND p."NullableString" = ANY (@__strings_0) IS NOT NULL) AND (p."NullableString" IS NOT NULL OR array_position(@__strings_0, NULL) IS NULL) """); } - public override async Task Parameter_collection_of_strings_Contains_non_nullable_string(bool async) + public override async Task Parameter_collection_of_nullable_strings_Contains_string(bool async) { - await base.Parameter_collection_of_strings_Contains_non_nullable_string(async); + await base.Parameter_collection_of_nullable_strings_Contains_string(async); AssertSql( """ -@__strings_0={ '10', '999' } (DbType = Object) +@__strings_0={ '10', NULL } (DbType = Object) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE p."String" = ANY (@__strings_0) +""", + // + """ +@__strings_0={ '10', NULL } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."String" = ANY (@__strings_0) AND p."String" = ANY (@__strings_0) IS NOT NULL) +"""); + } + + public override async Task Parameter_collection_of_nullable_strings_Contains_nullable_string(bool async) + { + await base.Parameter_collection_of_nullable_strings_Contains_nullable_string(async); + + AssertSql( + """ +@__strings_0={ '999', NULL } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."NullableString" = ANY (@__strings_0) OR (p."NullableString" IS NULL AND array_position(@__strings_0, NULL) IS NOT NULL) +""", + // + """ +@__strings_0={ '999', NULL } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."NullableString" = ANY (@__strings_0) AND p."NullableString" = ANY (@__strings_0) IS NOT NULL) AND (p."NullableString" IS NOT NULL OR array_position(@__strings_0, NULL) IS NULL) """); } @@ -674,7 +853,19 @@ SELECT count(*)::int FROM ( SELECT DISTINCT i.value FROM unnest(p."Ints") AS i(value) - ) AS t) = 3 + ) AS i0) = 3 +"""); + } + + public override async Task Column_collection_SelectMany(bool async) + { + await base.Column_collection_SelectMany(async); + + AssertSql( + """ +SELECT i.value +FROM "PrimitiveCollectionsEntity" AS p +JOIN LATERAL unnest(p."Ints") AS i(value) ON TRUE """); } @@ -754,7 +945,7 @@ FROM unnest(p."Ints") AS i(value) UNION SELECT i0.value FROM unnest(@__ints_0) AS i0(value) - ) AS t) = 2 + ) AS u) = 2 """); } @@ -773,7 +964,7 @@ SELECT i.value FROM unnest(p."Ints") AS i(value) INTERSECT VALUES (11::int), (111) - ) AS t) = 2 + ) AS i0) = 2 """); } @@ -813,8 +1004,8 @@ SELECT v."Value" EXCEPT SELECT i.value AS "Value" FROM unnest(p."Ints") AS i(value) - ) AS t - WHERE t."Value" % 2 = 1) = 2 + ) AS e + WHERE e."Value" % 2 = 1) = 2 """); } @@ -911,7 +1102,7 @@ FROM unnest(@__ints[2:]) AS i(value) UNION SELECT i0.value FROM unnest(p."Ints") AS i0(value) - ) AS t) = 3 + ) AS u) = 3 """); } @@ -933,7 +1124,7 @@ FROM unnest(@__Skip_0) AS s(value) UNION SELECT i.value FROM unnest(p."Ints") AS i(value) - ) AS t) = 3 + ) AS u) = 3 """); } @@ -953,22 +1144,22 @@ SELECT count(*)::int SELECT s.value FROM unnest(@__Skip_0) AS s(value) UNION - SELECT t1.value + SELECT i2.value FROM ( - SELECT t0.value + SELECT i1.value FROM ( - SELECT DISTINCT t2.value + SELECT DISTINCT i0.value FROM ( - SELECT i.value, i.ordinality - FROM unnest(p."Ints") WITH ORDINALITY AS i(value) + SELECT i.value + FROM unnest(p."Ints") AS i(value) ORDER BY i.value NULLS FIRST OFFSET 1 - ) AS t2 - ) AS t0 - ORDER BY t0.value DESC NULLS LAST + ) AS i0 + ) AS i1 + ORDER BY i1.value DESC NULLS LAST LIMIT 20 - ) AS t1 - ) AS t) = 3 + ) AS i2 + ) AS u) = 3 """); } @@ -1021,7 +1212,7 @@ FROM unnest(p."Ints"[2:]) AS i(value) UNION SELECT i0.value FROM unnest(@__ints_0) AS i0(value) - ) AS t) = 3 + ) AS u) = 3 """); } @@ -1056,13 +1247,13 @@ public override async Task Project_collection_of_datetimes_filtered(bool async) AssertSql( """ -SELECT p."Id", t.value, t.ordinality +SELECT p."Id", d0.value, d0.ordinality FROM "PrimitiveCollectionsEntity" AS p LEFT JOIN LATERAL ( SELECT d.value, d.ordinality FROM unnest(p."DateTimes") WITH ORDINALITY AS d(value) WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 OR d.value AT TIME ZONE 'UTC' IS NULL -) AS t ON TRUE +) AS d0 ON TRUE ORDER BY p."Id" NULLS FIRST """); } @@ -1086,15 +1277,15 @@ public override async Task Project_collection_of_nullable_ints_with_paging2(bool AssertSql( """ -SELECT p."Id", t.value, t.ordinality +SELECT p."Id", n0.value, n0.ordinality FROM "PrimitiveCollectionsEntity" AS p LEFT JOIN LATERAL ( SELECT n.value, n.ordinality FROM unnest(p."NullableInts") WITH ORDINALITY AS n(value) ORDER BY n.value NULLS FIRST OFFSET 1 -) AS t ON TRUE -ORDER BY p."Id" NULLS FIRST, t.value NULLS FIRST +) AS n0 ON TRUE +ORDER BY p."Id" NULLS FIRST, n0.value NULLS FIRST """); } @@ -1117,12 +1308,12 @@ public override async Task Project_collection_of_ints_with_distinct(bool async) AssertSql( """ -SELECT p."Id", t.value +SELECT p."Id", i0.value FROM "PrimitiveCollectionsEntity" AS p LEFT JOIN LATERAL ( SELECT DISTINCT i.value - FROM unnest(p."Ints") AS i(value) -) AS t ON TRUE + FROM unnest(p."Ints") WITH ORDINALITY AS i(value) +) AS i0 ON TRUE ORDER BY p."Id" NULLS FIRST """); } @@ -1134,25 +1325,43 @@ public override async Task Project_collection_of_nullable_ints_with_distinct(boo AssertSql(); } + public override async Task Project_collection_of_ints_with_ToList_and_FirstOrDefault(bool async) + { + await base.Project_collection_of_ints_with_ToList_and_FirstOrDefault(async); + + AssertSql( + """ +SELECT p0."Id", i.value, i.ordinality +FROM ( + SELECT p."Id", p."Ints" + FROM "PrimitiveCollectionsEntity" AS p + ORDER BY p."Id" NULLS FIRST + LIMIT 1 +) AS p0 +LEFT JOIN LATERAL unnest(p0."Ints") WITH ORDINALITY AS i(value) ON TRUE +ORDER BY p0."Id" NULLS FIRST +"""); + } + public override async Task Project_empty_collection_of_nullables_and_collection_only_containing_nulls(bool async) { await base.Project_empty_collection_of_nullables_and_collection_only_containing_nulls(async); AssertSql( """ -SELECT p."Id", t.value, t.ordinality, t0.value, t0.ordinality +SELECT p."Id", n1.value, n1.ordinality, n2.value, n2.ordinality FROM "PrimitiveCollectionsEntity" AS p LEFT JOIN LATERAL ( SELECT n.value, n.ordinality FROM unnest(p."NullableInts") WITH ORDINALITY AS n(value) WHERE FALSE -) AS t ON TRUE +) AS n1 ON TRUE LEFT JOIN LATERAL ( SELECT n0.value, n0.ordinality FROM unnest(p."NullableInts") WITH ORDINALITY AS n0(value) WHERE n0.value IS NULL -) AS t0 ON TRUE -ORDER BY p."Id" NULLS FIRST, t.ordinality NULLS FIRST +) AS n2 ON TRUE +ORDER BY p."Id" NULLS FIRST, n1.ordinality NULLS FIRST """); } @@ -1180,7 +1389,7 @@ await AssertQuery( AssertSql( """ -SELECT p."Id", i.value, i.ordinality, i0.value, i0.ordinality, t.value, t.ordinality, t0.value, t0.ordinality +SELECT p."Id", i.value, i.ordinality, i0.value, i0.ordinality, d1.value, d1.ordinality, d2.value, d2.ordinality FROM "PrimitiveCollectionsEntity" AS p LEFT JOIN LATERAL unnest(p."Ints") WITH ORDINALITY AS i(value) ON TRUE LEFT JOIN LATERAL unnest(p."Ints") WITH ORDINALITY AS i0(value) ON TRUE @@ -1188,13 +1397,13 @@ LEFT JOIN LATERAL ( SELECT d.value, d.ordinality FROM unnest(p."DateTimes") WITH ORDINALITY AS d(value) WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 OR d.value AT TIME ZONE 'UTC' IS NULL -) AS t ON TRUE +) AS d1 ON TRUE LEFT JOIN LATERAL ( SELECT d0.value, d0.ordinality FROM unnest(p."DateTimes") WITH ORDINALITY AS d0(value) WHERE d0.value > TIMESTAMPTZ '2000-01-01T00:00:00Z' -) AS t0 ON TRUE -ORDER BY p."Id" NULLS FIRST, i.ordinality NULLS FIRST, i0.value DESC NULLS LAST, i0.ordinality NULLS FIRST, t.ordinality NULLS FIRST +) AS d2 ON TRUE +ORDER BY p."Id" NULLS FIRST, i.ordinality NULLS FIRST, i0.value DESC NULLS LAST, i0.ordinality NULLS FIRST, d1.ordinality NULLS FIRST """); } @@ -1211,6 +1420,42 @@ ORDER BY p."Id" NULLS FIRST """); } + public override async Task Nested_contains_with_Lists_and_no_inferred_type_mapping(bool async) + { + await base.Nested_contains_with_Lists_and_no_inferred_type_mapping(async); + + AssertSql( + """ +@__ints_1={ '1', '2', '3' } (DbType = Object) +@__strings_0={ 'one', 'two', 'three' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE CASE + WHEN p."Int" = ANY (@__ints_1) THEN 'one' + ELSE 'two' +END = ANY (@__strings_0) +"""); + } + + public override async Task Nested_contains_with_arrays_and_no_inferred_type_mapping(bool async) + { + await base.Nested_contains_with_arrays_and_no_inferred_type_mapping(async); + + AssertSql( + """ +@__ints_1={ '1', '2', '3' } (DbType = Object) +@__strings_0={ 'one', 'two', 'three' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE CASE + WHEN p."Int" = ANY (@__ints_1) THEN 'one' + ELSE 'two' +END = ANY (@__strings_0) +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs index 764a1a636..7021a6497 100644 --- a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs @@ -263,13 +263,13 @@ public void Union_aggregate() AssertSql( """ -SELECT range_agg(t."IntRange") +SELECT range_agg(r0."IntRange") FROM ( SELECT r."IntRange", TRUE AS "Key" FROM "RangeTestEntities" AS r WHERE r."Id" IN (1, 2) -) AS t -GROUP BY t."Key" +) AS r0 +GROUP BY r0."Key" LIMIT 2 """); } @@ -309,13 +309,13 @@ public void Intersect_aggregate() AssertSql( """ -SELECT range_intersect_agg(t."IntRange") +SELECT range_intersect_agg(r0."IntRange") FROM ( SELECT r."IntRange", TRUE AS "Key" FROM "RangeTestEntities" AS r WHERE r."Id" IN (1, 2) -) AS t -GROUP BY t."Key" +) AS r0 +GROUP BY r0."Key" LIMIT 2 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/SimpleQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SimpleQueryNpgsqlTest.cs deleted file mode 100644 index 1861efae3..000000000 --- a/test/EFCore.PG.FunctionalTests/Query/SimpleQueryNpgsqlTest.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; - -public class SimpleQueryNpgsqlTest : SimpleQueryRelationalTestBase -{ - protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; - - // Writes DateTime with Kind=Unspecified to timestamptz - public override Task SelectMany_where_Select(bool async) - => Task.CompletedTask; - - // 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/Scaffolding/CompiledModelNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Scaffolding/CompiledModelNpgsqlTest.cs new file mode 100644 index 000000000..f2ad5e6ac --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Scaffolding/CompiledModelNpgsqlTest.cs @@ -0,0 +1,59 @@ +// using System.Runtime.CompilerServices; +// using Npgsql.EntityFrameworkCore.PostgreSQL.Design.Internal; +// using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +// using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; +// +// namespace Npgsql.EntityFrameworkCore.PostgreSQL.Scaffolding; +// +// public class CompiledModelNpgsqlTest : CompiledModelRelationalTestBase +// { +// protected override TestHelpers TestHelpers => NpgsqlTestHelpers.Instance; +// protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; +// +// // #3087 +// public override void BigModel() +// => Assert.Throws(() => base.BigModel()); +// +// // #3087 +// public override void BigModel_with_JSON_columns() +// => Assert.Throws(() => base.BigModel()); +// +// // #3087 +// public override void CheckConstraints() +// => Assert.Throws(() => base.BigModel()); +// +// // #3087 +// public override void DbFunctions() +// => Assert.Throws(() => base.BigModel()); +// +// // #3087 +// public override void Triggers() +// => Assert.Throws(() => base.BigModel()); +// +// // https://github.com/dotnet/efcore/pull/32341/files#r1485603038 +// public override void Tpc() +// => Assert.Throws(() => base.Tpc()); +// +// // https://github.com/dotnet/efcore/pull/32341/files#r1485603038 +// public override void ComplexTypes() +// => Assert.Throws(() => base.ComplexTypes()); +// +// protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) +// { +// new NpgsqlDbContextOptionsBuilder(builder).UseNetTopologySuite(); +// return builder; +// } +// +// protected override void AddDesignTimeServices(IServiceCollection services) +// => new NpgsqlNetTopologySuiteDesignTimeServices().ConfigureDesignTimeServices(services); +// +// protected override BuildSource AddReferences(BuildSource build, [CallerFilePath] string filePath = "") +// { +// base.AddReferences(build); +// build.References.Add(BuildReference.ByName("Npgsql.EntityFrameworkCore.PostgreSQL")); +// build.References.Add(BuildReference.ByName("Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite")); +// build.References.Add(BuildReference.ByName("Npgsql")); +// build.References.Add(BuildReference.ByName("NetTopologySuite")); +// return build; +// } +// } diff --git a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs index 35b749fb0..c26bcc273 100644 --- a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs @@ -1886,7 +1886,7 @@ public void Index_covering() // Assert.Equal(new[] { "b", "c" }, indexWith.FindAnnotation(NpgsqlAnnotationNames.IndexInclude).Value); var indexWithout = table.Indexes.Single(i => i.Name == "ix_without"); - Assert.Equal(["a", "b", "c"], indexWithout.Columns.Select(i => i.Name).ToArray()); + Assert.Equal(new[] { "a", "b", "c" }, indexWithout.Columns.Select(i => i.Name).ToArray()); Assert.Null(indexWithout.FindAnnotation(NpgsqlAnnotationNames.IndexInclude)); }, @"DROP TABLE ""IndexCovering"""); @@ -1979,6 +1979,7 @@ public void Dropped_columns() public void Postgres_extensions() => Test( """ +DROP EXTENSION IF EXISTS postgis; CREATE EXTENSION hstore; CREATE EXTENSION pgcrypto SCHEMA db2; """, @@ -2124,13 +2125,13 @@ line line [RequiresPostgis] public void System_tables_are_ignored() => Test( - "CREATE EXTENSION postgis", + """ +DROP EXTENSION IF EXISTS postgis; +CREATE EXTENSION postgis; +""", Enumerable.Empty(), Enumerable.Empty(), - dbModel => - { - Assert.Empty(dbModel.Tables); - }, + dbModel => Assert.Empty(dbModel.Tables), "DROP EXTENSION postgis"); #endregion diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStoreFactory.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStoreFactory.cs index 1f6858fbb..95c16a849 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStoreFactory.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStoreFactory.cs @@ -22,4 +22,5 @@ public override TestStore GetOrCreate(string storeName) public override IServiceCollection AddProviderServices(IServiceCollection serviceCollection) => serviceCollection.AddEntityFrameworkNpgsql(); + // .AddEntityFrameworkNpgsqlNetTopologySuite(); } diff --git a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs index 7a555425c..cd67af7b5 100644 --- a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs @@ -18,7 +18,7 @@ public override async Task Add_element_to_json_collection_branch() AssertSql( """ -@p0='[{"Date":"2101-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":10.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":10.2,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2010-10-10T00:00:00","Enum":2,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (DbType = Object) +@p0='[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":10.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedCollectionBranch}', @p0) @@ -58,7 +58,7 @@ public override async Task Add_element_to_json_collection_on_derived() AssertSql( """ -@p0='[{"Date":"2221-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":221.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2222-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":222.1,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2010-10-10T00:00:00","Enum":2,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (DbType = Object) +@p0='[{"Date":"2221-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":221.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2222-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":222.1,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (DbType = Object) @p1='2' UPDATE "JsonEntitiesInheritance" SET "CollectionOnDerived" = @p0 @@ -79,7 +79,7 @@ public override async Task Add_element_to_json_collection_root() AssertSql( """ -@p0='[{"Name":"e1_c1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":11.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":11.2,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"e1_c2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":12.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":12.0,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Name":"new Name","Names":null,"Number":142,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":2,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}]' (Nullable = false) (DbType = Object) +@p0='[{"Name":"e1_c1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"e1_c2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Name":"new Name","Names":null,"Number":142,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 @@ -99,7 +99,7 @@ public override async Task Add_element_to_json_collection_root_null_navigations( AssertSql( """ -@p0='[{"Name":"e1_c1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":11.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":11.2,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"e1_c2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":12.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":12.0,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Name":"new Name","Names":null,"Number":142,"Numbers":null,"OwnedCollectionBranch":null,"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":2,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":null,"OwnedReferenceLeaf":null}}]' (Nullable = false) (DbType = Object) +@p0='[{"Name":"e1_c1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"e1_c2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Name":"new Name","Names":null,"Number":142,"Numbers":null,"OwnedCollectionBranch":null,"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":null,"OwnedReferenceLeaf":null}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 @@ -119,7 +119,7 @@ public override async Task Add_entity_with_json() AssertSql( """ -@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":2,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (DbType = Object) +@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (DbType = Object) @p1='[]' (Nullable = false) (DbType = Object) @p2='2' @p3=NULL (DbType = Int32) @@ -141,7 +141,7 @@ public override async Task Add_entity_with_json_null_navigations() AssertSql( """ -@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":null,"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":2,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":null}}' (Nullable = false) (DbType = Object) +@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":null,"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":null}}' (Nullable = false) (DbType = Object) @p1='2' @p2=NULL (DbType = Int32) @p3='NewEntity' @@ -182,7 +182,7 @@ public override async Task Add_json_reference_root() AssertSql( """ -@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":2,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (DbType = Object) +@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = @p0 @@ -360,8 +360,8 @@ public override async Task Edit_element_in_json_multiple_levels_partial_update() AssertSql( """ -@p0='[{"Date":"2111-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":11.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"...and another"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":11.2,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"yet another change"},{"SomethingSomething":"and another"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}]' (Nullable = false) (DbType = Object) -@p1='{"Name":"edit","Names":["e1_r1","e1_r2"],"Number":10,"Numbers":[-2147483648,-1,0,1,2147483647],"OwnedCollectionBranch":[{"Date":"2101-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":10.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":10.2,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}}],"OwnedReferenceBranch":{"Date":"2111-11-11T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":10.0,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}}' (Nullable = false) (DbType = Object) +@p0='[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"...and another"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"yet another change"},{"SomethingSomething":"and another"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}]' (Nullable = false) (DbType = Object) +@p1='{"Name":"edit","Names":["e1_r1","e1_r2"],"Number":10,"Numbers":[-2147483648,-1,0,1,2147483647],"OwnedCollectionBranch":[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":10.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}}],"OwnedReferenceBranch":{"Date":"2111-11-11T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":10.0,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}}' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = jsonb_set("OwnedCollectionRoot", '{0,OwnedCollectionBranch}', @p0), "OwnedReferenceRoot" = @p1 @@ -381,7 +381,7 @@ public override async Task Edit_element_in_json_branch_collection_and_add_elemen AssertSql( """ -@p0='[{"Date":"2101-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":4321.3,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":10.2,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2222-11-11T00:00:00","Enum":2,"Enums":null,"Fraction":45.32,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":null,"OwnedReferenceLeaf":{"SomethingSomething":"cc"}}]' (Nullable = false) (DbType = Object) +@p0='[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":4321.3,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2222-11-11T00:00:00","Enum":-3,"Enums":null,"Fraction":45.32,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":null,"OwnedReferenceLeaf":{"SomethingSomething":"cc"}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedCollectionBranch}', @p0) @@ -421,7 +421,7 @@ public override async Task Edit_two_elements_in_the_same_json_collection_at_the_ AssertSql( """ -@p0='[{"Name":"edit1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":11.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":11.2,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"edit2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":1,"Enums":[0,-1,1],"Fraction":12.1,"NullableEnum":0,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":12.0,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}}]' (Nullable = false) (DbType = Object) +@p0='[{"Name":"edit1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"edit2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 @@ -441,7 +441,7 @@ public override async Task Edit_collection_element_and_reference_at_once() AssertSql( """ -@p0='{"Date":"2102-01-01T00:00:00","Enum":2,"Enums":[0,-1,1],"Fraction":10.2,"NullableEnum":1,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"edit1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit2"}}' (Nullable = false) (DbType = Object) +@p0='{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"edit1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit2"}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedCollectionBranch,1}', @p0) @@ -461,8 +461,8 @@ public override async Task Edit_single_enum_property() AssertSql( """ -@p0='1' (Nullable = false) (DbType = Object) -@p1='1' (Nullable = false) (DbType = Object) +@p0='2' (Nullable = false) (DbType = Object) +@p1='2' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = jsonb_set("OwnedCollectionRoot", '{1,OwnedCollectionBranch,1,Enum}', @p0), "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedReferenceBranch,Enum}', @p1) @@ -920,8 +920,8 @@ public override async Task Edit_single_property_enum() AssertSql( """ -@p0='2' (Nullable = false) (DbType = Object) -@p1='2' (Nullable = false) (DbType = Object) +@p0='-3' (Nullable = false) (DbType = Object) +@p1='-3' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestEnum}', @p0), "Reference" = jsonb_set("Reference", '{TestEnum}', @p1) @@ -942,8 +942,8 @@ public override async Task Edit_single_property_enum_with_int_converter() AssertSql( """ -@p0='2' (Nullable = false) (DbType = Object) -@p1='2' (Nullable = false) (DbType = Object) +@p0='-3' (Nullable = false) (DbType = Object) +@p1='-3' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestEnumWithIntConverter}', @p0), "Reference" = jsonb_set("Reference", '{TestEnumWithIntConverter}', @p1) @@ -964,8 +964,8 @@ public override async Task Edit_single_property_nullable_enum() AssertSql( """ -@p0='2' (Nullable = false) (DbType = Object) -@p1='2' (Nullable = false) (DbType = Object) +@p0='-3' (Nullable = false) (DbType = Object) +@p1='-3' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestEnum}', @p0), "Reference" = jsonb_set("Reference", '{TestEnum}', @p1) @@ -1008,8 +1008,8 @@ public override async Task Edit_single_property_nullable_enum_with_int_converter AssertSql( """ -@p0='0' (Nullable = false) (DbType = Object) -@p1='2' (Nullable = false) (DbType = Object) +@p0='-1' (Nullable = false) (DbType = Object) +@p1='-3' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestNullableEnumWithIntConverter}', @p0), "Reference" = jsonb_set("Reference", '{TestNullableEnumWithIntConverter}', @p1) @@ -1096,8 +1096,8 @@ public override async Task Edit_two_properties_on_same_entity_updates_the_entire AssertSql( """ -@p0='{"TestBoolean":false,"TestBooleanCollection":[true,false],"TestByte":25,"TestByteCollection":null,"TestCharacter":"h","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2323-04-03","TestDateOnlyCollection":["3234-01-23","4331-01-21"],"TestDateTime":"2100-11-11T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2200-11-11T12:34:56-05:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-123450.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInCollection1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.2345,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":0,"TestEnumCollection":[0,2,-7],"TestEnumWithIntConverter":1,"TestEnumWithIntConverterCollection":[0,2,-7],"TestGuid":"00000000-0000-0000-0000-000000000000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-12,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Baz","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":0,"TestNullableEnumCollection":[0,null,2,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Two","TestNullableEnumWithConverterThatHandlesNullsCollection":[0,null,-7],"TestNullableEnumWithIntConverter":2,"TestNullableEnumWithIntConverterCollection":[0,null,2,-7],"TestNullableInt32":90,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-18,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.4,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"05:07:08.0000000","TestTimeOnlyCollection":["13:42:23.0000000","07:17:25.0000000"],"TestTimeSpan":"06:05:04.003","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":12,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":12345,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567867,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) -@p1='{"TestBoolean":true,"TestBooleanCollection":[true,false],"TestByte":255,"TestByteCollection":null,"TestCharacter":"a","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2023-10-10","TestDateOnlyCollection":["1234-01-23","4321-01-21"],"TestDateTime":"2000-01-01T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2000-01-01T12:34:56-08:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-1234567890.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInReference1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.23456789,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":0,"TestEnumCollection":[0,2,-7],"TestEnumWithIntConverter":1,"TestEnumWithIntConverterCollection":[0,2,-7],"TestGuid":"12345678-1234-4321-7777-987654321000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-1234,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Foo","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":0,"TestNullableEnumCollection":[0,null,2,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Three","TestNullableEnumWithConverterThatHandlesNullsCollection":[0,null,-7],"TestNullableEnumWithIntConverter":1,"TestNullableEnumWithIntConverterCollection":[0,null,2,-7],"TestNullableInt32":78,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-128,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.234,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"11:12:13.0000000","TestTimeOnlyCollection":["11:42:23.0000000","07:17:27.0000000"],"TestTimeSpan":"10:09:08.007","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":1234,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":1234565789,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567890123456789,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) +@p0='{"TestBoolean":false,"TestBooleanCollection":[true,false],"TestByte":25,"TestByteCollection":null,"TestCharacter":"h","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2323-04-03","TestDateOnlyCollection":["3234-01-23","4331-01-21"],"TestDateTime":"2100-11-11T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2200-11-11T12:34:56-05:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-123450.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInCollection1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.2345,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"00000000-0000-0000-0000-000000000000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-12,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Baz","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Two","TestNullableEnumWithConverterThatHandlesNullsCollection":[-1,null,-7],"TestNullableEnumWithIntConverter":-3,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":90,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-18,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.4,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"05:07:08.0000000","TestTimeOnlyCollection":["13:42:23.0000000","07:17:25.0000000"],"TestTimeSpan":"06:05:04.003","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":12,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":12345,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567867,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) +@p1='{"TestBoolean":true,"TestBooleanCollection":[true,false],"TestByte":255,"TestByteCollection":null,"TestCharacter":"a","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2023-10-10","TestDateOnlyCollection":["1234-01-23","4321-01-21"],"TestDateTime":"2000-01-01T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2000-01-01T12:34:56-08:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-1234567890.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInReference1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.23456789,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"12345678-1234-4321-7777-987654321000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-1234,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Foo","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Three","TestNullableEnumWithConverterThatHandlesNullsCollection":[-1,null,-7],"TestNullableEnumWithIntConverter":2,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":78,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-128,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.234,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"11:12:13.0000000","TestTimeOnlyCollection":["11:42:23.0000000","07:17:27.0000000"],"TestTimeSpan":"10:09:08.007","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":1234,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":1234565789,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567890123456789,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0}', @p0), "Reference" = @p1 @@ -1118,7 +1118,7 @@ public override async Task Edit_a_scalar_property_and_reference_navigation_on_th AssertSql( """ -@p0='{"Date":"2100-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (DbType = Object) +@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedReferenceBranch}', @p0) @@ -1138,7 +1138,7 @@ public override async Task Edit_a_scalar_property_and_collection_navigation_on_t AssertSql( """ -@p0='{"Date":"2100-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"edit"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}' (Nullable = false) (DbType = Object) +@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"edit"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedReferenceBranch}', @p0) @@ -1158,7 +1158,7 @@ public override async Task Edit_a_scalar_property_and_another_property_behind_re AssertSql( """ -@p0='{"Date":"2100-01-01T00:00:00","Enum":0,"Enums":[0,-1,1],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,1],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (DbType = Object) +@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedReferenceBranch}', @p0) @@ -1775,8 +1775,8 @@ public override async Task Edit_single_property_collection_of_enum() AssertSql( """ -@p0='[2]' (Nullable = false) (DbType = Object) -@p1='[2]' (Nullable = false) (DbType = Object) +@p0='[-3]' (Nullable = false) (DbType = Object) +@p1='[-3]' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestEnumCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestEnumCollection}', @p1) @@ -1797,8 +1797,8 @@ public override async Task Edit_single_property_collection_of_enum_with_int_conv AssertSql( """ -@p0='[2]' (Nullable = false) (DbType = Object) -@p1='[2]' (Nullable = false) (DbType = Object) +@p0='[-3]' (Nullable = false) (DbType = Object) +@p1='[-3]' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestEnumWithIntConverterCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestEnumWithIntConverterCollection}', @p1) @@ -1819,8 +1819,8 @@ public override async Task Edit_single_property_collection_of_nullable_enum() AssertSql( """ -@p0='[2]' (Nullable = false) (DbType = Object) -@p1='[2]' (Nullable = false) (DbType = Object) +@p0='[-3]' (Nullable = false) (DbType = Object) +@p1='[-3]' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestEnumCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestEnumCollection}', @p1) @@ -1863,8 +1863,8 @@ public override async Task Edit_single_property_collection_of_nullable_enum_with AssertSql( """ -@p0='[0,null,-7,1]' (Nullable = false) (DbType = Object) -@p1='[0,2,-7,1]' (Nullable = false) (DbType = Object) +@p0='[-1,null,-7,2]' (Nullable = false) (DbType = Object) +@p1='[-1,-3,-7,2]' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestNullableEnumWithIntConverterCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestNullableEnumWithIntConverterCollection}', @p1) @@ -1907,8 +1907,8 @@ public override async Task Edit_single_property_collection_of_nullable_enum_with AssertSql( """ -@p0='[2]' (Nullable = false) (DbType = Object) -@p1='[0]' (Nullable = false) (DbType = Object) +@p0='[-3]' (Nullable = false) (DbType = Object) +@p1='[-1]' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestNullableEnumWithConverterThatHandlesNullsCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestNullableEnumWithConverterThatHandlesNullsCollection}', @p1) diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs index f59bcf5ef..7841a2a3c 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs @@ -1212,12 +1212,12 @@ public async Task Interval_RangeAgg(bool async) AssertSql( """ -SELECT range_agg(t."Interval") +SELECT range_agg(n0."Interval") FROM ( SELECT n."Interval", TRUE AS "Key" FROM "NodaTimeTypes" AS n -) AS t -GROUP BY t."Key" +) AS n0 +GROUP BY n0."Key" LIMIT 2 """); } @@ -1242,12 +1242,12 @@ public async Task Interval_Intersect_aggregate(bool async) AssertSql( """ -SELECT range_intersect_agg(t."Interval") +SELECT range_intersect_agg(n0."Interval") FROM ( SELECT n."Interval", TRUE AS "Key" FROM "NodaTimeTypes" AS n -) AS t -GROUP BY t."Key" +) AS n0 +GROUP BY n0."Key" LIMIT 2 """); } @@ -1420,12 +1420,12 @@ public async Task DateInterval_RangeAgg(bool async) AssertSql( """ -SELECT range_agg(t."DateInterval") +SELECT range_agg(n0."DateInterval") FROM ( SELECT n."DateInterval", TRUE AS "Key" FROM "NodaTimeTypes" AS n -) AS t -GROUP BY t."Key" +) AS n0 +GROUP BY n0."Key" LIMIT 2 """); } @@ -1449,12 +1449,12 @@ public async Task DateInterval_Intersect_aggregate(bool async) AssertSql( """ -SELECT range_intersect_agg(t."DateInterval") +SELECT range_intersect_agg(n0."DateInterval") FROM ( SELECT n."DateInterval", TRUE AS "Key" FROM "NodaTimeTypes" AS n -) AS t -GROUP BY t."Key" +) AS n0 +GROUP BY n0."Key" LIMIT 2 """); } diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlArrayValueConverterTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlArrayValueConverterTest.cs index 0f16dd6b2..65c1be941 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlArrayValueConverterTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlArrayValueConverterTest.cs @@ -12,12 +12,12 @@ public void Can_convert_enum_arrays_to_number_arrays() { var converter = EnumArrayToNumberArray.ConvertToProviderExpression.Compile(); - Assert.Equal([7], converter([Beatles.John])); - Assert.Equal([4], converter([Beatles.Paul])); - Assert.Equal([1], converter([Beatles.George])); - Assert.Equal([-1], converter([Beatles.Ringo])); - Assert.Equal([77], converter([(Beatles)77])); - Assert.Equal([0], converter([default(Beatles)])); + Assert.Equal(new[] { 7 }, converter([Beatles.John])); + Assert.Equal(new[] { 4 }, converter([Beatles.Paul])); + Assert.Equal(new[] { 1 }, converter([Beatles.George])); + Assert.Equal(new[] { -1 }, converter([Beatles.Ringo])); + Assert.Equal(new[] { 77 }, converter([(Beatles)77])); + Assert.Equal(new[] { 0 }, converter([default(Beatles)])); Assert.Null(converter(null)); } From ba34317375842b9baecb3a7e3e9f8122f2a80332 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 11 Feb 2024 21:57:56 +0200 Subject: [PATCH 024/107] Prune PgTableValuedFunctionExpression's projection (#3090) Closes #3088 --- .../Extensions/ExpressionVisitorExtensions.cs | 9 +- .../PgTableValuedFunctionExpression.cs | 9 ++ .../Internal/PgUnnestExpression.cs | 24 ++++- .../NpgsqlQueryTranslationPostprocessor.cs | 11 +++ .../Query/Internal/NpgsqlSqlTreePruner.cs | 56 +++++++++++ .../Internal/NpgsqlUnnestPostprocessor.cs | 8 +- .../Query/JsonQueryNpgsqlTest.cs | 96 ++----------------- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 2 +- 8 files changed, 117 insertions(+), 98 deletions(-) create mode 100644 src/EFCore.PG/Query/Internal/NpgsqlSqlTreePruner.cs diff --git a/src/EFCore.PG/Extensions/ExpressionVisitorExtensions.cs b/src/EFCore.PG/Extensions/ExpressionVisitorExtensions.cs index d1d72da0b..25f9921f5 100644 --- a/src/EFCore.PG/Extensions/ExpressionVisitorExtensions.cs +++ b/src/EFCore.PG/Extensions/ExpressionVisitorExtensions.cs @@ -16,12 +16,13 @@ internal static class ExpressionVisitorExtensions /// /// The modified expression list, if any of the elements were modified; otherwise, returns the original expression list. /// - public static IReadOnlyList Visit(this ExpressionVisitor visitor, IReadOnlyList nodes) + public static IReadOnlyList Visit(this ExpressionVisitor visitor, IReadOnlyList nodes) + where T : Expression { - Expression[]? newNodes = null; + T[]? newNodes = null; for (int i = 0, n = nodes.Count; i < n; i++) { - var node = visitor.Visit(nodes[i]); + var node = (T)visitor.Visit(nodes[i]); if (newNodes is not null) { @@ -29,7 +30,7 @@ public static IReadOnlyList Visit(this ExpressionVisitor visitor, IR } else if (!ReferenceEquals(node, nodes[i])) { - newNodes = new Expression[n]; + newNodes = new T[n]; for (var j = 0; j < i; j++) { newNodes[j] = nodes[j]; diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs index e4e02bc43..0d7f5c418 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs @@ -96,6 +96,15 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni public override PgTableValuedFunctionExpression WithAlias(string newAlias) => new(newAlias, Name, Arguments, ColumnInfos, WithOrdinality); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PgTableValuedFunctionExpression WithColumnInfos(IReadOnlyList columnInfos) + => new(Alias, Name, Arguments, columnInfos, WithOrdinality); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs index 941014fa0..d55fd4b8d 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs @@ -54,7 +54,12 @@ public virtual string ColumnName /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public PgUnnestExpression(string alias, SqlExpression array, string columnName, bool withOrdinality = true) - : base(alias, "unnest", new[] { array }, new[] { new ColumnInfo(columnName) }, withOrdinality) + : this(alias, array, new ColumnInfo(columnName), withOrdinality) + { + } + + private PgUnnestExpression(string alias, SqlExpression array, ColumnInfo? columnInfo, bool withOrdinality = true) + : base(alias, "unnest", new[] { array }, columnInfo is null ? null : [columnInfo.Value], withOrdinality) { } @@ -91,6 +96,19 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni => new PgUnnestExpression(alias!, (SqlExpression)cloningExpressionVisitor.Visit(Array), ColumnName, WithOrdinality); /// - public override PgTableValuedFunctionExpression WithAlias(string newAlias) - => new(newAlias, Name, Arguments, ColumnInfos, WithOrdinality); + public override PgUnnestExpression WithAlias(string newAlias) + => new(newAlias, Array, ColumnName, WithOrdinality); + + /// + public override PgUnnestExpression WithColumnInfos(IReadOnlyList columnInfos) + => new( + Alias, + Array, + columnInfos switch + { + [] => null, + [var columnInfo] => columnInfo, + _ => throw new ArgumentException() + }, + WithOrdinality); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs index de98b5f84..af2cb57ea 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs @@ -8,6 +8,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; /// public class NpgsqlQueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor { + private readonly NpgsqlSqlTreePruner _pruner = new(); + /// /// 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 @@ -37,4 +39,13 @@ public override Expression Process(Expression query) return result; } + + /// + /// 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 Expression Prune(Expression query) + => _pruner.Prune(query); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTreePruner.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTreePruner.cs new file mode 100644 index 000000000..bed46f91f --- /dev/null +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTreePruner.cs @@ -0,0 +1,56 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; +using ColumnInfo = Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal.PgTableValuedFunctionExpression.ColumnInfo; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class NpgsqlSqlTreePruner : SqlTreePruner +{ + /// + /// 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 Expression VisitExtension(Expression node) + { + switch (node) + { + case PgTableValuedFunctionExpression { ColumnInfos: IReadOnlyList columnInfos } tvf: + var arguments = this.Visit(tvf.Arguments); + + List? newColumnInfos = null; + + if (ReferencedColumnMap.TryGetValue(tvf.Alias, out var referencedAliases)) + { + for (var i = 0; i < columnInfos.Count; i++) + { + if (referencedAliases.Contains(columnInfos[i].Name)) + { + newColumnInfos?.Add(columnInfos[i]); + } + else if (newColumnInfos is null) + { + newColumnInfos = []; + for (var j = 0; j < i; j++) + { + newColumnInfos.Add(columnInfos[j]); + } + } + } + } + + return tvf + .Update(arguments) + .WithColumnInfos(newColumnInfos ?? columnInfos); + + default: + return base.VisitExtension(node); + } + } +} diff --git a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs index 1ac0dc924..1fc98797b 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs @@ -36,16 +36,16 @@ public class NpgsqlUnnestPostprocessor : ExpressionVisitor for (var i = 0; i < selectExpression.Tables.Count; i++) { var table = selectExpression.Tables[i]; + var unwrappedTable = table.UnwrapJoin(); // Find any unnest table which does not have any references to its ordinality column in the projection or orderings // (this is where they may appear when a column is an identifier). - var unnest = table as PgUnnestExpression ?? (table as JoinExpressionBase)?.Table as PgUnnestExpression; - if (unnest is not null + if (unwrappedTable is PgUnnestExpression unnest && !selectExpression.Orderings.Select(o => o.Expression) .Concat(selectExpression.Projection.Select(p => p.Expression)) .Any( - p => p is ColumnExpression { Name: "ordinality", } ordinalityColumn - && ordinalityColumn.TableAlias == table.Alias)) + p => p is ColumnExpression { Name: "ordinality" } ordinalityColumn + && ordinalityColumn.TableAlias == unwrappedTable.Alias)) { if (newTables is null) { diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs index 0149ad5d2..53c34d6a3 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs @@ -1032,16 +1032,7 @@ public override async Task Json_collection_Any_with_predicate(bool async) FROM "JsonEntitiesBasic" AS j WHERE EXISTS ( SELECT 1 - FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( - "Date" timestamp without time zone, - "Enum" integer, - "Enums" integer[], - "Fraction" numeric(18,2), - "NullableEnum" integer, - "NullableEnums" integer[], - "OwnedCollectionLeaf" jsonb, - "OwnedReferenceLeaf" jsonb - )) WITH ORDINALITY AS o + FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ("OwnedReferenceLeaf" jsonb)) WITH ORDINALITY AS o WHERE (o."OwnedReferenceLeaf" ->> 'SomethingSomething') = 'e1_r_c1_r') """); } @@ -1059,11 +1050,7 @@ SELECT o."OwnedReferenceLeaf" ->> 'SomethingSomething' FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( "Date" timestamp without time zone, "Enum" integer, - "Enums" integer[], "Fraction" numeric(18,2), - "NullableEnum" integer, - "NullableEnums" integer[], - "OwnedCollectionLeaf" jsonb, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o WHERE o."Enum" = -3 @@ -1083,16 +1070,7 @@ public override async Task Json_collection_Skip(bool async) SELECT o0.c FROM ( SELECT o."OwnedReferenceLeaf" ->> 'SomethingSomething' AS c - FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( - "Date" timestamp without time zone, - "Enum" integer, - "Enums" integer[], - "Fraction" numeric(18,2), - "NullableEnum" integer, - "NullableEnums" integer[], - "OwnedCollectionLeaf" jsonb, - "OwnedReferenceLeaf" jsonb - )) WITH ORDINALITY AS o + FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ("OwnedReferenceLeaf" jsonb)) WITH ORDINALITY AS o OFFSET 1 ) AS o0 LIMIT 1 OFFSET 0) = 'e1_r_c2_r' @@ -1114,11 +1092,7 @@ SELECT o0.c FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( "Date" timestamp without time zone, "Enum" integer, - "Enums" integer[], "Fraction" numeric(18,2), - "NullableEnum" integer, - "NullableEnums" integer[], - "OwnedCollectionLeaf" jsonb, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o ORDER BY o."Date" DESC NULLS LAST @@ -1166,14 +1140,7 @@ public override async Task Json_collection_within_collection_Count(bool async) FROM "JsonEntitiesBasic" AS j WHERE EXISTS ( SELECT 1 - FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( - "Name" text, - "Names" text[], - "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, - "OwnedReferenceBranch" jsonb - )) WITH ORDINALITY AS o + FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedCollectionBranch" jsonb)) WITH ORDINALITY AS o WHERE ( SELECT count(*)::int FROM ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( @@ -1220,11 +1187,7 @@ public override async Task Json_collection_in_projection_with_anonymous_projecti FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "Name" text, - "Names" text[], - "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, - "OwnedReferenceBranch" jsonb + "Number" integer )) WITH ORDINALITY AS o ON TRUE ORDER BY j."Id" NULLS FIRST """); @@ -1242,11 +1205,7 @@ LEFT JOIN LATERAL ( SELECT o."Name", o."Number", o.ordinality FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "Name" text, - "Names" text[], - "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, - "OwnedReferenceBranch" jsonb + "Number" integer )) WITH ORDINALITY AS o WHERE o."Name" = 'Foo' ) AS o0 ON TRUE @@ -1268,9 +1227,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "Name" text, "Names" text[], "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, - "OwnedReferenceBranch" jsonb + "Numbers" integer[] )) WITH ORDINALITY AS o WHERE o."Name" = 'Foo' ) AS o0 ON TRUE @@ -1312,14 +1269,7 @@ public override async Task Json_nested_collection_filter_in_projection(bool asyn FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o.ordinality, o1."Id", o1."Date", o1."Enum", o1."Enums", o1."Fraction", o1."NullableEnum", o1."NullableEnums", o1.c, o1.c0, o1.ordinality AS ordinality0 - FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( - "Name" text, - "Names" text[], - "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, - "OwnedReferenceBranch" jsonb - )) WITH ORDINALITY AS o + FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedCollectionBranch" jsonb)) WITH ORDINALITY AS o LEFT JOIN LATERAL ( SELECT j."Id", o0."Date", o0."Enum", o0."Enums", o0."Fraction", o0."NullableEnum", o0."NullableEnums", o0."OwnedCollectionLeaf" AS c, o0."OwnedReferenceLeaf" AS c0, o0.ordinality FROM ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( @@ -1349,21 +1299,12 @@ public override async Task Json_nested_collection_anonymous_projection_in_projec FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o.ordinality, o0."Date" AS c, o0."Enum" AS c0, o0."Enums" AS c1, o0."Fraction" AS c2, o0."OwnedReferenceLeaf" AS c3, j."Id", o0."OwnedCollectionLeaf" AS c4, o0.ordinality AS ordinality0 - FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( - "Name" text, - "Names" text[], - "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, - "OwnedReferenceBranch" jsonb - )) WITH ORDINALITY AS o + FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedCollectionBranch" jsonb)) WITH ORDINALITY AS o LEFT JOIN LATERAL ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( "Date" timestamp without time zone, "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), - "NullableEnum" integer, - "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o0 ON TRUE @@ -1434,10 +1375,7 @@ LEFT JOIN LATERAL ( SELECT o."OwnedReferenceBranch" AS c, j."Id", o.ordinality, o."Name" AS c0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "Name" text, - "Names" text[], "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o ORDER BY o."Name" NULLS FIRST @@ -1520,14 +1458,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( ) AS o1 ON TRUE LEFT JOIN LATERAL ( SELECT o2.ordinality, o5."Id", o5."Date", o5."Enum", o5."Enums", o5."Fraction", o5."NullableEnum", o5."NullableEnums", o5.c, o5.c0, o5.ordinality AS ordinality0 - FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( - "Name" text, - "Names" text[], - "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, - "OwnedReferenceBranch" jsonb - )) WITH ORDINALITY AS o2 + FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedCollectionBranch" jsonb)) WITH ORDINALITY AS o2 LEFT JOIN LATERAL ( SELECT j."Id", o3."Date", o3."Enum", o3."Enums", o3."Fraction", o3."NullableEnum", o3."NullableEnums", o3."OwnedCollectionLeaf" AS c, o3."OwnedReferenceLeaf" AS c0, o3.ordinality FROM ROWS FROM (jsonb_to_recordset(o2."OwnedCollectionBranch") AS ( @@ -1756,14 +1687,7 @@ public override async Task Json_collection_Select_entity_in_anonymous_object_Ele FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT o."OwnedReferenceBranch" AS c, j."Id", 1 AS c0 - FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( - "Name" text, - "Names" text[], - "Number" integer, - "Numbers" integer[], - "OwnedCollectionBranch" jsonb, - "OwnedReferenceBranch" jsonb - )) WITH ORDINALITY AS o + FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedReferenceBranch" jsonb)) WITH ORDINALITY AS o LIMIT 1 OFFSET 0 ) AS o0 ON TRUE ORDER BY j."Id" NULLS FIRST diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 145973cee..e18c0bfa7 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -1312,7 +1312,7 @@ public override async Task Project_collection_of_ints_with_distinct(bool async) FROM "PrimitiveCollectionsEntity" AS p LEFT JOIN LATERAL ( SELECT DISTINCT i.value - FROM unnest(p."Ints") WITH ORDINALITY AS i(value) + FROM unnest(p."Ints") AS i(value) ) AS i0 ON TRUE ORDER BY p."Id" NULLS FIRST """); From ea0a10368b6473452d92fc63218a2c384c923faf Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 14 Feb 2024 16:01:16 +0200 Subject: [PATCH 025/107] Downgrade xunit back to 2.6.1 (#3094) --- Directory.Packages.props | 2 +- .../NpgsqlModelBuilderGenericTest.cs | 25 ------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c6c22d7a8..77ce318ac 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,7 +23,7 @@ - + diff --git a/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderGenericTest.cs b/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderGenericTest.cs index 8dc5b2348..9478c332d 100644 --- a/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderGenericTest.cs +++ b/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderGenericTest.cs @@ -8,10 +8,6 @@ public class NpgsqlModelBuilderGenericTest : NpgsqlModelBuilderTestBase { public class NpgsqlGenericNonRelationship(NpgsqlModelBuilderFixture fixture) : NpgsqlNonRelationship(fixture) { - // https://github.com/dotnet/efcore/issues/33059 - public override void Can_add_multiple_indexes() - => Assert.Throws(() => base.Can_add_multiple_indexes()); - // PostgreSQL actually does support mapping multi-dimensional arrays, so no exception is thrown as expected protected override void Mapping_throws_for_non_ignored_three_dimensional_array() => Assert.Throws(() => base.Mapping_throws_for_non_ignored_three_dimensional_array()); @@ -23,14 +19,6 @@ protected override TestModelBuilder CreateModelBuilder( public class NpgsqlGenericComplexType(NpgsqlModelBuilderFixture fixture) : NpgsqlComplexType(fixture) { - // https://github.com/dotnet/efcore/issues/33059 - public override void Access_mode_can_be_overridden_at_entity_and_property_levels() - => Assert.Throws(() => base.Access_mode_can_be_overridden_at_entity_and_property_levels()); - - // https://github.com/dotnet/efcore/issues/33059 - public override void Complex_properties_not_discovered_by_convention() - => Assert.Throws(() => base.Complex_properties_not_discovered_by_convention()); - protected override TestModelBuilder CreateModelBuilder( Action? configure) => new GenericTestModelBuilder(Fixture, configure); @@ -66,11 +54,6 @@ protected override TestModelBuilder CreateModelBuilder( public class NpgsqlGenericManyToMany(NpgsqlModelBuilderFixture fixture) : NpgsqlManyToMany(fixture) { - // https://github.com/dotnet/efcore/issues/33059 - public override void Can_use_implicit_shared_type_with_default_name_and_implicit_relationships_as_join_entity() - => Assert.Throws( - () => base.Can_use_implicit_shared_type_with_default_name_and_implicit_relationships_as_join_entity()); - protected override TestModelBuilder CreateModelBuilder( Action? configure) => new GenericTestModelBuilder(Fixture, configure); @@ -78,14 +61,6 @@ protected override TestModelBuilder CreateModelBuilder( public class NpgsqlGenericOwnedTypes(NpgsqlModelBuilderFixture fixture) : NpgsqlOwnedTypes(fixture) { - // https://github.com/dotnet/efcore/issues/33059 - public override void Can_configure_chained_ownerships() - => Assert.Throws(() => base.Can_configure_chained_ownerships()); - - // https://github.com/dotnet/efcore/issues/33059 - public override void Can_configure_chained_ownerships_different_order() - => Assert.Throws(() => base.Can_configure_chained_ownerships_different_order()); - // PostgreSQL stored procedures do not support result columns public override void Can_use_sproc_mapping_with_owned_reference() => Assert.Throws(() => base.Can_use_sproc_mapping_with_owned_reference()); From c0c962c8b2aa76c7e614806153e5c9e9bbb6c253 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 17 Feb 2024 20:32:29 +0200 Subject: [PATCH 026/107] Don't use NpgsqlDataSource from DI if connection string/connection is specified (#3102) Fixes #3060 --- .../Internal/NpgsqlSingletonOptions.cs | 4 +++- .../NpgsqlRelationalConnectionTest.cs | 22 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs b/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs index a6f859155..e0fa14260 100644 --- a/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs +++ b/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs @@ -89,7 +89,9 @@ public virtual void Initialize(IDbContextOptions options) // TODO: Remove after https://github.com/dotnet/efcore/pull/29950 ApplicationServiceProvider = coreOptions.ApplicationServiceProvider; - DataSource = npgsqlOptions.DataSource ?? coreOptions.ApplicationServiceProvider?.GetService(); + DataSource = npgsqlOptions.DataSource ?? (npgsqlOptions.ConnectionString is null && npgsqlOptions.Connection is null + ? coreOptions.ApplicationServiceProvider?.GetService() + : null); } /// diff --git a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs index 8d152daed..7f6421338 100644 --- a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs @@ -51,7 +51,7 @@ public void Uses_DbDataSource_from_DbContextOptions() Assert.Equal("Host=FakeHost", connection2.ConnectionString); } - [Fact] + [Fact(Skip = "Passes in isolation, but fails when the entire test suite is run because of #2891")] public void Uses_DbDataSource_from_application_service_provider() { var serviceCollection = new ServiceCollection(); @@ -81,6 +81,26 @@ public void Uses_DbDataSource_from_application_service_provider() Assert.Equal("Host=FakeHost", connection2.ConnectionString); } + [Fact] // #3060 + public void DbDataSource_from_application_service_provider_does_not_used_if_connection_string_is_specified() + { + var serviceCollection = new ServiceCollection(); + + serviceCollection + .AddNpgsqlDataSource("Host=FakeHost1") + .AddDbContext(o => o.UseNpgsql("Host=FakeHost2")); + + using var serviceProvider = serviceCollection.BuildServiceProvider(); + + using var scope1 = serviceProvider.CreateScope(); + var context1 = scope1.ServiceProvider.GetRequiredService(); + var relationalConnection1 = (NpgsqlRelationalConnection)context1.GetService()!; + Assert.Null(relationalConnection1.DbDataSource); + + var connection1 = context1.GetService().Database.GetDbConnection(); + Assert.Equal("Host=FakeHost2", connection1.ConnectionString); + } + [Fact] public void Can_create_master_connection_with_connection_string() { From 2382b8bfd05f68c33f495be97bb7a7629109edf7 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 17 Feb 2024 21:03:15 +0200 Subject: [PATCH 027/107] Remove unneeded backslash ESCAPE from LIKE translations (#3103) Fixes #3085 --- .../Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs | 3 +-- test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index cbc64eaf9..e6194f099 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -456,8 +456,7 @@ when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPref translation = _sqlExpressionFactory.Like( translatedInstance, - new SqlParameterExpression(escapedPatternParameter.Name!, escapedPatternParameter.Type, stringTypeMapping), - _sqlExpressionFactory.Constant(LikeEscapeChar.ToString())); + new SqlParameterExpression(escapedPatternParameter.Name!, escapedPatternParameter.Type, stringTypeMapping)); return true; } diff --git a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs index 20d48e02e..1ff63672d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs @@ -48,7 +48,7 @@ public void StartsWith_param_pattern() SELECT s."Id", s."CaseInsensitiveText" FROM "SomeEntities" AS s -WHERE s."CaseInsensitiveText" LIKE @__param_0_startswith ESCAPE '\' +WHERE s."CaseInsensitiveText" LIKE @__param_0_startswith LIMIT 2 """); } @@ -102,7 +102,7 @@ public void EndsWith_param_pattern() SELECT s."Id", s."CaseInsensitiveText" FROM "SomeEntities" AS s -WHERE s."CaseInsensitiveText" LIKE @__param_0_endswith ESCAPE '\' +WHERE s."CaseInsensitiveText" LIKE @__param_0_endswith LIMIT 2 """); } @@ -156,7 +156,7 @@ public void Contains_param_pattern() SELECT s."Id", s."CaseInsensitiveText" FROM "SomeEntities" AS s -WHERE s."CaseInsensitiveText" LIKE @__param_0_contains ESCAPE '\' +WHERE s."CaseInsensitiveText" LIKE @__param_0_contains LIMIT 2 """); } From 707c89ea6327f8425d7948bf813c4c96f85bee3c Mon Sep 17 00:00:00 2001 From: Georg Jung Date: Sun, 25 Feb 2024 00:54:35 +0100 Subject: [PATCH 028/107] Add translation of string.Join overload used with List parameter (#3106) Fixes #3105 --- .../Internal/NpgsqlStringMethodTranslator.cs | 8 +- .../Query/ArrayArrayQueryTest.cs | 143 +++++++++------- .../Query/ArrayListQueryTest.cs | 153 +++++++++++------- .../Query/ArrayQueryFixture.cs | 6 +- .../Query/ArrayQueryTest.cs | 10 +- .../TestModels/Array/ArrayEntity.cs | 6 +- .../TestModels/Array/ArrayQueryContext.cs | 22 ++- .../TestModels/Array/ArrayQueryData.cs | 12 +- 8 files changed, 229 insertions(+), 131 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs index ae89c3243..3a97d91e8 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs @@ -1,5 +1,6 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics; using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; @@ -100,6 +101,9 @@ private static readonly MethodInfo LastOrDefaultMethodInfoWithoutArgs private static readonly MethodInfo String_Join4 = typeof(string).GetMethod(nameof(string.Join), [typeof(char), typeof(string[])])!; + private static readonly MethodInfo String_Join5 = + typeof(string).GetMethod(nameof(string.Join), [typeof(string), typeof(IEnumerable)])!; + private static readonly MethodInfo String_Join_generic1 = typeof(string).GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) .Single( @@ -334,8 +338,10 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I || method == String_Join2 || method == String_Join3 || method == String_Join4 + || method == String_Join5 || method.IsClosedFormOf(String_Join_generic1) - || method.IsClosedFormOf(String_Join_generic2))) + || method.IsClosedFormOf(String_Join_generic2)) + && arguments[1].TypeMapping is NpgsqlArrayTypeMapping) { // If the array of strings to be joined is a constant (NewArrayExpression), we translate to concat_ws. // Otherwise we translate to array_to_string, which also supports array columns and parameters. diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index 629e474f5..3387bac2a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -13,7 +13,7 @@ public override async Task Index_with_constant(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntArray"[1] = 3 """); @@ -27,7 +27,7 @@ public override async Task Index_with_parameter(bool async) """ @__x_0='0' -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntArray"[@__x_0 + 1] = 3 """); @@ -39,7 +39,7 @@ public override async Task Nullable_index_with_constant(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableIntArray"[1] = 3 """); @@ -51,7 +51,7 @@ public override async Task Nullable_value_array_index_compare_to_null(bool async AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableIntArray"[3] IS NULL """); @@ -63,7 +63,7 @@ public override async Task Non_nullable_value_array_index_compare_to_null(bool a AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE FALSE """); @@ -75,7 +75,7 @@ public override async Task Nullable_reference_array_index_compare_to_null(bool a AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableStringArray"[3] IS NULL """); @@ -87,7 +87,7 @@ public override async Task Non_nullable_reference_array_index_compare_to_null(bo AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE FALSE """); @@ -103,7 +103,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE get_byte(s."Bytea", 0) = 3 """); @@ -121,7 +121,7 @@ public override async Task SequenceEqual_with_parameter(bool async) """ @__arr_0={ '3', '4' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntArray" = @__arr_0 """); @@ -147,7 +147,7 @@ public override async Task SequenceEqual_over_nullable_with_parameter(bool async """ @__arr_0={ '3', '4', NULL } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableIntArray" = @__arr_0 """); @@ -163,7 +163,7 @@ public override async Task Array_column_Any_equality_operator(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."StringArray" @> ARRAY['3']::text[] """); @@ -175,7 +175,7 @@ public override async Task Array_column_Any_Equals(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."StringArray" @> ARRAY['3']::text[] """); @@ -187,7 +187,7 @@ public override async Task Array_column_Contains_literal_item(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntArray" @> ARRAY[3]::integer[] """); @@ -201,7 +201,7 @@ public override async Task Array_column_Contains_parameter_item(bool async) """ @__p_0='3' -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntArray" @> ARRAY[@__p_0]::integer[] """); @@ -213,7 +213,7 @@ public override async Task Array_column_Contains_column_item(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntArray" @> ARRAY[s."Id" + 2]::integer[] """); @@ -225,7 +225,7 @@ public override async Task Array_column_Contains_null_constant(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE array_position(s."NullableStringArray", NULL) IS NOT NULL """); @@ -257,7 +257,7 @@ public override async Task Nullable_array_column_Contains_literal_item(bool asyn AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableIntArray" @> ARRAY[3]::integer[] """); @@ -271,7 +271,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" IN ('foo', 'xxx') """); @@ -289,7 +289,7 @@ await AssertQuery( """ @__array_0={ 'foo', 'xxx' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" = ANY (@__array_0) OR (s."NullableText" IS NULL AND array_position(@__array_0, NULL) IS NOT NULL) """); @@ -307,7 +307,7 @@ await AssertQuery( """ @__array_0={ '1' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."Id" = ANY (@__array_0) """); @@ -397,7 +397,7 @@ await AssertQuery( """ @__values_0={ '1', '999' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."Id"::text = ANY (@__values_0) """); @@ -416,7 +416,7 @@ await AssertQuery( """ @__values_0='0x14' (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."Byte" = ANY (@__values_0) """); @@ -434,7 +434,7 @@ await AssertQuery( """ @__array_0={ '-2', '-3' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."EnumConvertedToInt" = ANY (@__array_0) """); @@ -452,7 +452,7 @@ await AssertQuery( """ @__array_0={ 'Two', 'Three' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."EnumConvertedToString" = ANY (@__array_0) """); @@ -470,7 +470,7 @@ await AssertQuery( """ @__array_0={ 'Two', 'Three' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableEnumConvertedToString" = ANY (@__array_0) OR (s."NullableEnumConvertedToString" IS NULL AND array_position(@__array_0, NULL) IS NOT NULL) """); @@ -488,7 +488,7 @@ await AssertQuery( """ @__array_0={ 'Two', 'Three' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableEnumConvertedToStringWithNonNullableLambda" = ANY (@__array_0) OR (s."NullableEnumConvertedToStringWithNonNullableLambda" IS NULL AND array_position(@__array_0, NULL) IS NOT NULL) """); @@ -500,15 +500,15 @@ public override async Task Array_column_Contains_value_converted_param(bool asyn await AssertQuery( async, - ss => ss.Set().Where(e => e.ValueConvertedArray.Contains(item))); + ss => ss.Set().Where(e => e.ValueConvertedArrayOfEnum.Contains(item))); AssertSql( """ @__item_0='Eight' (Nullable = false) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."ValueConvertedArray" @> ARRAY[@__item_0]::text[] +WHERE s."ValueConvertedArrayOfEnum" @> ARRAY[@__item_0]::text[] """); } @@ -516,13 +516,13 @@ public override async Task Array_column_Contains_value_converted_constant(bool a { await AssertQuery( async, - ss => ss.Set().Where(e => e.ValueConvertedArray.Contains(SomeEnum.Eight))); + ss => ss.Set().Where(e => e.ValueConvertedArrayOfEnum.Contains(SomeEnum.Eight))); AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."ValueConvertedArray" @> ARRAY['Eight']::text[] +WHERE s."ValueConvertedArrayOfEnum" @> ARRAY['Eight']::text[] """); } @@ -532,15 +532,15 @@ public override async Task Array_param_Contains_value_converted_array_column(boo await AssertQuery( async, - ss => ss.Set().Where(e => e.ValueConvertedArray.All(x => p.Contains(x)))); + ss => ss.Set().Where(e => e.ValueConvertedArrayOfEnum.All(x => p.Contains(x)))); AssertSql( """ @__p_0={ 'Eight', 'Nine' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."ValueConvertedArray" <@ @__p_0 +WHERE s."ValueConvertedArrayOfEnum" <@ @__p_0 """); } @@ -567,7 +567,7 @@ public override async Task IList_column_contains_constant(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IList" @> ARRAY[10]::integer[] """); @@ -585,7 +585,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE cardinality(s."IntArray") = 2 """); @@ -599,7 +599,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE cardinality(s."NullableIntArray") = 3 """); @@ -613,7 +613,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE cardinality(s."IntArray") = 2 """); @@ -629,7 +629,7 @@ public override async Task Any_no_predicate(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE cardinality(s."IntArray") > 0 """); @@ -646,7 +646,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" LIKE ANY (ARRAY['a%','b%','c%']::text[]) """); @@ -663,7 +663,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" ILIKE ANY (ARRAY['a%','b%','c%']::text[]) """); @@ -687,7 +687,7 @@ await AssertQuery( """ @__patternsActual_0={ 'a%', 'b%', 'c%' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" LIKE ANY (@__patternsActual_0) """); @@ -704,7 +704,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" LIKE ALL (ARRAY['b%','ba%']::text[]) """); @@ -721,7 +721,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" ILIKE ALL (ARRAY['B%','ba%']::text[]) """); @@ -733,7 +733,7 @@ public override async Task Any_Contains_on_constant_array(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE ARRAY[2,3]::integer[] && s."IntArray" """); @@ -747,7 +747,7 @@ public override async Task Any_Contains_between_column_and_List(bool async) """ @__ints_0={ '2', '3' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntArray" && @__ints_0 """); @@ -761,7 +761,7 @@ public override async Task Any_Contains_between_column_and_array(bool async) """ @__ints_0={ '2', '3' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntArray" && @__ints_0 """); @@ -773,15 +773,15 @@ public override async Task Any_Contains_between_column_and_other_type(bool async await AssertQuery( async, - ss => ss.Set().Where(e => e.ValueConvertedArray.Any(i => list.Contains(i)))); + ss => ss.Set().Where(e => e.ValueConvertedArrayOfEnum.Any(i => list.Contains(i)))); AssertSql( """ @__list_0={ 'Eight' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."ValueConvertedArray" && @__list_0 +WHERE s."ValueConvertedArrayOfEnum" && @__list_0 """); } @@ -791,7 +791,7 @@ public override async Task All_Contains(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE ARRAY[5,6]::integer[] <@ s."IntArray" """); @@ -833,7 +833,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE COALESCE(array_position(s."IntArray", 6) - 1, -1) = 1 """); @@ -847,25 +847,54 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE COALESCE(array_position(s."IntArray", 6, 2) - 1, -1) = 1 """); } // Note: see NorthwindFunctionsQueryNpgsqlTest.String_Join_non_aggregate for regular use without an array column/parameter - public override async Task String_Join_with_array_parameter(bool async) + public override async Task String_Join_with_array_of_int_column(bool async) { - await base.String_Join_with_array_parameter(async); + await base.String_Join_with_array_of_int_column(async); AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE array_to_string(s."IntArray", ', ', '') = '3, 4' """); } + public override async Task String_Join_with_array_of_string_column(bool async) + { + // This is not in ArrayQueryTest because string.Join uses another overload for string[] than for List and thus + // ArrayToListReplacingExpressionVisitor won't work. + await AssertQuery( + async, + ss => ss.Set() + .Where(e => string.Join(", ", e.StringArray) == "3, 4")); + + AssertSql( + """ +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" +FROM "SomeEntities" AS s +WHERE array_to_string(s."StringArray", ', ', '') = '3, 4' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public override async Task String_Join_disallow_non_array_type_mapped_parameter(bool async) + { + // This is not in ArrayQueryTest because string.Join uses another overload for string[] than for List and thus + // ArrayToListReplacingExpressionVisitor won't work. + await AssertTranslationFailed(() => AssertQuery( + async, + ss => ss.Set() + .Where(e => string.Join(", ", e.ArrayOfStringConvertedToDelimitedString) == "3, 4"))); + } + #endregion Other translations public class ArrayArrayQueryFixture : ArrayQueryFixture diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs index bd8d59e33..0dd9a8753 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs @@ -18,7 +18,7 @@ public override async Task Index_with_constant(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntList"[1] = 3 """); @@ -32,7 +32,7 @@ public override async Task Index_with_parameter(bool async) """ @__x_0='0' -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntList"[@__x_0 + 1] = 3 """); @@ -44,7 +44,7 @@ public override async Task Nullable_index_with_constant(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableIntList"[1] = 3 """); @@ -56,7 +56,7 @@ public override async Task Nullable_value_array_index_compare_to_null(bool async AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableIntList"[3] IS NULL """); @@ -68,7 +68,7 @@ public override async Task Non_nullable_value_array_index_compare_to_null(bool a AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE FALSE """); @@ -80,7 +80,7 @@ public override async Task Nullable_reference_array_index_compare_to_null(bool a AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableStringList"[3] IS NULL """); @@ -92,7 +92,7 @@ public override async Task Non_nullable_reference_array_index_compare_to_null(bo AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE FALSE """); @@ -110,7 +110,7 @@ public override async Task SequenceEqual_with_parameter(bool async) """ @__arr_0={ '3', '4' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntList" = @__arr_0 """); @@ -136,7 +136,7 @@ public override async Task SequenceEqual_over_nullable_with_parameter(bool async """ @__arr_0={ '3', '4', NULL } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableIntList" = @__arr_0 """); @@ -152,7 +152,7 @@ public override async Task Array_column_Any_equality_operator(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."StringList" @> ARRAY['3']::text[] """); @@ -164,7 +164,7 @@ public override async Task Array_column_Any_Equals(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."StringList" @> ARRAY['3']::text[] """); @@ -176,7 +176,7 @@ public override async Task Array_column_Contains_literal_item(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntList" @> ARRAY[3]::integer[] """); @@ -190,7 +190,7 @@ public override async Task Array_column_Contains_parameter_item(bool async) """ @__p_0='3' -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntList" @> ARRAY[@__p_0]::integer[] """); @@ -202,7 +202,7 @@ public override async Task Array_column_Contains_column_item(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntList" @> ARRAY[s."Id" + 2]::integer[] """); @@ -214,7 +214,7 @@ public override async Task Array_column_Contains_null_constant(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE array_position(s."NullableStringList", NULL) IS NOT NULL """); @@ -246,7 +246,7 @@ public override async Task Nullable_array_column_Contains_literal_item(bool asyn AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableIntList" @> ARRAY[3]::integer[] """); @@ -260,7 +260,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" IN ('foo', 'xxx') """); @@ -278,7 +278,7 @@ await AssertQuery( """ @__array_0={ 'foo', 'xxx' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" = ANY (@__array_0) OR (s."NullableText" IS NULL AND array_position(@__array_0, NULL) IS NOT NULL) """); @@ -296,7 +296,7 @@ await AssertQuery( """ @__array_0={ '1' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."Id" = ANY (@__array_0) """); @@ -406,7 +406,7 @@ await AssertQuery( """ @__values_0={ '1', '999' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."Id"::text = ANY (@__values_0) """); @@ -424,7 +424,7 @@ await AssertQuery( """ @__values_0={ '20' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."Byte" = ANY (@__values_0) """); @@ -442,7 +442,7 @@ await AssertQuery( """ @__array_0={ '-2', '-3' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."EnumConvertedToInt" = ANY (@__array_0) """); @@ -460,7 +460,7 @@ await AssertQuery( """ @__array_0={ 'Two', 'Three' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."EnumConvertedToString" = ANY (@__array_0) """); @@ -478,7 +478,7 @@ await AssertQuery( """ @__array_0={ 'Two', 'Three' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableEnumConvertedToString" = ANY (@__array_0) OR (s."NullableEnumConvertedToString" IS NULL AND array_position(@__array_0, NULL) IS NOT NULL) """); @@ -496,7 +496,7 @@ await AssertQuery( """ @__array_0={ 'Two', 'Three' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableEnumConvertedToStringWithNonNullableLambda" = ANY (@__array_0) OR (s."NullableEnumConvertedToStringWithNonNullableLambda" IS NULL AND array_position(@__array_0, NULL) IS NOT NULL) """); @@ -508,15 +508,15 @@ public override async Task Array_column_Contains_value_converted_param(bool asyn await AssertQuery( async, - ss => ss.Set().Where(e => e.ValueConvertedList.Contains(item))); + ss => ss.Set().Where(e => e.ValueConvertedListOfEnum.Contains(item))); AssertSql( """ @__item_0='Eight' (Nullable = false) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."ValueConvertedList" @> ARRAY[@__item_0]::text[] +WHERE s."ValueConvertedListOfEnum" @> ARRAY[@__item_0]::text[] """); } @@ -524,13 +524,13 @@ public override async Task Array_column_Contains_value_converted_constant(bool a { await AssertQuery( async, - ss => ss.Set().Where(e => e.ValueConvertedList.Contains(SomeEnum.Eight))); + ss => ss.Set().Where(e => e.ValueConvertedListOfEnum.Contains(SomeEnum.Eight))); AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."ValueConvertedList" @> ARRAY['Eight']::text[] +WHERE s."ValueConvertedListOfEnum" @> ARRAY['Eight']::text[] """); } @@ -540,15 +540,15 @@ public override async Task Array_param_Contains_value_converted_array_column(boo await AssertQuery( async, - ss => ss.Set().Where(e => e.ValueConvertedArray.All(x => p.Contains(x)))); + ss => ss.Set().Where(e => e.ValueConvertedArrayOfEnum.All(x => p.Contains(x)))); AssertSql( """ @__p_0={ 'Eight', 'Nine' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."ValueConvertedList" <@ @__p_0 +WHERE s."ValueConvertedListOfEnum" <@ @__p_0 """); } @@ -558,7 +558,7 @@ public override async Task IList_column_contains_constant(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IList" @> ARRAY[10]::integer[] """); @@ -593,7 +593,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE cardinality(s."IntList") = 2 """); @@ -607,7 +607,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE cardinality(s."NullableIntList") = 3 """); @@ -621,7 +621,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE cardinality(s."IntList") = 2 """); @@ -637,7 +637,7 @@ public override async Task Any_no_predicate(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE cardinality(s."IntList") > 0 """); @@ -654,7 +654,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" LIKE ANY (ARRAY['a%','b%','c%']::text[]) """); @@ -671,7 +671,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" ILIKE ANY (ARRAY['a%','b%','c%']::text[]) """); @@ -705,7 +705,7 @@ await AssertQuery( """ @__patternsActual_0={ 'a%', 'b%', 'c%' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" LIKE ANY (@__patternsActual_0) """); @@ -722,7 +722,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" LIKE ALL (ARRAY['b%','ba%']::text[]) """); @@ -739,7 +739,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."NullableText" ILIKE ALL (ARRAY['B%','ba%']::text[]) """); @@ -751,7 +751,7 @@ public override async Task Any_Contains_on_constant_array(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE ARRAY[2,3]::integer[] && s."IntList" """); @@ -765,7 +765,7 @@ public override async Task Any_Contains_between_column_and_List(bool async) """ @__ints_0={ '2', '3' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntList" && @__ints_0 """); @@ -779,7 +779,7 @@ public override async Task Any_Contains_between_column_and_array(bool async) """ @__ints_0={ '2', '3' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE s."IntList" && @__ints_0 """); @@ -791,15 +791,15 @@ public override async Task Any_Contains_between_column_and_other_type(bool async await AssertQuery( async, - ss => ss.Set().Where(e => e.ValueConvertedList.Any(i => array.Contains(i)))); + ss => ss.Set().Where(e => e.ValueConvertedListOfEnum.Any(i => array.Contains(i)))); AssertSql( """ @__array_0={ 'Eight' } (DbType = Object) -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."ValueConvertedList" && @__array_0 +WHERE s."ValueConvertedListOfEnum" && @__array_0 """); } @@ -809,7 +809,7 @@ public override async Task All_Contains(bool async) AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE ARRAY[5,6]::integer[] <@ s."IntList" """); @@ -853,7 +853,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE COALESCE(array_position(s."IntList", 6) - 1, -1) = 1 """); @@ -869,25 +869,54 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE COALESCE(array_position(s."IntList", 6, 2) - 1, -1) = 1 """); } // Note: see NorthwindFunctionsQueryNpgsqlTest.String_Join_non_aggregate for regular use without an array column/parameter - public override async Task String_Join_with_array_parameter(bool async) + public override async Task String_Join_with_array_of_int_column(bool async) { - await base.String_Join_with_array_parameter(async); + await base.String_Join_with_array_of_int_column(async); AssertSql( """ -SELECT s."Id", s."ArrayContainerEntityId", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArray", s."ValueConvertedList", s."Varchar10", s."Varchar15" +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s WHERE array_to_string(s."IntList", ', ', '') = '3, 4' """); } + public override async Task String_Join_with_array_of_string_column(bool async) + { + // This is not in ArrayQueryTest because string.Join uses another overload for string[] than for List and thus + // ArrayToListReplacingExpressionVisitor won't work. + await AssertQuery( + async, + ss => ss.Set() + .Where(e => string.Join(", ", e.StringList) == "3, 4")); + + AssertSql( + """ +SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" +FROM "SomeEntities" AS s +WHERE array_to_string(s."StringList", ', ', '') = '3, 4' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public override async Task String_Join_disallow_non_array_type_mapped_parameter(bool async) + { + // This is not in ArrayQueryTest because string.Join uses another overload for string[] than for List and thus + // ArrayToListReplacingExpressionVisitor won't work. + await AssertTranslationFailed(() => AssertQuery( + async, + ss => ss.Set() + .Where(e => string.Join(", ", e.ListOfStringConvertedToDelimitedString) == "3, 4"))); + } + #endregion Other translations public class ArrayListQueryFixture : ArrayQueryFixture @@ -925,11 +954,11 @@ private static readonly PropertyInfo StringList private static readonly PropertyInfo NullableStringList = typeof(ArrayEntity).GetProperty(nameof(ArrayEntity.NullableStringList)); - private static readonly PropertyInfo ValueConvertedArray - = typeof(ArrayEntity).GetProperty(nameof(ArrayEntity.ValueConvertedArray)); + private static readonly PropertyInfo ValueConvertedArrayOfEnum + = typeof(ArrayEntity).GetProperty(nameof(ArrayEntity.ValueConvertedArrayOfEnum)); - private static readonly PropertyInfo ValueConvertedList - = typeof(ArrayEntity).GetProperty(nameof(ArrayEntity.ValueConvertedList)); + private static readonly PropertyInfo ValueConvertedListOfEnum + = typeof(ArrayEntity).GetProperty(nameof(ArrayEntity.ValueConvertedListOfEnum)); protected override Expression VisitMember(MemberExpression node) { @@ -953,9 +982,9 @@ protected override Expression VisitMember(MemberExpression node) return Expression.MakeMemberAccess(node.Expression, NullableStringList); } - if (node.Member == ValueConvertedArray) + if (node.Member == ValueConvertedArrayOfEnum) { - return Expression.MakeMemberAccess(node.Expression, ValueConvertedList); + return Expression.MakeMemberAccess(node.Expression, ValueConvertedListOfEnum); } return node; diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs index e1b529203..3d684dfdb 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs @@ -56,8 +56,10 @@ public IReadOnlyDictionary EntityAsserters Assert.Equal(ee.NullableText, ee.NullableText); Assert.Equal(ee.NonNullableText, ee.NonNullableText); Assert.Equal(ee.EnumConvertedToInt, ee.EnumConvertedToInt); - Assert.Equal(ee.ValueConvertedArray, ee.ValueConvertedArray); - Assert.Equal(ee.ValueConvertedList, ee.ValueConvertedList); + Assert.Equal(ee.ArrayOfStringConvertedToDelimitedString, ee.ArrayOfStringConvertedToDelimitedString); + Assert.Equal(ee.ListOfStringConvertedToDelimitedString, ee.ListOfStringConvertedToDelimitedString); + Assert.Equal(ee.ValueConvertedArrayOfEnum, ee.ValueConvertedArrayOfEnum); + Assert.Equal(ee.ValueConvertedListOfEnum, ee.ValueConvertedListOfEnum); Assert.Equal(ee.IList, ee.IList); Assert.Equal(ee.Byte, ee.Byte); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs index 1a34cb9d4..1951cd85e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs @@ -503,12 +503,20 @@ public virtual Task Concat(bool async) // Note: see NorthwindFunctionsQueryNpgsqlTest.String_Join_non_aggregate for regular use without an array column/parameter [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task String_Join_with_array_parameter(bool async) + public virtual Task String_Join_with_array_of_int_column(bool async) => AssertQuery( async, ss => ss.Set() .Where(e => string.Join(", ", e.IntArray) == "3, 4")); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public abstract Task String_Join_with_array_of_string_column(bool async); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public abstract Task String_Join_disallow_non_array_type_mapped_parameter(bool async); + #endregion Other translations #region Support diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayEntity.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayEntity.cs index 3d4600386..c7dde4960 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayEntity.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayEntity.cs @@ -23,8 +23,10 @@ public class ArrayEntity public SomeEnum EnumConvertedToString { get; set; } public SomeEnum? NullableEnumConvertedToString { get; set; } public SomeEnum? NullableEnumConvertedToStringWithNonNullableLambda { get; set; } - public SomeEnum[] ValueConvertedArray { get; set; } = null!; - public List ValueConvertedList { get; set; } = null!; + public SomeEnum[] ValueConvertedArrayOfEnum { get; set; } = null!; + public List ValueConvertedListOfEnum { get; set; } = null!; + public string[] ArrayOfStringConvertedToDelimitedString { get; set; } = null!; + public List ListOfStringConvertedToDelimitedString { get; set; } = null!; public IList IList { get; set; } = null!; public byte Byte { get; set; } } diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs index ea7ef0197..0c45ed887 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs @@ -27,10 +27,28 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) e.Property(ae => ae.NullableEnumConvertedToStringWithNonNullableLambda) .HasConversion(new ValueConverter(w => w.ToString(), v => Enum.Parse(v))); - e.PrimitiveCollection(ae => ae.ValueConvertedArray) + e.Property(ae => ae.ListOfStringConvertedToDelimitedString) + .HasConversion( + v => string.Join(",", v), + v => v.Split(',', StringSplitOptions.None).ToList(), + new ValueComparer>( + (c1, c2) => c1.SequenceEqual(c2), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), + c => c.ToList())); + + e.Property(ae => ae.ArrayOfStringConvertedToDelimitedString) + .HasConversion( + v => string.Join(",", v), + v => v.Split(',', StringSplitOptions.None).ToArray(), + new ValueComparer( + (c1, c2) => c1.SequenceEqual(c2), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), + c => c.ToArray())); + + e.PrimitiveCollection(ae => ae.ValueConvertedArrayOfEnum) .ElementType(eb => eb.HasConversion(typeof(EnumToStringConverter))); - e.PrimitiveCollection(ae => ae.ValueConvertedList) + e.PrimitiveCollection(ae => ae.ValueConvertedListOfEnum) .ElementType(eb => eb.HasConversion(typeof(EnumToStringConverter))); e.HasIndex(ae => ae.NonNullableText); diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs index 34bf3dd30..55f9f0bd9 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs @@ -45,8 +45,10 @@ public static IReadOnlyList CreateArrayEntities() EnumConvertedToString = SomeEnum.One, NullableEnumConvertedToString = SomeEnum.One, NullableEnumConvertedToStringWithNonNullableLambda = SomeEnum.One, - ValueConvertedArray = [SomeEnum.Eight, SomeEnum.Nine], - ValueConvertedList = [SomeEnum.Eight, SomeEnum.Nine], + ValueConvertedArrayOfEnum = [SomeEnum.Eight, SomeEnum.Nine], + ValueConvertedListOfEnum = [SomeEnum.Eight, SomeEnum.Nine], + ArrayOfStringConvertedToDelimitedString = ["3", "4"], + ListOfStringConvertedToDelimitedString = ["3", "4"], IList = new[] { 8, 9 }, Byte = 10 }, @@ -71,8 +73,10 @@ public static IReadOnlyList CreateArrayEntities() EnumConvertedToString = SomeEnum.Two, NullableEnumConvertedToString = SomeEnum.Two, NullableEnumConvertedToStringWithNonNullableLambda = SomeEnum.Two, - ValueConvertedArray = [SomeEnum.Nine, SomeEnum.Ten], - ValueConvertedList = [SomeEnum.Nine, SomeEnum.Ten], + ValueConvertedArrayOfEnum = [SomeEnum.Nine, SomeEnum.Ten], + ValueConvertedListOfEnum = [SomeEnum.Nine, SomeEnum.Ten], + ArrayOfStringConvertedToDelimitedString = ["5", "6", "7", "8"], + ListOfStringConvertedToDelimitedString = ["5", "6", "7", "8"], IList = new[] { 9, 10 }, Byte = 20 } From 08f341d4f08e7120667dad643115165163b99494 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 5 Mar 2024 11:18:41 +0200 Subject: [PATCH 029/107] Sync to EF 9.0.0-preview.2.24128.4 (#3125) Closes #3123 --- Directory.Packages.props | 4 +- .../NpgsqlMigrationsSqlGenerator.cs | 62 ++++ .../Internal/NpgsqlDatabaseModelFactory.cs | 125 ++++++-- .../INpgsqlSequenceValueGeneratorFactory.cs | 3 +- .../NpgsqlSequenceValueGeneratorFactory.cs | 9 +- .../Internal/NpgsqlValueGeneratorSelector.cs | 54 +++- .../NorthwindBulkUpdatesNpgsqlFixture.cs | 2 +- .../ManyToManyFieldsLoadNpgsqlTest.cs | 2 +- .../ManyToManyLoadNpgsqlTest.cs | 2 +- .../ManyToManyTrackingNpgsqlTest.cs | 5 +- .../Migrations/MigrationsNpgsqlTest.cs | 178 +++++++++-- .../Query/AdHocJsonQueryNpgsqlTest.cs | 79 ++--- .../AdHocMiscellaneousQueryNpgsqlTest.cs | 10 - .../Query/AdHocNavigationsQueryNpgsqlTest.cs | 4 - .../Query/ArrayArrayQueryTest.cs | 4 +- .../Query/ArrayListQueryTest.cs | 4 +- .../Query/ArrayQueryFixture.cs | 2 +- .../Query/BigIntegerQueryTest.cs | 2 +- .../ComplexNavigationsQueryNpgsqlTest.cs | 4 - .../Query/ComplexTypeQueryNpgsqlTest.cs | 281 ++++++++++++++++++ .../Query/Ef6GroupByNpgsqlTest.cs | 2 +- .../Query/EnumQueryTest.cs | 2 +- .../Query/FunkyDataQueryNpgsqlTest.cs | 2 +- .../NorthwindMiscellaneousQueryNpgsqlTest.cs | 16 +- .../Query/NorthwindQueryNpgsqlFixture.cs | 2 +- .../Query/NorthwindWhereQueryNpgsqlTest.cs | 8 +- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 18 +- .../Query/SqlExecutorNpgsqlTest.cs | 10 +- .../Query/TimestampQueryTest.cs | 2 +- .../Query/UdfDbFunctionNpgsqlTests.cs | 16 +- .../Update/JsonUpdateNpgsqlTest.cs | 6 +- .../NodaTimeQueryNpgsqlTest.cs | 97 +++--- .../NpgsqlValueGeneratorSelectorTest.cs | 24 +- 33 files changed, 815 insertions(+), 226 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 77ce318ac..93aecba8c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - 9.0.0-preview.1.24081.2 - 9.0.0-preview.1.24080.9 + 9.0.0-preview.2.24128.4 + 9.0.0-preview.2.24128.5 8.0.2 diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 2b43ed701..03fc496de 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -786,6 +786,68 @@ protected override void Generate(RenameSequenceOperation operation, IModel? mode EndStatement(builder); } + /// + protected override void SequenceOptions( + string? schema, + string name, + SequenceOperation operation, + IModel? model, + MigrationCommandListBuilder builder, + bool forAlter) + { + var intTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(int)); + var longTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(long)); + + builder + .Append(" INCREMENT BY ") + .Append(intTypeMapping.GenerateSqlLiteral(operation.IncrementBy)); + + if (operation.MinValue != null) + { + builder + .Append(" MINVALUE ") + .Append(longTypeMapping.GenerateSqlLiteral(operation.MinValue)); + } + else if (forAlter) + { + builder + .Append(" NO MINVALUE"); + } + + if (operation.MaxValue != null) + { + builder + .Append(" MAXVALUE ") + .Append(longTypeMapping.GenerateSqlLiteral(operation.MaxValue)); + } + else if (forAlter) + { + builder + .Append(" NO MAXVALUE"); + } + + builder.Append(operation.IsCyclic ? " CYCLE" : " NO CYCLE"); + + if (!operation.IsCached) + { + // The base implementation appends NO CACHE, which isn't supported by PG + builder + .Append(" CACHE 1"); + } + else if (operation.CacheSize != null) + { + builder + .Append(" CACHE ") + .Append(intTypeMapping.GenerateSqlLiteral(operation.CacheSize.Value)); + } + else if (forAlter) + { + // The base implementation just appends CACHE, which isn't supported by PG + builder + .Append(" CACHE 1"); + } + } + /// protected override void Generate(RestartSequenceOperation operation, IModel? model, MigrationCommandListBuilder builder) { diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 5cb3103cd..37ea2c68a 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -10,6 +10,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Scaffolding.Internal; +// ReSharper disable StringLiteralTypo + /// /// The default database model factory for Npgsql. /// @@ -458,7 +460,7 @@ ORDER BY attnum MaxValue = seqInfo.MaxValue, IncrementBy = (int)(seqInfo.IncrementBy ?? 1), IsCyclic = seqInfo.IsCyclic ?? false, - NumbersToCache = seqInfo.NumbersToCache ?? 1 + NumbersToCache = seqInfo.CacheSize ?? 1 }; if (!sequenceData.Equals(IdentitySequenceOptionsData.Empty)) @@ -976,11 +978,67 @@ private static IEnumerable GetSequences( Func? schemaFilter, IDiagnosticsLogger logger) { - // Note: we consult information_schema.sequences instead of pg_sequence but the latter was only introduced in PG 10 - var commandText = $""" + // pg_sequence was only introduced in PG 10; we prefer that (cleaner and also exposes sequence caching info), but retain the old + // code for backwards compat + return connection.PostgreSqlVersion >= new Version(10, 0) + ? GetSequencesNew(connection, databaseModel, schemaFilter, logger) + : GetSequencesOld(connection, databaseModel, schemaFilter, logger); + + static IEnumerable GetSequencesNew( + NpgsqlConnection connection, + DatabaseModel databaseModel, + Func? schemaFilter, + IDiagnosticsLogger logger) + { + var commandText = $""" +SELECT nspname, relname, typname, seqstart, seqincrement, seqmax, seqmin, seqcache, seqcycle +FROM pg_sequence +JOIN pg_class AS cls ON cls.oid=seqrelid +JOIN pg_namespace AS ns ON ns.oid = cls.relnamespace +JOIN pg_type AS typ ON typ.oid = seqtypid +/* Filter out owned serial and identity sequences */ +WHERE NOT EXISTS (SELECT * FROM pg_depend AS dep WHERE dep.objid = cls.oid AND dep.deptype IN ('i', 'I', 'a')) + {(schemaFilter is not null ? $"AND {schemaFilter("nspname")}" : null)} +"""; + + using var command = new NpgsqlCommand(commandText, connection); + using var reader = command.ExecuteReader(); + + foreach (var record in reader.Cast()) + { + var sequenceSchema = reader.GetFieldValue("nspname"); + var sequenceName = reader.GetFieldValue("relname"); + + var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion); + var sequence = new DatabaseSequence + { + Database = databaseModel, + Name = sequenceName, + Schema = sequenceSchema, + StoreType = seqInfo.StoreType, + StartValue = seqInfo.StartValue, + MinValue = seqInfo.MinValue, + MaxValue = seqInfo.MaxValue, + IncrementBy = (int?)seqInfo.IncrementBy, + IsCyclic = seqInfo.IsCyclic, + IsCached = seqInfo.CacheSize is not null, + CacheSize = seqInfo.CacheSize + }; + + yield return sequence; + } + } + + static IEnumerable GetSequencesOld( + NpgsqlConnection connection, + DatabaseModel databaseModel, + Func? schemaFilter, + IDiagnosticsLogger logger) + { + var commandText = $""" SELECT sequence_schema, sequence_name, - data_type AS seqtype, + data_type AS typname, {(connection.PostgreSqlVersion >= new Version(9, 1) ? "start_value" : "1")}::bigint AS seqstart, minimum_value::bigint AS seqmin, maximum_value::bigint AS seqmax, @@ -1001,29 +1059,30 @@ AND NOT EXISTS (SELECT * FROM pg_depend AS dep WHERE dep.objid = cls.oid AND dep {(schemaFilter is not null ? $"AND {schemaFilter("nspname")}" : null)} """; - using var command = new NpgsqlCommand(commandText, connection); - using var reader = command.ExecuteReader(); - - foreach (var record in reader.Cast()) - { - var sequenceName = reader.GetFieldValue("sequence_name"); - var sequenceSchema = reader.GetFieldValue("sequence_schema"); + using var command = new NpgsqlCommand(commandText, connection); + using var reader = command.ExecuteReader(); - var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion); - var sequence = new DatabaseSequence + foreach (var record in reader.Cast()) { - Database = databaseModel, - Name = sequenceName, - Schema = sequenceSchema, - StoreType = seqInfo.StoreType, - StartValue = seqInfo.StartValue, - MinValue = seqInfo.MinValue, - MaxValue = seqInfo.MaxValue, - IncrementBy = (int?)seqInfo.IncrementBy, - IsCyclic = seqInfo.IsCyclic - }; - - yield return sequence; + var sequenceName = reader.GetFieldValue("sequence_name"); + var sequenceSchema = reader.GetFieldValue("sequence_schema"); + + var seqInfo = ReadSequenceInfo(record, connection.PostgreSqlVersion); + var sequence = new DatabaseSequence + { + Database = databaseModel, + Name = sequenceName, + Schema = sequenceSchema, + StoreType = seqInfo.StoreType, + StartValue = seqInfo.StartValue, + MinValue = seqInfo.MinValue, + MaxValue = seqInfo.MaxValue, + IncrementBy = (int?)seqInfo.IncrementBy, + IsCyclic = seqInfo.IsCyclic + }; + + yield return sequence; + } } } @@ -1225,16 +1284,24 @@ private static void AdjustDefaults(DatabaseColumn column, string systemTypeName) private static SequenceInfo ReadSequenceInfo(DbDataRecord record, Version postgresVersion) { - var storeType = record.GetFieldValue("seqtype"); + var storeType = record.GetFieldValue("typname"); var startValue = record.GetValueOrDefault("seqstart"); var minValue = record.GetValueOrDefault("seqmin"); var maxValue = record.GetValueOrDefault("seqmax"); var incrementBy = record.GetValueOrDefault("seqincrement"); var isCyclic = record.GetValueOrDefault("seqcycle"); - var numbersToCache = (int)record.GetValueOrDefault("seqcache"); + var cacheSize = (int?)record.GetValueOrDefault("seqcache"); long defaultStart, defaultMin, defaultMax; + storeType = storeType switch + { + "int2" => "smallint", + "int4" => "integer", + "int8" => "bigint", + _ => storeType + }; + switch (storeType) { case "smallint" when incrementBy > 0: @@ -1293,7 +1360,7 @@ private static SequenceInfo ReadSequenceInfo(DbDataRecord record, Version postgr MaxValue = maxValue == defaultMax ? null : maxValue, IncrementBy = incrementBy == 1 ? null : incrementBy, IsCyclic = isCyclic == false ? null : true, - NumbersToCache = numbersToCache == 1 ? null : numbersToCache + CacheSize = cacheSize is 1 or null ? null : cacheSize }; } @@ -1305,7 +1372,7 @@ private sealed class SequenceInfo(string storeType) public long? MaxValue { get; set; } public long? IncrementBy { get; set; } public bool? IsCyclic { get; set; } - public long? NumbersToCache { get; set; } + public int? CacheSize { get; set; } } #endregion diff --git a/src/EFCore.PG/ValueGeneration/Internal/INpgsqlSequenceValueGeneratorFactory.cs b/src/EFCore.PG/ValueGeneration/Internal/INpgsqlSequenceValueGeneratorFactory.cs index 4663f098b..51d91b739 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/INpgsqlSequenceValueGeneratorFactory.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/INpgsqlSequenceValueGeneratorFactory.cs @@ -12,8 +12,9 @@ public interface INpgsqlSequenceValueGeneratorFactory /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - ValueGenerator Create( + ValueGenerator? TryCreate( IProperty property, + Type clrType, NpgsqlSequenceValueGeneratorState generatorState, INpgsqlRelationalConnection connection, IRawSqlCommandBuilder rawSqlCommandBuilder, diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlSequenceValueGeneratorFactory.cs b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlSequenceValueGeneratorFactory.cs index 936cf3a06..37bf81d93 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlSequenceValueGeneratorFactory.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlSequenceValueGeneratorFactory.cs @@ -26,15 +26,14 @@ public NpgsqlSequenceValueGeneratorFactory( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual ValueGenerator Create( + public virtual ValueGenerator? TryCreate( IProperty property, + Type type, NpgsqlSequenceValueGeneratorState generatorState, INpgsqlRelationalConnection connection, IRawSqlCommandBuilder rawSqlCommandBuilder, IRelationalCommandDiagnosticsLogger commandLogger) { - var type = property.ClrType.UnwrapNullableType().UnwrapEnumType(); - if (type == typeof(long)) { return new NpgsqlSequenceHiLoValueGenerator( @@ -89,8 +88,6 @@ public virtual ValueGenerator Create( rawSqlCommandBuilder, _sqlGenerator, generatorState, connection, commandLogger); } - throw new ArgumentException( - CoreStrings.InvalidValueGeneratorFactoryProperty( - nameof(NpgsqlSequenceValueGeneratorFactory), property.Name, property.DeclaringType.DisplayName())); + return null; } } diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs index f18313457..4b44c178e 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs @@ -49,16 +49,50 @@ public NpgsqlValueGeneratorSelector( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override ValueGenerator Select(IProperty property, ITypeBase typeBase) - => property.GetValueGeneratorFactory() is null - && property.GetValueGenerationStrategy() == NpgsqlValueGenerationStrategy.SequenceHiLo - ? _sequenceFactory.Create( - property, - Cache.GetOrAddSequenceState(property, _connection), - _connection, - _rawSqlCommandBuilder, - _commandLogger) - : base.Select(property, typeBase); + public override bool TrySelect(IProperty property, ITypeBase typeBase, out ValueGenerator? valueGenerator) + { + if (property.GetValueGeneratorFactory() != null + || property.GetValueGenerationStrategy() != NpgsqlValueGenerationStrategy.SequenceHiLo) + { + return base.TrySelect(property, typeBase, out valueGenerator); + } + + var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType(); + + valueGenerator = _sequenceFactory.TryCreate( + property, + propertyType, + Cache.GetOrAddSequenceState(property, _connection), + _connection, + _rawSqlCommandBuilder, + _commandLogger); + + if (valueGenerator != null) + { + return true; + } + + var converter = property.GetTypeMapping().Converter; + if (converter != null + && converter.ProviderClrType != propertyType) + { + valueGenerator = _sequenceFactory.TryCreate( + property, + converter.ProviderClrType, + Cache.GetOrAddSequenceState(property, _connection), + _connection, + _rawSqlCommandBuilder, + _commandLogger); + + if (valueGenerator != null) + { + valueGenerator = valueGenerator.WithConverter(converter); + return true; + } + } + + return false; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs index 9cb600c39..4dfa8eea2 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; public class NorthwindBulkUpdatesNpgsqlFixture : NorthwindBulkUpdatesFixture - where TModelCustomizer : IModelCustomizer, new() + where TModelCustomizer : ITestModelCustomizer, new() { protected override ITestStoreFactory TestStoreFactory => NpgsqlNorthwindTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs index 7edbcc6ab..e56daca7a 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyFieldsLoadNpgsqlTest.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class ManyToManyFieldsLoadNpgsqlTest(ManyToManyFieldsLoadNpgsqlTest.ManyToManyFieldsLoadNpgsqlFixture fixture) : ManyToManyFieldsLoadTestBase(fixture) { - public class ManyToManyFieldsLoadNpgsqlFixture : ManyToManyFieldsLoadFixtureBase + public class ManyToManyFieldsLoadNpgsqlFixture : ManyToManyFieldsLoadFixtureBase, ITestSqlLoggerFactory { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs index eaa5fea6a..c25a46d38 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyLoadNpgsqlTest.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class ManyToManyLoadNpgsqlTest(ManyToManyLoadNpgsqlTest.ManyToManyLoadNpgsqlFixture fixture) : ManyToManyLoadTestBase(fixture) { - public class ManyToManyLoadNpgsqlFixture : ManyToManyLoadFixtureBase + public class ManyToManyLoadNpgsqlFixture : ManyToManyLoadFixtureBase, ITestSqlLoggerFactory { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs index b7a906ffd..9faf5f107 100644 --- a/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ManyToManyTrackingNpgsqlTest.cs @@ -10,11 +10,14 @@ public class ManyToManyTrackingNpgsqlTest(ManyToManyTrackingNpgsqlTest.ManyToMan protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); - public class ManyToManyTrackingNpgsqlFixture : ManyToManyTrackingRelationalFixture + public class ManyToManyTrackingNpgsqlFixture : ManyToManyTrackingRelationalFixture, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 0c558c943..190860809 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -1519,10 +1519,14 @@ await Test( }); AssertSql( - @"CREATE SEQUENCE ""People_Id_seq"" AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;", + """ +CREATE SEQUENCE "People_Id_seq" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE; +""", // - @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" SET DEFAULT (nextval('""People_Id_seq""')); -ALTER SEQUENCE ""People_Id_seq"" OWNED BY ""People"".""Id"";"); + """ +ALTER TABLE "People" ALTER COLUMN "Id" SET DEFAULT (nextval('"People_Id_seq"')); +ALTER SEQUENCE "People_Id_seq" OWNED BY "People"."Id"; +"""); } [Fact] @@ -1545,10 +1549,14 @@ await Test( }); AssertSql( - @"CREATE SEQUENCE some_schema.""People_Id_seq"" AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;", + """ +CREATE SEQUENCE some_schema."People_Id_seq" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE; +""", // - @"ALTER TABLE some_schema.""People"" ALTER COLUMN ""Id"" SET DEFAULT (nextval('some_schema.""People_Id_seq""')); -ALTER SEQUENCE some_schema.""People_Id_seq"" OWNED BY some_schema.""People"".""Id"";"); + """ +ALTER TABLE some_schema."People" ALTER COLUMN "Id" SET DEFAULT (nextval('some_schema."People_Id_seq"')); +ALTER SEQUENCE some_schema."People_Id_seq" OWNED BY some_schema."People"."Id"; +"""); } [Fact] @@ -1571,10 +1579,14 @@ await Test( }); AssertSql( - @"CREATE SEQUENCE ""People_Id_seq"" START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;", + """ +CREATE SEQUENCE "People_Id_seq" START WITH 1 INCREMENT BY 1 NO CYCLE; +""", // - @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" SET DEFAULT (nextval('""People_Id_seq""')); -ALTER SEQUENCE ""People_Id_seq"" OWNED BY ""People"".""Id"";"); + """ +ALTER TABLE "People" ALTER COLUMN "Id" SET DEFAULT (nextval('"People_Id_seq"')); +ALTER SEQUENCE "People_Id_seq" OWNED BY "People"."Id"; +"""); } [Fact] @@ -2614,7 +2626,9 @@ public override async Task Create_sequence() await base.Create_sequence(); AssertSql( - @"CREATE SEQUENCE ""TestSequence"" AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;"); + """ +CREATE SEQUENCE "TestSequence" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE; +"""); } public override async Task Create_sequence_all_settings() @@ -2631,7 +2645,39 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'dbo2') THEN END $EF$; """, // - @"CREATE SEQUENCE dbo2.""TestSequence"" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE;"); + @"CREATE SEQUENCE dbo2.""TestSequence"" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE CACHE 20;"); + } + + public override async Task Create_sequence_nocache() + { + await base.Create_sequence_nocache(); + + AssertSql("""CREATE SEQUENCE "Alpha" START WITH 1 INCREMENT BY 1 NO CYCLE CACHE 1;"""); + } + + public override async Task Create_sequence_cache() + { + await base.Create_sequence_cache(); + + AssertSql("""CREATE SEQUENCE "Beta" START WITH 1 INCREMENT BY 1 NO CYCLE CACHE 20;"""); + } + + public override async Task Create_sequence_default_cache() + { + // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as + // disabling caching. + await Test( + builder => { }, + builder => builder.HasSequence("Gamma").UseCache(), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal("Gamma", sequence.Name); + Assert.False(sequence.IsCached); + Assert.Null(sequence.CacheSize); + }); + + AssertSql("""CREATE SEQUENCE "Gamma" START WITH 1 INCREMENT BY 1 NO CYCLE;"""); } [Fact] @@ -2648,34 +2694,19 @@ await Test( }); AssertSql( - @"CREATE SEQUENCE ""TestSequence"" AS smallint START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;"); + """ +CREATE SEQUENCE "TestSequence" AS smallint START WITH 1 INCREMENT BY 1 NO CYCLE; +"""); } [Fact] public override async Task Alter_sequence_all_settings() { - await Test( - builder => builder.HasSequence("foo"), - _ => { }, - builder => builder.HasSequence("foo") - .StartsAt(-3) - .IncrementsBy(2) - .HasMin(-5) - .HasMax(10) - .IsCyclic(), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.Equal(-3, sequence.StartValue); - Assert.Equal(2, sequence.IncrementBy); - Assert.Equal(-5, sequence.MinValue); - Assert.Equal(10, sequence.MaxValue); - Assert.True(sequence.IsCyclic); - }); + await base.Alter_sequence_all_settings(); AssertSql( """ -ALTER SEQUENCE foo INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE; +ALTER SEQUENCE foo INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE CACHE 20; """, // """ @@ -2689,7 +2720,79 @@ public override async Task Alter_sequence_increment_by() await base.Alter_sequence_increment_by(); AssertSql( - @"ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE;"); + @"ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"); + } + + public override async Task Alter_sequence_default_cache_to_cache() + { + await base.Alter_sequence_default_cache_to_cache(); + + AssertSql( + """ALTER SEQUENCE "Delta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); + } + + public override async Task Alter_sequence_default_cache_to_nocache() + { + await base.Alter_sequence_default_cache_to_nocache(); + + AssertSql( + """ALTER SEQUENCE "Epsilon" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + } + + public override async Task Alter_sequence_cache_to_nocache() + { + await base.Alter_sequence_cache_to_nocache(); + + AssertSql( + """ALTER SEQUENCE "Zeta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + } + + public override async Task Alter_sequence_cache_to_default_cache() + { + // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as + // disabling caching. + await Test( + builder => builder.HasSequence("Eta").UseCache(20), + builder => { }, + builder => builder.HasSequence("Eta").UseCache(), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.False(sequence.IsCached); + Assert.Null(sequence.CacheSize); + }); + + AssertSql( + """ALTER SEQUENCE "Eta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + } + + public override async Task Alter_sequence_nocache_to_cache() + { + await base.Alter_sequence_nocache_to_cache(); + + AssertSql( + """ +ALTER SEQUENCE "Theta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20; +"""); + } + + public override async Task Alter_sequence_nocache_to_default_cache() + { + // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as + // disabling caching. + await Test( + builder => builder.HasSequence("Iota").UseNoCache(), + builder => { }, + builder => builder.HasSequence("Iota").UseCache(), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.False(sequence.IsCached); + Assert.Null(sequence.CacheSize); + }); + + AssertSql( + """ALTER SEQUENCE "Iota" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); } public override async Task Alter_sequence_restart_with() @@ -2887,7 +2990,16 @@ SELECT setval( """); } - #endregion + + #endregion Data seeding + + [ConditionalFact] + public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core("ARRAY[3, 2, 1]"); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL DEFAULT (ARRAY[3, 2, 1]);"""); + } #region PostgreSQL extensions diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs index edb0f85fc..c218b8a5d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs @@ -31,52 +31,59 @@ protected override void Seed29219(MyContext29219 ctx) ctx.Entities.AddRange(entity1, entity2); ctx.SaveChanges(); - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$""" INSERT INTO "Entities" ("Id", "Reference", "Collection") -VALUES(3, '{{ "NonNullableScalar" : 30 }}', '[{{ "NonNullableScalar" : 10001 }}]') +VALUES(3, '{ "NonNullableScalar" : 30 }', '[{ "NonNullableScalar" : 10001 }]') """); } protected override void Seed30028(MyContext30028 ctx) { // complete - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( 1, -'{{"RootName":"e1","Collection":[{{"BranchName":"e1 c1","Nested":{{"LeafName":"e1 c1 l"}}}},{{"BranchName":"e1 c2","Nested":{{"LeafName":"e1 c2 l"}}}}],"OptionalReference":{{"BranchName":"e1 or","Nested":{{"LeafName":"e1 or l"}}}},"RequiredReference":{{"BranchName":"e1 rr","Nested":{{"LeafName":"e1 rr l"}}}}}}') +'{"RootName":"e1","Collection":[{"BranchName":"e1 c1","Nested":{"LeafName":"e1 c1 l"}},{"BranchName":"e1 c2","Nested":{"LeafName":"e1 c2 l"}}],"OptionalReference":{"BranchName":"e1 or","Nested":{"LeafName":"e1 or l"}},"RequiredReference":{"BranchName":"e1 rr","Nested":{"LeafName":"e1 rr l"}}}') """); // missing collection - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( 2, -'{{"RootName":"e2","OptionalReference":{{"BranchName":"e2 or","Nested":{{"LeafName":"e2 or l"}}}},"RequiredReference":{{"BranchName":"e2 rr","Nested":{{"LeafName":"e2 rr l"}}}}}}') +'{"RootName":"e2","OptionalReference":{"BranchName":"e2 or","Nested":{"LeafName":"e2 or l"}},"RequiredReference":{"BranchName":"e2 rr","Nested":{"LeafName":"e2 rr l"}}}') """); // missing optional reference - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( 3, -'{{"RootName":"e3","Collection":[{{"BranchName":"e3 c1","Nested":{{"LeafName":"e3 c1 l"}}}},{{"BranchName":"e3 c2","Nested":{{"LeafName":"e3 c2 l"}}}}],"RequiredReference":{{"BranchName":"e3 rr","Nested":{{"LeafName":"e3 rr l"}}}}}}') +'{"RootName":"e3","Collection":[{"BranchName":"e3 c1","Nested":{"LeafName":"e3 c1 l"}},{"BranchName":"e3 c2","Nested":{"LeafName":"e3 c2 l"}}],"RequiredReference":{"BranchName":"e3 rr","Nested":{"LeafName":"e3 rr l"}}}') """); // missing required reference - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( 4, -'{{"RootName":"e4","Collection":[{{"BranchName":"e4 c1","Nested":{{"LeafName":"e4 c1 l"}}}},{{"BranchName":"e4 c2","Nested":{{"LeafName":"e4 c2 l"}}}}],"OptionalReference":{{"BranchName":"e4 or","Nested":{{"LeafName":"e4 or l"}}}}}}') +'{"RootName":"e4","Collection":[{"BranchName":"e4 c1","Nested":{"LeafName":"e4 c1 l"}},{"BranchName":"e4 c2","Nested":{"LeafName":"e4 c2 l"}}],"OptionalReference":{"BranchName":"e4 or","Nested":{"LeafName":"e4 or l"}}}') """); } + protected override void Seed33046(Context33046 ctx) + => ctx.Database.ExecuteSql( + $$""" +INSERT INTO "Reviews" ("Rounds", "Id") +VALUES('[{"RoundNumber":11,"SubRounds":[{"SubRoundNumber":111},{"SubRoundNumber":112}]}]', 1) +"""); + protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) { var entity1 = new MyEntityArrayOfPrimitives @@ -124,53 +131,53 @@ protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) } protected override void SeedJunkInJson(MyContextJunkInJson ctx) - => ctx.Database.ExecuteSqlRaw( - """ + => ctx.Database.ExecuteSql( + $$$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id") VALUES( -'[{{"JunkReference":{{"Something":"SomeValue" }},"Name":"c11","JunkProperty1":50,"Number":11.5,"JunkCollection1":[],"JunkCollection2":[{{"Foo":"junk value"}}],"NestedCollection":[{{"DoB":"2002-04-01T00:00:00","DummyProp":"Dummy value"}},{{"DoB":"2002-04-02T00:00:00","DummyReference":{{"Foo":5}}}}],"NestedReference":{{"DoB":"2002-03-01T00:00:00"}}}},{{"Name":"c12","Number":12.5,"NestedCollection":[{{"DoB":"2002-06-01T00:00:00"}},{{"DoB":"2002-06-02T00:00:00"}}],"NestedDummy":59,"NestedReference":{{"DoB":"2002-05-01T00:00:00"}}}}]', -'[{{"MyBool":true,"Name":"c11 ctor","JunkReference":{{"Something":"SomeValue","JunkCollection":[{{"Foo":"junk value"}}]}},"NestedCollection":[{{"DoB":"2002-08-01T00:00:00"}},{{"DoB":"2002-08-02T00:00:00"}}],"NestedReference":{{"DoB":"2002-07-01T00:00:00"}}}},{{"MyBool":false,"Name":"c12 ctor","NestedCollection":[{{"DoB":"2002-10-01T00:00:00"}},{{"DoB":"2002-10-02T00:00:00"}}],"JunkCollection":[{{"Foo":"junk value"}}],"NestedReference":{{"DoB":"2002-09-01T00:00:00"}}}}]', -'{{"Name":"r1","JunkCollection":[{{"Foo":"junk value"}}],"JunkReference":{{"Something":"SomeValue" }},"Number":1.5,"NestedCollection":[{{"DoB":"2000-02-01T00:00:00","JunkReference":{{"Something":"SomeValue"}}}},{{"DoB":"2000-02-02T00:00:00"}}],"NestedReference":{{"DoB":"2000-01-01T00:00:00"}}}}', -'{{"MyBool":true,"JunkCollection":[{{"Foo":"junk value"}}],"Name":"r1 ctor","JunkReference":{{"Something":"SomeValue" }},"NestedCollection":[{{"DoB":"2001-02-01T00:00:00"}},{{"DoB":"2001-02-02T00:00:00"}}],"NestedReference":{{"JunkCollection":[{{"Foo":"junk value"}}],"DoB":"2001-01-01T00:00:00"}}}}', +'[{"JunkReference":{"Something":"SomeValue" },"Name":"c11","JunkProperty1":50,"Number":11.5,"JunkCollection1":[],"JunkCollection2":[{"Foo":"junk value"}],"NestedCollection":[{"DoB":"2002-04-01T00:00:00","DummyProp":"Dummy value"},{"DoB":"2002-04-02T00:00:00","DummyReference":{"Foo":5}}],"NestedReference":{"DoB":"2002-03-01T00:00:00"}},{"Name":"c12","Number":12.5,"NestedCollection":[{"DoB":"2002-06-01T00:00:00"},{"DoB":"2002-06-02T00:00:00"}],"NestedDummy":59,"NestedReference":{"DoB":"2002-05-01T00:00:00"}}]', +'[{"MyBool":true,"Name":"c11 ctor","JunkReference":{"Something":"SomeValue","JunkCollection":[{"Foo":"junk value"}]},"NestedCollection":[{"DoB":"2002-08-01T00:00:00"},{"DoB":"2002-08-02T00:00:00"}],"NestedReference":{"DoB":"2002-07-01T00:00:00"}},{"MyBool":false,"Name":"c12 ctor","NestedCollection":[{"DoB":"2002-10-01T00:00:00"},{"DoB":"2002-10-02T00:00:00"}],"JunkCollection":[{"Foo":"junk value"}],"NestedReference":{"DoB":"2002-09-01T00:00:00"}}]', +'{"Name":"r1","JunkCollection":[{"Foo":"junk value"}],"JunkReference":{"Something":"SomeValue" },"Number":1.5,"NestedCollection":[{"DoB":"2000-02-01T00:00:00","JunkReference":{"Something":"SomeValue"}},{"DoB":"2000-02-02T00:00:00"}],"NestedReference":{"DoB":"2000-01-01T00:00:00"}}', +'{"MyBool":true,"JunkCollection":[{"Foo":"junk value"}],"Name":"r1 ctor","JunkReference":{"Something":"SomeValue" },"NestedCollection":[{"DoB":"2001-02-01T00:00:00"},{"DoB":"2001-02-02T00:00:00"}],"NestedReference":{"JunkCollection":[{"Foo":"junk value"}],"DoB":"2001-01-01T00:00:00"}}', 1) """); protected override void SeedTrickyBuffering(MyContextTrickyBuffering ctx) - => ctx.Database.ExecuteSqlRaw( - """ + => ctx.Database.ExecuteSql( + $$$""" INSERT INTO "Entities" ("Reference", "Id") VALUES( -'{{"Name": "r1", "Number": 7, "JunkReference":{{"Something": "SomeValue" }}, "JunkCollection": [{{"Foo": "junk value"}}], "NestedReference": {{"DoB": "2000-01-01T00:00:00Z"}}, "NestedCollection": [{{"DoB": "2000-02-01T00:00:00Z", "JunkReference": {{"Something": "SomeValue"}}}}, {{"DoB": "2000-02-02T00:00:00Z"}}]}}',1) +'{"Name": "r1", "Number": 7, "JunkReference":{"Something": "SomeValue" }, "JunkCollection": [{"Foo": "junk value"}], "NestedReference": {"DoB": "2000-01-01T00:00:00Z"}, "NestedCollection": [{"DoB": "2000-02-01T00:00:00Z", "JunkReference": {"Something": "SomeValue"}}, {"DoB": "2000-02-02T00:00:00Z"}]}',1) """); protected override void SeedShadowProperties(MyContextShadowProperties ctx) - => ctx.Database.ExecuteSqlRaw( - """ + => ctx.Database.ExecuteSql( + $$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id", "Name") VALUES( -'[{{"Name":"e1_c1","ShadowDouble":5.5}},{{"ShadowDouble":20.5,"Name":"e1_c2"}}]', -'[{{"Name":"e1_c1 ctor","ShadowNullableByte":6}},{{"ShadowNullableByte":null,"Name":"e1_c2 ctor"}}]', -'{{"Name":"e1_r", "ShadowString":"Foo"}}', -'{{"ShadowInt":143,"Name":"e1_r ctor"}}', +'[{"Name":"e1_c1","ShadowDouble":5.5},{"ShadowDouble":20.5,"Name":"e1_c2"}]', +'[{"Name":"e1_c1 ctor","ShadowNullableByte":6},{"ShadowNullableByte":null,"Name":"e1_c2 ctor"}]', +'{"Name":"e1_r", "ShadowString":"Foo"}', +'{"ShadowInt":143,"Name":"e1_r ctor"}', 1, 'e1') """); protected override void SeedNotICollection(MyContextNotICollection ctx) { - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$""" INSERT INTO "Entities" ("Json", "Id") VALUES( -'{{"Collection":[{{"Bar":11,"Foo":"c11"}},{{"Bar":12,"Foo":"c12"}},{{"Bar":13,"Foo":"c13"}}]}}', +'{"Collection":[{"Bar":11,"Foo":"c11"},{"Bar":12,"Foo":"c12"},{"Bar":13,"Foo":"c13"}]}', 1) """); - ctx.Database.ExecuteSqlRaw( - """ + ctx.Database.ExecuteSql( + $$""" INSERT INTO "Entities" ("Json", "Id") VALUES( -'{{"Collection":[{{"Bar":21,"Foo":"c21"}},{{"Bar":22,"Foo":"c22"}}]}}', +'{"Collection":[{"Bar":21,"Foo":"c21"},{"Bar":22,"Foo":"c22"}]}', 2) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs index 0e1b6a5d4..6d44a9bdd 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs @@ -14,16 +14,6 @@ protected override void Seed2951(Context2951 context) INSERT INTO "ZeroKey" VALUES (NULL) """); - // https://github.com/dotnet/efcore/pull/32542/files#r1485633978 - public override Task Nested_queries_does_not_cause_concurrency_exception_sync(bool tracking) - => Assert.ThrowsAsync( - () => base.Nested_queries_does_not_cause_concurrency_exception_sync(tracking)); - - // https://github.com/dotnet/efcore/pull/32542/files#r1485633978 - public override Task Select_nested_projection() - => Assert.ThrowsAsync( - () => base.Select_nested_projection()); - // Writes DateTime with Kind=Unspecified to timestamptz public override Task SelectMany_where_Select(bool async) => Task.CompletedTask; diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs index dbbd481b3..90feaf094 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocNavigationsQueryNpgsqlTest.cs @@ -8,10 +8,6 @@ public class AdHocNavigationsQueryNpgsqlTest : AdHocNavigationsQueryRelationalTe public override Task Reference_include_on_derived_type_with_sibling_works() => Assert.ThrowsAsync(() => base.Reference_include_on_derived_type_with_sibling_works()); - // https://github.com/dotnet/efcore/pull/32542/files#r1485618022 - public override Task Nested_include_queries_do_not_populate_navigation_twice() - => Assert.ThrowsAsync(() => base.Nested_include_queries_do_not_populate_navigation_twice()); - protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index 3387bac2a..b89306d03 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -685,11 +685,11 @@ await AssertQuery( AssertSql( """ -@__patternsActual_0={ 'a%', 'b%', 'c%' } (DbType = Object) +@__patternsActual_1={ 'a%', 'b%', 'c%' } (DbType = Object) SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."NullableText" LIKE ANY (@__patternsActual_0) +WHERE s."NullableText" LIKE ANY (@__patternsActual_1) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs index 0dd9a8753..99e88ded6 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs @@ -703,11 +703,11 @@ await AssertQuery( AssertSql( """ -@__patternsActual_0={ 'a%', 'b%', 'c%' } (DbType = Object) +@__patternsActual_1={ 'a%', 'b%', 'c%' } (DbType = Object) SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE s."NullableText" LIKE ANY (@__patternsActual_0) +WHERE s."NullableText" LIKE ANY (@__patternsActual_1) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs index 3d684dfdb..2c470c46f 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs @@ -3,7 +3,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public abstract class ArrayQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase +public abstract class ArrayQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs index 44b42b2b9..207ba6a23 100644 --- a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs @@ -145,7 +145,7 @@ public class Entity public BigInteger BigInteger { get; set; } } - public class BigIntegerQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase + public class BigIntegerQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory { private BigIntegerData _expectedData; diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs index 1771836d8..ae27cc581 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsQueryNpgsqlTest.cs @@ -10,10 +10,6 @@ public ComplexNavigationsQueryNpgsqlTest(ComplexNavigationsQueryNpgsqlFixture fi Fixture.TestSqlLoggerFactory.Clear(); } - // https://github.com/dotnet/efcore/pull/33060 - public override Task Max_in_multi_level_nested_subquery(bool async) - => Assert.ThrowsAsync(() => base.Max_in_multi_level_nested_subquery(async)); - public override async Task Join_with_result_selector_returning_queryable_throws_validation_error(bool async) => await Assert.ThrowsAsync( () => base.Join_with_result_selector_returning_queryable_throws_validation_error(async)); diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs index d490537cc..6cedca28b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs @@ -742,6 +742,287 @@ public override async Task Union_two_different_struct_complex_type(bool async) AssertSql(); } + public override async Task Project_same_entity_with_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_entity_with_nested_complex_type_twice_with_pushdown(async); + + AssertSql( +""" +SELECT s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_Tags", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_Tags0", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", c0."Id" AS "Id0", c0."Name" AS "Name0", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c0."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 +) AS s +"""); + } + + public override async Task Project_same_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_nested_complex_type_twice_with_pushdown(async); + + AssertSql( + """ +SELECT s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 +) AS s +"""); + } + + public override async Task Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_entity_with_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT s0."Id", s0."Name", s0."BillingAddress_AddressLine1", s0."BillingAddress_AddressLine2", s0."BillingAddress_Tags", s0."BillingAddress_ZipCode", s0."BillingAddress_Country_Code", s0."BillingAddress_Country_FullName", s0."ShippingAddress_AddressLine1", s0."ShippingAddress_AddressLine2", s0."ShippingAddress_Tags", s0."ShippingAddress_ZipCode", s0."ShippingAddress_Country_Code", s0."ShippingAddress_Country_FullName", s0."Id0", s0."Name0", s0."BillingAddress_AddressLine10", s0."BillingAddress_AddressLine20", s0."BillingAddress_Tags0", s0."BillingAddress_ZipCode0", s0."BillingAddress_Country_Code0", s0."BillingAddress_Country_FullName0", s0."ShippingAddress_AddressLine10", s0."ShippingAddress_AddressLine20", s0."ShippingAddress_Tags0", s0."ShippingAddress_ZipCode0", s0."ShippingAddress_Country_Code0", s0."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_Tags", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_Tags0", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0" + FROM ( + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", c0."Id" AS "Id0", c0."Name" AS "Name0", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c0."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + ORDER BY c."Id" NULLS FIRST, c0."Id" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); + } + + public override async Task Project_same_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT s0."BillingAddress_AddressLine1", s0."BillingAddress_AddressLine2", s0."BillingAddress_Tags", s0."BillingAddress_ZipCode", s0."BillingAddress_Country_Code", s0."BillingAddress_Country_FullName", s0."BillingAddress_AddressLine10", s0."BillingAddress_AddressLine20", s0."BillingAddress_Tags0", s0."BillingAddress_ZipCode0", s0."BillingAddress_Country_Code0", s0."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0" + FROM ( + SELECT c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + ORDER BY c."Id" NULLS FIRST, c0."Id" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); + } + + public override async Task Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_entity_with_struct_nested_complex_type_twice_with_pushdown(async); + + AssertSql( + """ +SELECT s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName", v0."Id" AS "Id0", v0."Name" AS "Name0", v0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", v0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", v0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", v0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", v0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", v0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", v0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", v0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", v0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", v0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "ValuedCustomer" AS v + CROSS JOIN "ValuedCustomer" AS v0 +) AS s +"""); + } + + public override async Task Project_same_struct_nested_complex_type_twice_with_pushdown(bool async) + { + await base.Project_same_struct_nested_complex_type_twice_with_pushdown(async); + + AssertSql( + """ +SELECT s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", v0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", v0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", v0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", v0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "ValuedCustomer" AS v + CROSS JOIN "ValuedCustomer" AS v0 +) AS s +"""); + } + + public override async Task Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_entity_with_struct_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT s0."Id", s0."Name", s0."BillingAddress_AddressLine1", s0."BillingAddress_AddressLine2", s0."BillingAddress_ZipCode", s0."BillingAddress_Country_Code", s0."BillingAddress_Country_FullName", s0."ShippingAddress_AddressLine1", s0."ShippingAddress_AddressLine2", s0."ShippingAddress_ZipCode", s0."ShippingAddress_Country_Code", s0."ShippingAddress_Country_FullName", s0."Id0", s0."Name0", s0."BillingAddress_AddressLine10", s0."BillingAddress_AddressLine20", s0."BillingAddress_ZipCode0", s0."BillingAddress_Country_Code0", s0."BillingAddress_Country_FullName0", s0."ShippingAddress_AddressLine10", s0."ShippingAddress_AddressLine20", s0."ShippingAddress_ZipCode0", s0."ShippingAddress_Country_Code0", s0."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0" + FROM ( + SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName", v0."Id" AS "Id0", v0."Name" AS "Name0", v0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", v0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", v0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", v0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", v0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", v0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", v0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", v0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", v0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", v0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "ValuedCustomer" AS v + CROSS JOIN "ValuedCustomer" AS v0 + ORDER BY v."Id" NULLS FIRST, v0."Id" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); + } + + public override async Task Project_same_struct_nested_complex_type_twice_with_double_pushdown(bool async) + { + await base.Project_same_struct_nested_complex_type_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT s0."BillingAddress_AddressLine1", s0."BillingAddress_AddressLine2", s0."BillingAddress_ZipCode", s0."BillingAddress_Country_Code", s0."BillingAddress_Country_FullName", s0."BillingAddress_AddressLine10", s0."BillingAddress_AddressLine20", s0."BillingAddress_ZipCode0", s0."BillingAddress_Country_Code0", s0."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0" + FROM ( + SELECT v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", v0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", v0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", v0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", v0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "ValuedCustomer" AS v + CROSS JOIN "ValuedCustomer" AS v0 + ORDER BY v."Id" NULLS FIRST, v0."Id" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); + } + + public override async Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(bool async) + { + await base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT u."Id", u."Name", u."BillingAddress_AddressLine1", u."BillingAddress_AddressLine2", u."BillingAddress_Tags", u."BillingAddress_ZipCode", u."BillingAddress_Country_Code", u."BillingAddress_Country_FullName", u."ShippingAddress_AddressLine1", u."ShippingAddress_AddressLine2", u."ShippingAddress_Tags", u."ShippingAddress_ZipCode", u."ShippingAddress_Country_Code", u."ShippingAddress_Country_FullName", u."Id0", u."Name0", u."BillingAddress_AddressLine10", u."BillingAddress_AddressLine20", u."BillingAddress_Tags0", u."BillingAddress_ZipCode0", u."BillingAddress_Country_Code0", u."BillingAddress_Country_FullName0", u."ShippingAddress_AddressLine10", u."ShippingAddress_AddressLine20", u."ShippingAddress_Tags0", u."ShippingAddress_ZipCode0", u."ShippingAddress_Country_Code0", u."ShippingAddress_Country_FullName0" +FROM ( + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", c0."Id" AS "Id0", c0."Name" AS "Name0", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c0."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + UNION + SELECT c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_Tags", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName", c2."Id" AS "Id0", c2."Name" AS "Name0", c2."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c2."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c2."BillingAddress_Tags" AS "BillingAddress_Tags0", c2."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c2."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c2."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c2."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c2."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c2."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c2."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c2."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c2."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c1 + CROSS JOIN "Customer" AS c2 +) AS u +ORDER BY u."Id" NULLS FIRST, u."Id0" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(bool async) + { + await base.Union_of_same_entity_with_nested_complex_type_projected_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT u1."Id", u1."Name", u1."BillingAddress_AddressLine1", u1."BillingAddress_AddressLine2", u1."BillingAddress_Tags", u1."BillingAddress_ZipCode", u1."BillingAddress_Country_Code", u1."BillingAddress_Country_FullName", u1."ShippingAddress_AddressLine1", u1."ShippingAddress_AddressLine2", u1."ShippingAddress_Tags", u1."ShippingAddress_ZipCode", u1."ShippingAddress_Country_Code", u1."ShippingAddress_Country_FullName", u1."Id0", u1."Name0", u1."BillingAddress_AddressLine10", u1."BillingAddress_AddressLine20", u1."BillingAddress_Tags0", u1."BillingAddress_ZipCode0", u1."BillingAddress_Country_Code0", u1."BillingAddress_Country_FullName0", u1."ShippingAddress_AddressLine10", u1."ShippingAddress_AddressLine20", u1."ShippingAddress_Tags0", u1."ShippingAddress_ZipCode0", u1."ShippingAddress_Country_Code0", u1."ShippingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT u0."Id", u0."Name", u0."BillingAddress_AddressLine1", u0."BillingAddress_AddressLine2", u0."BillingAddress_Tags", u0."BillingAddress_ZipCode", u0."BillingAddress_Country_Code", u0."BillingAddress_Country_FullName", u0."ShippingAddress_AddressLine1", u0."ShippingAddress_AddressLine2", u0."ShippingAddress_Tags", u0."ShippingAddress_ZipCode", u0."ShippingAddress_Country_Code", u0."ShippingAddress_Country_FullName", u0."Id0", u0."Name0", u0."BillingAddress_AddressLine10", u0."BillingAddress_AddressLine20", u0."BillingAddress_Tags0", u0."BillingAddress_ZipCode0", u0."BillingAddress_Country_Code0", u0."BillingAddress_Country_FullName0", u0."ShippingAddress_AddressLine10", u0."ShippingAddress_AddressLine20", u0."ShippingAddress_Tags0", u0."ShippingAddress_ZipCode0", u0."ShippingAddress_Country_Code0", u0."ShippingAddress_Country_FullName0" + FROM ( + SELECT u."Id", u."Name", u."BillingAddress_AddressLine1", u."BillingAddress_AddressLine2", u."BillingAddress_Tags", u."BillingAddress_ZipCode", u."BillingAddress_Country_Code", u."BillingAddress_Country_FullName", u."ShippingAddress_AddressLine1", u."ShippingAddress_AddressLine2", u."ShippingAddress_Tags", u."ShippingAddress_ZipCode", u."ShippingAddress_Country_Code", u."ShippingAddress_Country_FullName", u."Id0", u."Name0", u."BillingAddress_AddressLine10", u."BillingAddress_AddressLine20", u."BillingAddress_Tags0", u."BillingAddress_ZipCode0", u."BillingAddress_Country_Code0", u."BillingAddress_Country_FullName0", u."ShippingAddress_AddressLine10", u."ShippingAddress_AddressLine20", u."ShippingAddress_Tags0", u."ShippingAddress_ZipCode0", u."ShippingAddress_Country_Code0", u."ShippingAddress_Country_FullName0" + FROM ( + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", c0."Id" AS "Id0", c0."Name" AS "Name0", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c0."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c0."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c0."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c0."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c0."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c0."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + UNION + SELECT c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_Tags", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName", c2."Id" AS "Id0", c2."Name" AS "Name0", c2."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c2."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c2."BillingAddress_Tags" AS "BillingAddress_Tags0", c2."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c2."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c2."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c2."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c2."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c2."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c2."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c2."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c2."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0" + FROM "Customer" AS c1 + CROSS JOIN "Customer" AS c2 + ) AS u + ORDER BY u."Id" NULLS FIRST, u."Id0" NULLS FIRST + LIMIT @__p_0 + ) AS u0 +) AS u1 +ORDER BY u1."Id" NULLS FIRST, u1."Id0" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_nested_complex_type_projected_twice_with_pushdown(bool async) + { + await base.Union_of_same_nested_complex_type_projected_twice_with_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT u."BillingAddress_AddressLine1", u."BillingAddress_AddressLine2", u."BillingAddress_Tags", u."BillingAddress_ZipCode", u."BillingAddress_Country_Code", u."BillingAddress_Country_FullName", u."BillingAddress_AddressLine10", u."BillingAddress_AddressLine20", u."BillingAddress_Tags0", u."BillingAddress_ZipCode0", u."BillingAddress_Country_Code0", u."BillingAddress_Country_FullName0" +FROM ( + SELECT c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + UNION + SELECT c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c2."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c2."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c2."BillingAddress_Tags" AS "BillingAddress_Tags0", c2."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c2."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c2."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c1 + CROSS JOIN "Customer" AS c2 +) AS u +ORDER BY u."BillingAddress_ZipCode" NULLS FIRST, u."BillingAddress_ZipCode0" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(bool async) + { + await base.Union_of_same_nested_complex_type_projected_twice_with_double_pushdown(async); + + AssertSql( + """ +@__p_0='50' + +SELECT u1."BillingAddress_AddressLine1", u1."BillingAddress_AddressLine2", u1."BillingAddress_Tags", u1."BillingAddress_ZipCode", u1."BillingAddress_Country_Code", u1."BillingAddress_Country_FullName", u1."BillingAddress_AddressLine10", u1."BillingAddress_AddressLine20", u1."BillingAddress_Tags0", u1."BillingAddress_ZipCode0", u1."BillingAddress_Country_Code0", u1."BillingAddress_Country_FullName0" +FROM ( + SELECT DISTINCT u0."BillingAddress_AddressLine1", u0."BillingAddress_AddressLine2", u0."BillingAddress_Tags", u0."BillingAddress_ZipCode", u0."BillingAddress_Country_Code", u0."BillingAddress_Country_FullName", u0."BillingAddress_AddressLine10", u0."BillingAddress_AddressLine20", u0."BillingAddress_Tags0", u0."BillingAddress_ZipCode0", u0."BillingAddress_Country_Code0", u0."BillingAddress_Country_FullName0" + FROM ( + SELECT u."BillingAddress_AddressLine1", u."BillingAddress_AddressLine2", u."BillingAddress_Tags", u."BillingAddress_ZipCode", u."BillingAddress_Country_Code", u."BillingAddress_Country_FullName", u."BillingAddress_AddressLine10", u."BillingAddress_AddressLine20", u."BillingAddress_Tags0", u."BillingAddress_ZipCode0", u."BillingAddress_Country_Code0", u."BillingAddress_Country_FullName0" + FROM ( + SELECT c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c0."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c0."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c0."BillingAddress_Tags" AS "BillingAddress_Tags0", c0."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c0."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c0."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c + CROSS JOIN "Customer" AS c0 + UNION + SELECT c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c2."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c2."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c2."BillingAddress_Tags" AS "BillingAddress_Tags0", c2."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c2."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c2."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0" + FROM "Customer" AS c1 + CROSS JOIN "Customer" AS c2 + ) AS u + ORDER BY u."BillingAddress_ZipCode" NULLS FIRST, u."BillingAddress_ZipCode0" NULLS FIRST + LIMIT @__p_0 + ) AS u0 +) AS u1 +ORDER BY u1."BillingAddress_ZipCode" NULLS FIRST, u1."BillingAddress_ZipCode0" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + { + await base.Same_entity_with_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async); + + AssertSql( + """ +SELECT c."Id", s."Id", s."Name", s."BillingAddress_AddressLine1", s."BillingAddress_AddressLine2", s."BillingAddress_Tags", s."BillingAddress_ZipCode", s."BillingAddress_Country_Code", s."BillingAddress_Country_FullName", s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_Tags", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName", s."Id0", s."Name0", s."BillingAddress_AddressLine10", s."BillingAddress_AddressLine20", s."BillingAddress_Tags0", s."BillingAddress_ZipCode0", s."BillingAddress_Country_Code0", s."BillingAddress_Country_FullName0", s."ShippingAddress_AddressLine10", s."ShippingAddress_AddressLine20", s."ShippingAddress_Tags0", s."ShippingAddress_ZipCode0", s."ShippingAddress_Country_Code0", s."ShippingAddress_Country_FullName0", s.c +FROM "Customer" AS c +LEFT JOIN LATERAL ( + SELECT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName", c1."Id" AS "Id0", c1."Name" AS "Name0", c1."BillingAddress_AddressLine1" AS "BillingAddress_AddressLine10", c1."BillingAddress_AddressLine2" AS "BillingAddress_AddressLine20", c1."BillingAddress_Tags" AS "BillingAddress_Tags0", c1."BillingAddress_ZipCode" AS "BillingAddress_ZipCode0", c1."BillingAddress_Country_Code" AS "BillingAddress_Country_Code0", c1."BillingAddress_Country_FullName" AS "BillingAddress_Country_FullName0", c1."ShippingAddress_AddressLine1" AS "ShippingAddress_AddressLine10", c1."ShippingAddress_AddressLine2" AS "ShippingAddress_AddressLine20", c1."ShippingAddress_Tags" AS "ShippingAddress_Tags0", c1."ShippingAddress_ZipCode" AS "ShippingAddress_ZipCode0", c1."ShippingAddress_Country_Code" AS "ShippingAddress_Country_Code0", c1."ShippingAddress_Country_FullName" AS "ShippingAddress_Country_FullName0", 1 AS c + FROM "Customer" AS c0 + CROSS JOIN "Customer" AS c1 + ORDER BY c0."Id" NULLS FIRST, c1."Id" DESC NULLS LAST + LIMIT 1 +) AS s ON TRUE +"""); + } + + public override async Task Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(bool async) + { + await base.Same_complex_type_projected_twice_with_pushdown_as_part_of_another_projection(async); + + AssertSql(""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs index b83d9748a..2e7a5b46e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Ef6GroupByNpgsqlTest.cs @@ -79,7 +79,7 @@ SELECT p0."MiddleInitial" private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class Ef6GroupByNpgsqlFixture : Ef6GroupByFixtureBase + public class Ef6GroupByNpgsqlFixture : Ef6GroupByFixtureBase, ITestSqlLoggerFactory { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index c7f577afc..479506646 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -287,7 +287,7 @@ public enum UnmappedByteEnum : byte } // ReSharper disable once ClassNeverInstantiated.Global - public class EnumFixture : SharedStoreFixtureBase, IQueryFixtureBase + public class EnumFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory { protected override string StoreName => "EnumQueryTest"; diff --git a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs index 0d9e16de1..6c46a8ff5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs @@ -38,7 +38,7 @@ await AssertQuery( private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class FunkyDataQueryNpgsqlFixture : FunkyDataQueryFixtureBase + public class FunkyDataQueryNpgsqlFixture : FunkyDataQueryFixtureBase, ITestSqlLoggerFactory { private FunkyDataData _expectedData; diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs index 188c1a88a..89f18e27e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs @@ -314,11 +314,11 @@ public async Task Array_Any_Like(bool async) AssertSql( """ -@__collection_0={ 'A%', 'B%', 'C%' } (DbType = Object) +@__collection_1={ 'A%', 'B%', 'C%' } (DbType = Object) 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."Address" LIKE ANY (@__collection_0) +WHERE c."Address" LIKE ANY (@__collection_1) """); } @@ -336,11 +336,11 @@ public async Task Array_All_Like(bool async) AssertSql( """ -@__collection_0={ 'A%', 'B%', 'C%' } (DbType = Object) +@__collection_1={ 'A%', 'B%', 'C%' } (DbType = Object) 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."Address" LIKE ALL (@__collection_0) +WHERE c."Address" LIKE ALL (@__collection_1) """); } @@ -383,11 +383,11 @@ public async Task Array_Any_ILike(bool async) AssertSql( """ -@__collection_0={ 'a%', 'b%', 'c%' } (DbType = Object) +@__collection_1={ 'a%', 'b%', 'c%' } (DbType = Object) 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."Address" ILIKE ANY (@__collection_0) +WHERE c."Address" ILIKE ANY (@__collection_1) """); } @@ -405,11 +405,11 @@ public async Task Array_All_ILike(bool async) AssertSql( """ -@__collection_0={ 'a%', 'b%', 'c%' } (DbType = Object) +@__collection_1={ 'a%', 'b%', 'c%' } (DbType = Object) 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."Address" ILIKE ALL (@__collection_0) +WHERE c."Address" ILIKE ALL (@__collection_1) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs index 0c2142a98..e8d412e21 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; public class NorthwindQueryNpgsqlFixture : NorthwindQueryRelationalFixture - where TModelCustomizer : IModelCustomizer, new() + where TModelCustomizer : ITestModelCustomizer, new() { protected override ITestStoreFactory TestStoreFactory => NpgsqlNorthwindTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs index cc6da3a3b..18109a3c8 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs @@ -328,11 +328,11 @@ public async Task Row_value_GreaterThan_with_parameter() AssertSql( """ -@__city1_1='Buenos Aires' +@__city1_0='Buenos Aires' SELECT count(*)::int FROM "Customers" AS c -WHERE (c."City", c."CustomerID") > (@__city1_1, 'OCEAN') +WHERE (c."City", c."CustomerID") > (@__city1_0, 'OCEAN') """); } @@ -352,11 +352,11 @@ public async Task Row_value_GreaterThan_with_parameter_with_ValueTuple_construct AssertSql( """ -@__city1_1='Buenos Aires' +@__city1_0='Buenos Aires' SELECT count(*)::int FROM "Customers" AS c -WHERE (c."City", c."CustomerID") > (@__city1_1, 'OCEAN') +WHERE (c."City", c."CustomerID") > (@__city1_0, 'OCEAN') """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index e18c0bfa7..3f73b78cf 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -1426,15 +1426,15 @@ public override async Task Nested_contains_with_Lists_and_no_inferred_type_mappi AssertSql( """ -@__ints_1={ '1', '2', '3' } (DbType = Object) -@__strings_0={ 'one', 'two', 'three' } (DbType = Object) +@__ints_0={ '1', '2', '3' } (DbType = Object) +@__strings_1={ 'one', 'two', 'three' } (DbType = Object) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE CASE - WHEN p."Int" = ANY (@__ints_1) THEN 'one' + WHEN p."Int" = ANY (@__ints_0) THEN 'one' ELSE 'two' -END = ANY (@__strings_0) +END = ANY (@__strings_1) """); } @@ -1444,15 +1444,15 @@ public override async Task Nested_contains_with_arrays_and_no_inferred_type_mapp AssertSql( """ -@__ints_1={ '1', '2', '3' } (DbType = Object) -@__strings_0={ 'one', 'two', 'three' } (DbType = Object) +@__ints_0={ '1', '2', '3' } (DbType = Object) +@__strings_1={ 'one', 'two', 'three' } (DbType = Object) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE CASE - WHEN p."Int" = ANY (@__ints_1) THEN 'one' + WHEN p."Int" = ANY (@__ints_0) THEN 'one' ELSE 'two' -END = ANY (@__strings_0) +END = ANY (@__strings_1) """); } @@ -1466,7 +1466,7 @@ private void AssertSql(params string[] expected) private PrimitiveCollectionsContext CreateContext() => Fixture.CreateContext(); - public class PrimitiveCollectionsQueryNpgsqlFixture : PrimitiveCollectionsQueryFixtureBase + public class PrimitiveCollectionsQueryNpgsqlFixture : PrimitiveCollectionsQueryFixtureBase, ITestSqlLoggerFactory { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs index fac71b515..2c10adc6f 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs @@ -2,9 +2,15 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class SqlExecutorNpgsqlTest(NorthwindQueryNpgsqlFixture fixture) - : SqlExecutorTestBase>(fixture) +public class SqlExecutorNpgsqlTest : SqlExecutorTestBase> { + public SqlExecutorNpgsqlTest(NorthwindQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + protected override DbParameter CreateDbParameter(string name, object value) => new NpgsqlParameter { ParameterName = name, Value = value }; diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index f94f7c692..4721eba93 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -860,7 +860,7 @@ public class Entity public NpgsqlRange TimestampDateTimeRange { get; set; } } - public class TimestampQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase + public class TimestampQueryFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory { protected override string StoreName => "TimestampQueryTest"; diff --git a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs index ec24c062d..02ed4cdf2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs +++ b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs @@ -232,12 +232,12 @@ public override void Scalar_Function_Let_Nested_Static() AssertSql( """ -@__starCount_0='3' -@__customerId_1='1' +@__starCount_1='3' +@__customerId_0='1' -SELECT c."LastName", "StarValue"(@__starCount_0, "CustomerOrderCount"(@__customerId_1)) AS "OrderCount" +SELECT c."LastName", "StarValue"(@__starCount_1, "CustomerOrderCount"(@__customerId_0)) AS "OrderCount" FROM "Customers" AS c -WHERE c."Id" = @__customerId_1 +WHERE c."Id" = @__customerId_0 LIMIT 2 """); } @@ -530,12 +530,12 @@ public override void Scalar_Function_Let_Nested_Instance() AssertSql( """ -@__starCount_1='3' -@__customerId_2='1' +@__starCount_2='3' +@__customerId_1='1' -SELECT c."LastName", "StarValue"(@__starCount_1, "CustomerOrderCount"(@__customerId_2)) AS "OrderCount" +SELECT c."LastName", "StarValue"(@__starCount_2, "CustomerOrderCount"(@__customerId_1)) AS "OrderCount" FROM "Customers" AS c -WHERE c."Id" = @__customerId_2 +WHERE c."Id" = @__customerId_1 LIMIT 2 """); } diff --git a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs index cd67af7b5..8b64a5cb1 100644 --- a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs @@ -1986,20 +1986,20 @@ public override Task Edit_single_property_relational_collection_of_nullable_enum => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum()); public override Task Edit_single_property_relational_collection_of_nullable_enum_set_to_null() - => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum_set_to_null()); + => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum_set_to_null()); public override Task Edit_single_property_relational_collection_of_nullable_enum_with_int_converter() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum_with_int_converter()); public override Task Edit_single_property_relational_collection_of_nullable_enum_with_int_converter_set_to_null() - => Assert.ThrowsAsync( + => Assert.ThrowsAsync( () => base.Edit_single_property_relational_collection_of_nullable_enum_with_int_converter_set_to_null()); public override Task Edit_single_property_relational_collection_of_nullable_int32() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_int32()); public override Task Edit_single_property_relational_collection_of_nullable_int32_set_to_null() - => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_int32_set_to_null()); + => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_int32_set_to_null()); public override Task Edit_single_property_relational_collection_of_uint16() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_uint16()); diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs index 7841a2a3c..134e4e820 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs @@ -1,5 +1,6 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; +using Xunit.Sdk; namespace Npgsql.EntityFrameworkCore.PostgreSQL; @@ -339,34 +340,43 @@ await AssertQuery( t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).ToInstant() == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + Assert.Throws( + () => + AssertSql( + """ @__ToInstant_0='2018-04-20T08:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."LocalDateTime" AT TIME ZONE 'Europe/Berlin' = @__ToInstant_0 -"""); +""")); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task LocalDateTime_InZoneLeniently_ToInstant_with_column_time_zone(bool async) { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb[t.TimeZoneId]).ToInstant() - == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + await Assert.ThrowsAsync( + async () => + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb[t.TimeZoneId]).ToInstant() + == new ZonedDateTime( + new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ @__ToInstant_0='2018-04-20T08:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."LocalDateTime" AT TIME ZONE n."TimeZoneId" = @__ToInstant_0 """); + }); } [ConditionalFact] @@ -1491,20 +1501,25 @@ WHERE @__dateRange_0 @> n."LocalDate" [MemberData(nameof(IsAsyncData))] public async Task Instance_InUtc(bool async) { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.Instant.InUtc() - == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero))); - - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + await Assert.ThrowsAsync( + async () => + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.Instant.InUtc() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero))); + + AssertSql( + """ @__p_0='2018-04-20T10:31:33 UTC (+00)' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."Instant" = @__p_0 """); + }); } [ConditionalTheory] @@ -1797,36 +1812,45 @@ await AssertQuery( [MemberData(nameof(IsAsyncData))] public async Task ZonedDateTime_ToInstant(bool async) { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.ZonedDateTime.ToInstant() - == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + await Assert.ThrowsAsync( + async () => + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.ZonedDateTime.ToInstant() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ @__ToInstant_0='2018-04-20T10:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."ZonedDateTime" = @__ToInstant_0 """); + }); } [ConditionalFact] public async Task ZonedDateTime_Distance() { await using var context = CreateContext(); - var closest = await context.NodaTimeTypes - .OrderBy( - t => EF.Functions.Distance( - t.ZonedDateTime, - new ZonedDateTime(new LocalDateTime(2018, 4, 1, 0, 0, 0), DateTimeZone.Utc, Offset.Zero))).FirstAsync(); - - Assert.Equal(1, closest.Id); - AssertSql( - """ + // TODO: https://github.com/dotnet/efcore/pull/33241 + await Assert.ThrowsAsync( + async () => + { + var closest = await context.NodaTimeTypes + .OrderBy( + t => EF.Functions.Distance( + t.ZonedDateTime, + new ZonedDateTime(new LocalDateTime(2018, 4, 1, 0, 0, 0), DateTimeZone.Utc, Offset.Zero))).FirstAsync(); + Assert.Equal(1, closest.Id); + + AssertSql( + """ @__p_1='2018-04-01T00:00:00 UTC (+00)' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" @@ -1834,6 +1858,7 @@ public async Task ZonedDateTime_Distance() ORDER BY n."ZonedDateTime" <-> @__p_1 NULLS FIRST LIMIT 1 """); + }); } #endregion ZonedDateTime diff --git a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs index c5ce7a25a..1b69e2dbe 100644 --- a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs @@ -47,10 +47,13 @@ private void AssertGenerator(string propertyName, bool setSequences = var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - Assert.IsType(selector.Select(entityType.FindProperty(propertyName), entityType)); + var property = entityType.FindProperty(propertyName)!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + + Assert.IsType(generator); } - [Fact] + [ConditionalFact] public void Returns_temp_guid_generator_when_default_sql_set() { var builder = NpgsqlTestHelpers.Instance.CreateConventionBuilder(); @@ -65,7 +68,9 @@ public void Returns_temp_guid_generator_when_default_sql_set() var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - Assert.IsType(selector.Select(entityType.FindProperty("Guid"), entityType)); + var property = entityType.FindProperty("Guid")!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + Assert.IsType(generator); } [ConditionalFact] @@ -83,7 +88,9 @@ public void Returns_temp_string_generator_when_default_sql_set() var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - var generator = selector.Select(entityType.FindProperty("String"), entityType); + var property = entityType.FindProperty("String")!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + Assert.IsType(generator); Assert.True(generator.GeneratesTemporaryValues); } @@ -103,7 +110,9 @@ public void Returns_temp_binary_generator_when_default_sql_set() var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - var generator = selector.Select(entityType.FindProperty("Binary"), entityType); + var property = entityType.FindProperty("Binary")!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + Assert.IsType(generator); Assert.True(generator.GeneratesTemporaryValues); } @@ -159,7 +168,10 @@ public void Returns_generator_configured_on_model_when_property_is_identity() var selector = NpgsqlTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - Assert.IsType>(selector.Select(entityType.FindProperty("Id"), entityType)); + var property = entityType.FindProperty("Id")!; + Assert.True(selector.TrySelect(property, property.DeclaringType, out var generator)); + + Assert.IsType>(generator); } private class AnEntity From 533b9d31aaf9a532b1a44069329aee548240f8ef Mon Sep 17 00:00:00 2001 From: Obed Kooijman Date: Wed, 20 Mar 2024 21:50:09 +0100 Subject: [PATCH 030/107] Add Translation for to_date and to_timestamp as DBFunctions extensions (#2936) Closes #2505 --- .../NpgsqlDbFunctionsExtensions.cs | 20 +++++++++++++ .../Internal/NpgsqlStringMethodTranslator.cs | 30 +++++++++++++++++++ .../NorthwindDbFunctionsQueryNpgsqlTest.cs | 28 +++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs index 9488c8ec8..15cf1ae2f 100644 --- a/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs +++ b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs @@ -141,4 +141,24 @@ public static int Distance(this DbFunctions _, DateOnly a, DateOnly b) /// public static TimeSpan Distance(this DbFunctions _, DateTime a, DateTime b) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Distance))); + + /// + /// Converts string to date according to the given format. + /// + /// The instance. + /// The string to be converted. + /// The format of the input date. + /// + public static DateOnly? ToDate(this DbFunctions _, string? value, string? format) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ToDate))); + + /// + /// Converts string to time stamp according to the given format. + /// + /// The instance. + /// The string to be converted + /// The format of the input date + /// + public static DateTime? ToTimestamp(this DbFunctions _, string? value, string? format) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ToTimestamp))); } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs index 3a97d91e8..4cf7a59de 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs @@ -78,6 +78,12 @@ public class NpgsqlStringMethodTranslator : IMethodCallTranslator private static readonly MethodInfo StringToArrayNullString = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( nameof(NpgsqlDbFunctionsExtensions.StringToArray), [typeof(DbFunctions), typeof(string), typeof(string), typeof(string)])!; + private static readonly MethodInfo ToDate = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( + nameof(NpgsqlDbFunctionsExtensions.ToDate), [typeof(DbFunctions), typeof(string), typeof(string)])!; + + private static readonly MethodInfo ToTimestamp = typeof(NpgsqlDbFunctionsExtensions).GetRuntimeMethod( + nameof(NpgsqlDbFunctionsExtensions.ToTimestamp), [typeof(DbFunctions), typeof(string), typeof(string)])!; + private static readonly MethodInfo FirstOrDefaultMethodInfoWithoutArgs = typeof(Enumerable).GetRuntimeMethods().Single( m => m.Name == nameof(Enumerable.FirstOrDefault) @@ -425,6 +431,30 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I _typeMappingSource.FindMapping(typeof(string[]))); } + if (method == ToDate) + { + return _sqlExpressionFactory.Function( + "to_date", + new[] { arguments[1], arguments[2] }, + nullable: true, + argumentsPropagateNullability: new[] { true, true }, + typeof(DateOnly?), + _typeMappingSource.FindMapping(typeof(DateOnly)) + ); + } + + if (method == ToTimestamp) + { + return _sqlExpressionFactory.Function( + "to_timestamp", + new[] { arguments[1], arguments[2] }, + nullable: true, + argumentsPropagateNullability: new[] { true, true }, + typeof(DateTime?), + _typeMappingSource.FindMapping(typeof(DateTime)) + ); + } + return null; } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs index 46d03dc14..4b9f7a5da 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs @@ -227,6 +227,34 @@ WHERE string_to_array(c."ContactName", ' ', 'Maria') = ARRAY[NULL,'Anders']::tex """); } + [Fact] + public void ToDate() + { + using var context = CreateContext(); + var count = context.Orders.Count(c => EF.Functions.ToDate(c.OrderDate.ToString(), "YYYY-MM-DD") < new DateOnly(2000, 01, 01)); + Assert.Equal(830, count); + AssertSql( +""" +SELECT count(*)::int +FROM "Orders" AS o +WHERE to_date(o."OrderDate"::text, 'YYYY-MM-DD') < DATE '2000-01-01' +"""); + } + + [Fact] + public void ToTimestamp() + { + using var context = CreateContext(); + var count = context.Orders.Count(c => EF.Functions.ToTimestamp(c.OrderDate.ToString(), "YYYY-MM-DD") < new DateTime(2000, 01, 01, 0,0,0, DateTimeKind.Utc)); + Assert.Equal(830, count); + AssertSql( + """ +SELECT count(*)::int +FROM "Orders" AS o +WHERE to_timestamp(o."OrderDate"::text, 'YYYY-MM-DD') < TIMESTAMPTZ '2000-01-01T00:00:00Z' +"""); + } + #endregion private void AssertSql(params string[] expected) From ea6f586dee4fa4c1ad9c4278337cf147ba756188 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 22 Mar 2024 16:18:20 +0100 Subject: [PATCH 031/107] Bump version to 9.0.0-preview.2 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index c27355bf6..7cc2d86ac 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0-preview.1 + 9.0.0-preview.2 latest true latest From 7f6bdbe94e70b1aece7a1ec71f49b914edffd4ec Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 22 Mar 2024 16:19:09 +0100 Subject: [PATCH 032/107] Bump version to v9.0.0-preview.3 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 7cc2d86ac..b913e4b87 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0-preview.2 + 9.0.0-preview.3 latest true latest From dcef41511b6ba0b9a0bf5add8e3f6a8c50dae999 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 1 Apr 2024 11:00:26 +0200 Subject: [PATCH 033/107] Make INpgsqlRelationalConnection.CloneWith return INpgsqlRelationalConnection (#3144) Closes #3124 --- src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs | 2 +- src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs index 6e8e74dfb..cfeef63a2 100644 --- a/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs @@ -22,5 +22,5 @@ public interface INpgsqlRelationalConnection : IRelationalConnection /// 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. /// - NpgsqlRelationalConnection CloneWith(string connectionString); + INpgsqlRelationalConnection CloneWith(string connectionString); } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs index 79e651096..77b3f81e2 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs @@ -215,7 +215,7 @@ public override Transaction? CurrentAmbientTransaction /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual NpgsqlRelationalConnection CloneWith(string connectionString) + public virtual INpgsqlRelationalConnection CloneWith(string connectionString) { var clonedDbConnection = DbConnection.CloneWith(connectionString); From 18008c9d69090b979bebf791ccd21c07e5282ce4 Mon Sep 17 00:00:00 2001 From: Obed Kooijman Date: Tue, 2 Apr 2024 23:55:51 +0200 Subject: [PATCH 034/107] Changed ToDate/ToTimestamp to non-nullable (#3146) --- .../DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs index 15cf1ae2f..868faa7e2 100644 --- a/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs +++ b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlDbFunctionsExtensions.cs @@ -149,7 +149,7 @@ public static TimeSpan Distance(this DbFunctions _, DateTime a, DateTime b) /// The string to be converted. /// The format of the input date. /// - public static DateOnly? ToDate(this DbFunctions _, string? value, string? format) + public static DateOnly ToDate(this DbFunctions _, string value, string format) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ToDate))); /// @@ -159,6 +159,6 @@ public static TimeSpan Distance(this DbFunctions _, DateTime a, DateTime b) /// The string to be converted /// The format of the input date /// - public static DateTime? ToTimestamp(this DbFunctions _, string? value, string? format) + public static DateTime ToTimestamp(this DbFunctions _, string value, string format) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ToTimestamp))); } From ac91e9a0cbfb359fabca58f621c9c08b9720e5b1 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 3 Apr 2024 12:51:09 +0200 Subject: [PATCH 035/107] Stop eagerly throwing when setting value generation strategy (#3147) Closes #3141 --- .../NpgsqlPropertyBuilderExtensions.cs | 30 ++++++++--- .../NpgsqlPropertyExtensions.cs | 54 +++---------------- .../Internal/NpgsqlModelValidator.cs | 49 +++++++++++++++++ .../NpgsqlMetadataBuilderExtensionsTest.cs | 18 ------- 4 files changed, 77 insertions(+), 74 deletions(-) diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlPropertyBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlPropertyBuilderExtensions.cs index 6c4362b7b..2698800d3 100644 --- a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlPropertyBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlPropertyBuilderExtensions.cs @@ -425,6 +425,26 @@ public static PropertyBuilder UseIdentityColumn( return null; } + /// + /// Returns a value indicating whether the given value can be set as the value generation strategy for a particular table. + /// + /// The builder for the property being configured. + /// The value generation strategy. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the default value generation strategy. + public static bool CanSetValueGenerationStrategy( + this IConventionPropertyBuilder propertyBuilder, + NpgsqlValueGenerationStrategy? valueGenerationStrategy, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder + .CanSetAnnotation( + NpgsqlAnnotationNames.ValueGenerationStrategy, + valueGenerationStrategy, + fromDataAnnotation) + ?? true; + /// /// Returns a value indicating whether the given value can be set as the value generation strategy. /// @@ -436,14 +456,8 @@ public static bool CanSetValueGenerationStrategy( this IConventionPropertyBuilder propertyBuilder, NpgsqlValueGenerationStrategy? valueGenerationStrategy, bool fromDataAnnotation = false) - { - Check.NotNull(propertyBuilder, nameof(propertyBuilder)); - - return (valueGenerationStrategy is null - || NpgsqlPropertyExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) - && propertyBuilder.CanSetAnnotation( - NpgsqlAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation); - } + => propertyBuilder.CanSetAnnotation( + NpgsqlAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation); #endregion General value generation strategy diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs index da331f59f..af88adbf5 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs @@ -589,11 +589,7 @@ private static NpgsqlValueGenerationStrategy GetDefaultValueGenerationStrategy( public static void SetValueGenerationStrategy( this IMutableProperty property, NpgsqlValueGenerationStrategy? value) - { - CheckValueGenerationStrategy(property, value); - - property.SetOrRemoveAnnotation(NpgsqlAnnotationNames.ValueGenerationStrategy, value); - } + => property.SetOrRemoveAnnotation(NpgsqlAnnotationNames.ValueGenerationStrategy, value); /// /// Sets the to use for the property. @@ -605,13 +601,8 @@ public static void SetValueGenerationStrategy( this IConventionProperty property, NpgsqlValueGenerationStrategy? value, bool fromDataAnnotation = false) - { - CheckValueGenerationStrategy(property, value); - - return (NpgsqlValueGenerationStrategy?)property.SetOrRemoveAnnotation( - NpgsqlAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation) - ?.Value; - } + => (NpgsqlValueGenerationStrategy?)property.SetOrRemoveAnnotation( + NpgsqlAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation)?.Value; /// /// Sets the to use for the property for a particular table. @@ -650,11 +641,7 @@ public static void SetValueGenerationStrategy( public static void SetValueGenerationStrategy( this IMutableRelationalPropertyOverrides overrides, NpgsqlValueGenerationStrategy? value) - { - CheckValueGenerationStrategy(overrides.Property, value); - - overrides.SetOrRemoveAnnotation(NpgsqlAnnotationNames.ValueGenerationStrategy, value); - } + => overrides.SetOrRemoveAnnotation(NpgsqlAnnotationNames.ValueGenerationStrategy, value); /// /// Sets the to use for the property for a particular table. @@ -667,37 +654,8 @@ public static void SetValueGenerationStrategy( this IConventionRelationalPropertyOverrides overrides, NpgsqlValueGenerationStrategy? value, bool fromDataAnnotation = false) - { - CheckValueGenerationStrategy(overrides.Property, value); - - return (NpgsqlValueGenerationStrategy?)overrides.SetOrRemoveAnnotation( - NpgsqlAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation) - ?.Value; - } - - private static void CheckValueGenerationStrategy(IReadOnlyProperty property, NpgsqlValueGenerationStrategy? value) - { - if (value is not null) - { - var propertyType = property.ClrType; - - if ((value is NpgsqlValueGenerationStrategy.IdentityAlwaysColumn or NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - && !IsCompatibleWithValueGeneration(property)) - { - throw new ArgumentException( - NpgsqlStrings.IdentityBadType( - property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName())); - } - - if (value is NpgsqlValueGenerationStrategy.SerialColumn or NpgsqlValueGenerationStrategy.SequenceHiLo - && !IsCompatibleWithValueGeneration(property)) - { - throw new ArgumentException( - NpgsqlStrings.SequenceBadType( - property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName())); - } - } - } + => (NpgsqlValueGenerationStrategy?)overrides.SetOrRemoveAnnotation( + NpgsqlAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation)?.Value; /// /// Returns the for the . diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlModelValidator.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlModelValidator.cs index 14f8dcbf2..6942a4f1e 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlModelValidator.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlModelValidator.cs @@ -103,6 +103,55 @@ protected override void ValidateValueGeneration( } } + /// + protected override void ValidateTypeMappings( + IModel model, + IDiagnosticsLogger logger) + { + base.ValidateTypeMappings(model, logger); + + foreach (var entityType in model.GetEntityTypes()) + { + foreach (var property in entityType.GetFlattenedDeclaredProperties()) + { + var strategy = property.GetValueGenerationStrategy(); + var propertyType = property.ClrType; + + switch (strategy) + { + case NpgsqlValueGenerationStrategy.None: + break; + + case NpgsqlValueGenerationStrategy.IdentityByDefaultColumn: + case NpgsqlValueGenerationStrategy.IdentityAlwaysColumn: + if (!NpgsqlPropertyExtensions.IsCompatibleWithValueGeneration(property)) + { + throw new InvalidOperationException( + NpgsqlStrings.IdentityBadType( + property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName())); + } + + break; + + case NpgsqlValueGenerationStrategy.SequenceHiLo: + case NpgsqlValueGenerationStrategy.Sequence: + case NpgsqlValueGenerationStrategy.SerialColumn: + if (!NpgsqlPropertyExtensions.IsCompatibleWithValueGeneration(property)) + { + throw new InvalidOperationException( + NpgsqlStrings.SequenceBadType( + property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName())); + } + + break; + + default: + throw new UnreachableException(); + } + } + } + } + /// /// 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 diff --git a/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs b/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs index 287bbcd18..78e23abfb 100644 --- a/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs +++ b/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs @@ -75,24 +75,6 @@ public void Can_access_property() a => a.Name.StartsWith(NpgsqlAnnotationNames.Prefix, StringComparison.Ordinal))); } - [ConditionalFact] - public void Throws_setting_sequence_generation_for_invalid_type() - { - var propertyBuilder = CreateBuilder() - .Entity(typeof(Splot)) - .Property(typeof(string), "Name"); - - Assert.Equal( - NpgsqlStrings.SequenceBadType("Name", nameof(Splot), "string"), - Assert.Throws( - () => propertyBuilder.HasValueGenerationStrategy(NpgsqlValueGenerationStrategy.SequenceHiLo)).Message); - - Assert.Equal( - NpgsqlStrings.SequenceBadType("Name", nameof(Splot), "string"), - Assert.Throws( - () => new PropertyBuilder((IMutableProperty)propertyBuilder.Metadata).UseHiLo()).Message); - } - [ConditionalFact] public void Can_access_index() { From 17fa8de6dec06936c8958637dcd4d0aa5d36ba1e Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 4 Apr 2024 16:19:51 +0200 Subject: [PATCH 036/107] Small primitive collection cleanup (#3149) Apply Redshift opt-out in translation phase instead of in preprocessing, like SQL Server. --- .../NpgsqlServiceCollectionExtensions.cs | 1 - .../Internal/NpgsqlQueryRootProcessor.cs | 43 ---------------- .../NpgsqlQueryTranslationPreprocessor.cs | 40 --------------- ...gsqlQueryTranslationPreprocessorFactory.cs | 49 ------------------- ...yableMethodTranslatingExpressionVisitor.cs | 17 +++++-- ...thodTranslatingExpressionVisitorFactory.cs | 11 ++++- 6 files changed, 23 insertions(+), 138 deletions(-) delete mode 100644 src/EFCore.PG/Query/Internal/NpgsqlQueryRootProcessor.cs delete mode 100644 src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPreprocessor.cs delete mode 100644 src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPreprocessorFactory.cs diff --git a/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs b/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs index 4c3e8a1cf..6335c3429 100644 --- a/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs +++ b/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs @@ -114,7 +114,6 @@ public static IServiceCollection AddEntityFrameworkNpgsql(this IServiceCollectio .TryAdd() .TryAdd() .TryAdd() - .TryAdd() .TryAdd() .TryAdd() .TryAdd() diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryRootProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryRootProcessor.cs deleted file mode 100644 index ade336f61..000000000 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryRootProcessor.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public class NpgsqlQueryRootProcessor : RelationalQueryRootProcessor -{ - private readonly bool _supportsUnnest; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public NpgsqlQueryRootProcessor( - QueryTranslationPreprocessorDependencies dependencies, - RelationalQueryTranslationPreprocessorDependencies relationalDependencies, - QueryCompilationContext queryCompilationContext, - INpgsqlSingletonOptions npgsqlSingletonOptions) - : base(dependencies, relationalDependencies, queryCompilationContext) - { - _supportsUnnest = !npgsqlSingletonOptions.UseRedshift; - } - - /// - /// Converts a to a , to be later translated to - /// PostgreSQL unnest over an array parameter. - /// - /// - /// 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 ShouldConvertToParameterQueryRoot(ParameterExpression parameterExpression) - => _supportsUnnest; -} diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPreprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPreprocessor.cs deleted file mode 100644 index b7ccfc05d..000000000 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPreprocessor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public class NpgsqlQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor -{ - private readonly INpgsqlSingletonOptions _npgsqlSingletonOptions; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public NpgsqlQueryTranslationPreprocessor( - QueryTranslationPreprocessorDependencies dependencies, - RelationalQueryTranslationPreprocessorDependencies relationalDependencies, - INpgsqlSingletonOptions npgsqlSingletonOptions, - QueryCompilationContext queryCompilationContext) - : base(dependencies, relationalDependencies, queryCompilationContext) - { - _npgsqlSingletonOptions = npgsqlSingletonOptions; - } - - /// - /// 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 Expression ProcessQueryRoots(Expression expression) - => new NpgsqlQueryRootProcessor(Dependencies, RelationalDependencies, QueryCompilationContext, _npgsqlSingletonOptions) - .Visit(expression); -} diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPreprocessorFactory.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPreprocessorFactory.cs deleted file mode 100644 index c1f9e818b..000000000 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPreprocessorFactory.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public class NpgsqlQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory -{ - private readonly INpgsqlSingletonOptions _npgsqlSingletonOptions; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public NpgsqlQueryTranslationPreprocessorFactory( - QueryTranslationPreprocessorDependencies dependencies, - RelationalQueryTranslationPreprocessorDependencies relationalDependencies, - INpgsqlSingletonOptions npgsqlSingletonOptions) - { - Dependencies = dependencies; - RelationalDependencies = relationalDependencies; - _npgsqlSingletonOptions = npgsqlSingletonOptions; - } - - /// - /// Dependencies for this service. - /// - protected virtual QueryTranslationPreprocessorDependencies Dependencies { get; } - - /// - /// Relational provider-specific dependencies for this service. - /// - protected virtual RelationalQueryTranslationPreprocessorDependencies RelationalDependencies { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext) - => new NpgsqlQueryTranslationPreprocessor(Dependencies, RelationalDependencies, _npgsqlSingletonOptions, queryCompilationContext); -} diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index d258ebb8c..84ec8668a 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; @@ -18,9 +19,9 @@ public class NpgsqlQueryableMethodTranslatingExpressionVisitor : RelationalQuery private readonly RelationalQueryCompilationContext _queryCompilationContext; private readonly NpgsqlTypeMappingSource _typeMappingSource; private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; + private readonly bool _isRedshift; private RelationalTypeMapping? _ordinalityTypeMapping; - #region MethodInfos private static readonly MethodInfo Like2MethodInfo = @@ -46,12 +47,14 @@ private static readonly MethodInfo ILike2MethodInfo public NpgsqlQueryableMethodTranslatingExpressionVisitor( QueryableMethodTranslatingExpressionVisitorDependencies dependencies, RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, - RelationalQueryCompilationContext queryCompilationContext) + RelationalQueryCompilationContext queryCompilationContext, + INpgsqlSingletonOptions npgsqlSingletonOptions) : base(dependencies, relationalDependencies, queryCompilationContext) { _queryCompilationContext = queryCompilationContext; _typeMappingSource = (NpgsqlTypeMappingSource)relationalDependencies.TypeMappingSource; _sqlExpressionFactory = (NpgsqlSqlExpressionFactory)relationalDependencies.SqlExpressionFactory; + _isRedshift = npgsqlSingletonOptions.UseRedshift; } /// @@ -66,6 +69,7 @@ protected NpgsqlQueryableMethodTranslatingExpressionVisitor(NpgsqlQueryableMetho _queryCompilationContext = parentVisitor._queryCompilationContext; _typeMappingSource = parentVisitor._typeMappingSource; _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; + _isRedshift = parentVisitor._isRedshift; } /// @@ -83,11 +87,18 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis /// 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 ShapedQueryExpression TranslatePrimitiveCollection( + protected override ShapedQueryExpression? TranslatePrimitiveCollection( SqlExpression sqlExpression, IProperty? property, string tableAlias) { + if (_isRedshift) + { + AddTranslationErrorDetails("Redshift does not support unnest, which is required for most forms of querying of JSON arrays."); + + return null; + } + var elementClrType = sqlExpression.Type.GetSequenceType(); var elementTypeMapping = (RelationalTypeMapping?)sqlExpression.TypeMapping?.ElementTypeMapping; diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs index b6e2eb279..39d8377f7 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitorFactory.cs @@ -1,3 +1,5 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; /// @@ -8,6 +10,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; /// public class NpgsqlQueryableMethodTranslatingExpressionVisitorFactory : IQueryableMethodTranslatingExpressionVisitorFactory { + private readonly INpgsqlSingletonOptions _npgsqlSingletonOptions; + /// /// 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 @@ -16,10 +20,12 @@ public class NpgsqlQueryableMethodTranslatingExpressionVisitorFactory : IQueryab /// public NpgsqlQueryableMethodTranslatingExpressionVisitorFactory( QueryableMethodTranslatingExpressionVisitorDependencies dependencies, - RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies) + RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, + INpgsqlSingletonOptions npgsqlSingletonOptions) { Dependencies = dependencies; RelationalDependencies = relationalDependencies; + _npgsqlSingletonOptions = npgsqlSingletonOptions; } /// @@ -48,5 +54,6 @@ public virtual QueryableMethodTranslatingExpressionVisitor Create(QueryCompilati => new NpgsqlQueryableMethodTranslatingExpressionVisitor( Dependencies, RelationalDependencies, - (RelationalQueryCompilationContext)queryCompilationContext); + (RelationalQueryCompilationContext)queryCompilationContext, + _npgsqlSingletonOptions); } From f38a96ceef14e36b11db9bf0111617b30f06c3bb Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 12 Apr 2024 17:28:21 +0300 Subject: [PATCH 037/107] Sync to 9.0.0-preview.3.24172.4 (#3153) * Implement quoting for custom SqlExpressions * Type mapping visitor changes * React to SqlConstantExpression changes --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- Directory.Build.props | 2 +- Directory.Packages.props | 12 +- EFCore.PG.sln.DotSettings | 207 +------ global.json | 4 +- .../PendingDateTimeZoneProviderExpression.cs | 3 + .../PendingZonedDateTimeExpression.cs | 3 + src/EFCore.PG/EFCore.PG.csproj | 1 + .../NpgsqlObjectToStringTranslator.cs | 2 +- .../Expressions/Internal/PgAllExpression.cs | 12 + .../Expressions/Internal/PgAnyExpression.cs | 12 + .../Internal/PgArrayIndexExpression.cs | 13 + .../Internal/PgArraySliceExpression.cs | 14 + .../Internal/PgBinaryExpression.cs | 13 + .../Expressions/Internal/PgILikeExpression.cs | 12 + .../Internal/PgJsonTraversalExpression.cs | 13 + .../Internal/PgNewArrayExpression.cs | 11 + .../Internal/PgRegexMatchExpression.cs | 12 + .../Internal/PgRowValueExpression.cs | 16 +- .../Internal/PgUnknownBinaryExpression.cs | 13 + .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 3 +- .../NpgsqlQueryTranslationPostprocessor.cs | 9 + ...yableMethodTranslatingExpressionVisitor.cs | 70 --- .../Internal/NpgsqlSqlNullabilityProcessor.cs | 2 +- .../NpgsqlSqlTranslatingExpressionVisitor.cs | 4 +- .../NpgsqlTypeMappingPostprocessor.cs | 60 +++ .../Query/NpgsqlSqlExpressionFactory.cs | 4 +- test/Directory.Build.props | 2 +- .../EFCore.PG.FunctionalTests.csproj | 4 + .../Migrations/MigrationsNpgsqlTest.cs | 507 ++++++++++-------- .../AdHocAdvancedMappingsQueryNpgsqlTest.cs | 5 + .../Query/ComplexTypeQueryNpgsqlTest.cs | 23 + .../TestUtilities/NpgsqlTestStore.cs | 74 +-- .../NodaTimeQueryNpgsqlTest.cs | 97 ++-- test/EFCore.PG.Tests/EFCore.PG.Tests.csproj | 4 + 36 files changed, 621 insertions(+), 626 deletions(-) create mode 100644 src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0dcb5e9e6..cabcf202d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: pull_request: env: - dotnet_sdk_version: '8.0.100' + dotnet_sdk_version: '9.0.100-preview.3.24204.13' postgis_version: 3 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6eed22bfd..e4a10226f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ on: - cron: '30 22 * * 6' env: - dotnet_sdk_version: '8.0.100' + dotnet_sdk_version: '9.0.100-preview.3.24204.13' jobs: analyze: diff --git a/Directory.Build.props b/Directory.Build.props index b913e4b87..5fb8849de 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,7 +12,7 @@ true true - Copyright 2023 © The Npgsql Development Team + Copyright 2024 © The Npgsql Development Team Npgsql true PostgreSQL diff --git a/Directory.Packages.props b/Directory.Packages.props index 93aecba8c..0fb184004 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - 9.0.0-preview.2.24128.4 - 9.0.0-preview.2.24128.5 + 9.0.0-preview.3.24172.4 + 9.0.0-preview.3.24172.9 8.0.2 @@ -23,8 +23,12 @@ - - + + + + + + diff --git a/EFCore.PG.sln.DotSettings b/EFCore.PG.sln.DotSettings index 0ae73aa7c..9055198e9 100644 --- a/EFCore.PG.sln.DotSettings +++ b/EFCore.PG.sln.DotSettings @@ -182,216 +182,11 @@ True True - - True - True - True - Side by side - Side by side - False - False - False - True - False - False - True - False - False - False - True - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> - True - FK - MARS - $object$_On$event$ - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Public" Description="Test Methods"><ElementKinds><Kind Name="TEST_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="Aa_bb" /></Policy> - EF - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> - $object$_On$event$ - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - - - True - C:\repos\EntityFrameworkCore\All.sln.DotSettings - - - - True - 2 - DO_NOTHING - True - True True - True True - True True - True True True - True - True - True - True True -<<<<<<< HEAD - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - True - + \ No newline at end of file diff --git a/global.json b/global.json index 817f0c380..50d6cea52 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "8.0.100-rc.2.23502.2", + "version": "9.0.100-preview.3.24204.13", "rollForward": "latestMajor", - "allowPrerelease": "true" + "allowPrerelease": true } } diff --git a/src/EFCore.PG.NodaTime/Query/Internal/PendingDateTimeZoneProviderExpression.cs b/src/EFCore.PG.NodaTime/Query/Internal/PendingDateTimeZoneProviderExpression.cs index 11879f214..7c2ee5ef9 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/PendingDateTimeZoneProviderExpression.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/PendingDateTimeZoneProviderExpression.cs @@ -9,6 +9,9 @@ private PendingDateTimeZoneProviderExpression() { } + public override Expression Quote() + => throw new UnreachableException("PendingDateTimeZoneProviderExpression is a temporary tree representation and should never be quoted"); + protected override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.Append("TZDB"); } diff --git a/src/EFCore.PG.NodaTime/Query/Internal/PendingZonedDateTimeExpression.cs b/src/EFCore.PG.NodaTime/Query/Internal/PendingZonedDateTimeExpression.cs index 35c8affee..41739d713 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/PendingZonedDateTimeExpression.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/PendingZonedDateTimeExpression.cs @@ -12,6 +12,9 @@ internal PendingZonedDateTimeExpression(SqlExpression operand, SqlExpression tim internal SqlExpression TimeZoneId { get; } + public override Expression Quote() + => throw new UnreachableException("PendingDateTimeZoneProviderExpression is a temporary tree representation and should never be quoted"); + protected override void Print(ExpressionPrinter expressionPrinter) { expressionPrinter.Visit(Operand); diff --git a/src/EFCore.PG/EFCore.PG.csproj b/src/EFCore.PG/EFCore.PG.csproj index 0ab1ace12..1fb3139f4 100644 --- a/src/EFCore.PG/EFCore.PG.csproj +++ b/src/EFCore.PG/EFCore.PG.csproj @@ -9,6 +9,7 @@ PostgreSQL/Npgsql provider for Entity Framework Core. npgsql;postgresql;postgres;Entity Framework Core;entity-framework-core;ef;efcore;orm;sql README.md + EF1003 diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs index 8478cae09..377939774 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs @@ -78,7 +78,7 @@ public NpgsqlObjectToStringTranslator(IRelationalTypeMappingSource typeMappingSo _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)), _sqlExpressionFactory.Constant(true.ToString())) }, - _sqlExpressionFactory.Constant(null)) + _sqlExpressionFactory.Constant(null, typeof(string))) : _sqlExpressionFactory.Case( new[] { diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgAllExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgAllExpression.cs index 714b895ac..0ee27abd3 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgAllExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgAllExpression.cs @@ -8,6 +8,8 @@ /// public class PgAllExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// public override Type Type => typeof(bool); @@ -63,6 +65,16 @@ public virtual PgAllExpression Update(SqlExpression item, SqlExpression array) ? new PgAllExpression(item, array, OperatorType, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgAllExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(PgAllOperatorType), typeof(RelationalTypeMapping)])!, + Item.Quote(), + Array.Quote(), + Constant(OperatorType), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override Expression VisitChildren(ExpressionVisitor visitor) => Update((SqlExpression)visitor.Visit(Item), (SqlExpression)visitor.Visit(Array)); diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs index 94c2330dd..3d57bc228 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs @@ -11,6 +11,8 @@ /// public class PgAnyExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// public override Type Type => typeof(bool); @@ -74,6 +76,16 @@ public virtual PgAnyExpression Update(SqlExpression item, SqlExpression array) ? new PgAnyExpression(item, array, OperatorType, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgAnyExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(PgAllOperatorType), typeof(RelationalTypeMapping)])!, + Item.Quote(), + Array.Quote(), + Constant(OperatorType), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override Expression VisitChildren(ExpressionVisitor visitor) => Update((SqlExpression)visitor.Visit(Item), (SqlExpression)visitor.Visit(Array)); diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgArrayIndexExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgArrayIndexExpression.cs index 4a4f3d51b..d3e676fb7 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgArrayIndexExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgArrayIndexExpression.cs @@ -9,6 +9,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// public class PgArrayIndexExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// /// The array being indexed. /// @@ -75,6 +77,17 @@ public virtual PgArrayIndexExpression Update(SqlExpression array, SqlExpression ? this : new PgArrayIndexExpression(array, index, IsNullable, Type, TypeMapping); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgArrayIndexExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(bool), typeof(Type), typeof(RelationalTypeMapping)])!, + Array.Quote(), + Index.Quote(), + Constant(IsNullable), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override Expression VisitChildren(ExpressionVisitor visitor) => Update((SqlExpression)visitor.Visit(Array), (SqlExpression)visitor.Visit(Index)); diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs index 3d03488f1..9c6d56fbe 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs @@ -8,6 +8,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// public class PgArraySliceExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// /// The array being sliced. /// @@ -72,6 +74,18 @@ public virtual PgArraySliceExpression Update(SqlExpression array, SqlExpression? ? this : new PgArraySliceExpression(array, lowerBound, upperBound, IsNullable, Type, TypeMapping); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgArraySliceExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(bool), typeof(Type), typeof(RelationalTypeMapping)])!, + Array.Quote(), + RelationalExpressionQuotingUtilities.VisitOrNull(LowerBound), + RelationalExpressionQuotingUtilities.VisitOrNull(UpperBound), + Constant(IsNullable), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override Expression VisitChildren(ExpressionVisitor visitor) => Update( diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgBinaryExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgBinaryExpression.cs index 0cbee2c1e..03387b988 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgBinaryExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgBinaryExpression.cs @@ -7,6 +7,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// public class PgBinaryExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -74,6 +76,17 @@ public virtual PgBinaryExpression Update(SqlExpression left, SqlExpression right : this; } + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgBinaryExpression).GetConstructor( + [typeof(PgExpressionType), typeof(SqlExpression), typeof(SqlExpression), typeof(Type), typeof(RelationalTypeMapping)])!, + Constant(OperatorType), + Left.Quote(), + Right.Quote(), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs index 850b1f358..2304618ec 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs @@ -6,6 +6,8 @@ // ReSharper disable once InconsistentNaming public class PgILikeExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// /// The match expression. /// @@ -60,6 +62,16 @@ public virtual PgILikeExpression Update( ? this : new PgILikeExpression(match, pattern, escapeChar, TypeMapping); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgILikeExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(RelationalTypeMapping)])!, + Match.Quote(), + Pattern.Quote(), + RelationalExpressionQuotingUtilities.VisitOrNull(EscapeChar), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// public override bool Equals(object? obj) => obj is PgILikeExpression other && Equals(other); diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgJsonTraversalExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgJsonTraversalExpression.cs index 076c8caea..61871b567 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgJsonTraversalExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgJsonTraversalExpression.cs @@ -5,6 +5,8 @@ /// public class PgJsonTraversalExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// /// The match expression. /// @@ -56,6 +58,17 @@ public virtual PgJsonTraversalExpression Update(SqlExpression expression, IReadO ? this : new PgJsonTraversalExpression(expression, path, ReturnsText, Type, TypeMapping); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgJsonTraversalExpression).GetConstructor( + [typeof(SqlExpression), typeof(IReadOnlyList), typeof(bool), typeof(Type), typeof(RelationalTypeMapping)])!, + Expression.Quote(), + NewArrayInit(typeof(SqlExpression), initializers: Path.Select(a => a.Quote())), + Constant(ReturnsText), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// /// Appends an additional path component to this and returns the result. /// diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs index 5aafba511..22cfe208d 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgNewArrayExpression.cs @@ -5,6 +5,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// public class PgNewArrayExpression : SqlExpression { + private static ConstructorInfo? _quotingConstructor; + /// /// Creates a new instance of the class. /// @@ -74,6 +76,15 @@ public virtual PgNewArrayExpression Update(IReadOnlyList expressi : new PgNewArrayExpression(expressions, Type, TypeMapping); } + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgNewArrayExpression).GetConstructor( + [typeof(IReadOnlyList), typeof(Type), typeof(RelationalTypeMapping)])!, + NewArrayInit(typeof(SqlExpression), initializers: Expressions.Select(a => a.Quote())), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgRegexMatchExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgRegexMatchExpression.cs index fa2a1a34b..f907e7b42 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgRegexMatchExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgRegexMatchExpression.cs @@ -7,6 +7,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// public class PgRegexMatchExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// public override Type Type => typeof(bool); @@ -58,6 +60,16 @@ public virtual PgRegexMatchExpression Update(SqlExpression match, SqlExpression ? new PgRegexMatchExpression(match, pattern, Options, TypeMapping) : this; + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgRegexMatchExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(RegexOptions), typeof(RelationalTypeMapping)])!, + Match.Quote(), + Pattern.Quote(), + Constant(Options), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// public virtual bool Equals(PgRegexMatchExpression? other) => ReferenceEquals(this, other) diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgRowValueExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgRowValueExpression.cs index 19d8c6b22..0d0e6f305 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgRowValueExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgRowValueExpression.cs @@ -11,13 +11,18 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// public class PgRowValueExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// /// The values of this PostgreSQL row value expression. /// public virtual IReadOnlyList Values { get; } /// - public PgRowValueExpression(IReadOnlyList values, Type type, RelationalTypeMapping? typeMapping = null) + public PgRowValueExpression( + IReadOnlyList values, + Type type, + RelationalTypeMapping? typeMapping = null) : base(type, typeMapping) { Check.NotNull(values, nameof(values)); @@ -64,6 +69,15 @@ public virtual PgRowValueExpression Update(IReadOnlyList values) ? this : new PgRowValueExpression(values, Type); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgRowValueExpression).GetConstructor( + [typeof(IReadOnlyList), typeof(Type), typeof(RelationalTypeMapping)])!, + NewArrayInit(typeof(SqlExpression), initializers: Values.Select(a => a.Quote())), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// protected override void Print(ExpressionPrinter expressionPrinter) { diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgUnknownBinaryExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgUnknownBinaryExpression.cs index bf2b89126..2d8dd0ee7 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgUnknownBinaryExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgUnknownBinaryExpression.cs @@ -9,6 +9,8 @@ /// public class PgUnknownBinaryExpression : SqlExpression, IEquatable { + private static ConstructorInfo? _quotingConstructor; + /// /// The left-hand expression. /// @@ -59,6 +61,17 @@ public virtual PgUnknownBinaryExpression Update(SqlExpression left, SqlExpressio ? this : new PgUnknownBinaryExpression(left, right, Operator, Type, TypeMapping); + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(PgUnknownBinaryExpression).GetConstructor( + [typeof(SqlExpression), typeof(SqlExpression), typeof(string), typeof(Type), typeof(RelationalTypeMapping)])!, + Left.Quote(), + Right.Quote(), + Constant(Operator), + Constant(Type), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + /// public virtual bool Equals(PgUnknownBinaryExpression? other) => ReferenceEquals(this, other) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 3afb677bf..5eba3759a 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -1088,8 +1088,7 @@ void GenerateJsonPath(bool returnsText) s => s switch { { PropertyName: string propertyName } - => new SqlConstantExpression( - Expression.Constant(propertyName), _textTypeMapping ??= _typeMappingSource.FindMapping(typeof(string))), + => new SqlConstantExpression(propertyName, _textTypeMapping ??= _typeMappingSource.FindMapping(typeof(string))), { ArrayIndex: SqlExpression arrayIndex } => arrayIndex, _ => throw new UnreachableException() }).ToList()); diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs index af2cb57ea..0acc8a795 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs @@ -40,6 +40,15 @@ public override Expression Process(Expression query) return result; } + /// + /// 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 Expression ProcessTypeMappings(Expression expression) + => new NpgsqlTypeMappingPostprocessor(Dependencies, RelationalDependencies, RelationalQueryCompilationContext).Process(expression); + /// /// 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 diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index 84ec8668a..0c4f030cb 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -266,18 +266,6 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT .SelectMany(t => t.GetDeclaredNavigations()); } - /// - /// 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 Expression ApplyInferredTypeMappings( - Expression expression, - IReadOnlyDictionary<(string, string), RelationalTypeMapping?> inferredTypeMappings) - => new NpgsqlInferredTypeMappingApplier( - RelationalDependencies.Model, _typeMappingSource, _sqlExpressionFactory, inferredTypeMappings).Visit(expression); - /// /// 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 @@ -1310,62 +1298,4 @@ public bool ContainsReferenceToMainTable(TableExpressionBase tableExpression) return base.Visit(expression); } } - - /// - /// 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 class NpgsqlInferredTypeMappingApplier : RelationalInferredTypeMappingApplier - { - private readonly NpgsqlTypeMappingSource _typeMappingSource; - private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public NpgsqlInferredTypeMappingApplier( - IModel model, - NpgsqlTypeMappingSource typeMappingSource, - NpgsqlSqlExpressionFactory sqlExpressionFactory, - IReadOnlyDictionary<(string, string), RelationalTypeMapping?> inferredTypeMappings) - : base(model, sqlExpressionFactory, inferredTypeMappings) - { - _typeMappingSource = typeMappingSource; - _sqlExpressionFactory = sqlExpressionFactory; - } - - /// - /// 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 Expression VisitExtension(Expression expression) - { - switch (expression) - { - case PgUnnestExpression unnestExpression - when TryGetInferredTypeMapping(unnestExpression.Alias, unnestExpression.ColumnName, out var elementTypeMapping): - { - var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, Model, elementTypeMapping); - - if (collectionTypeMapping is null) - { - throw new InvalidOperationException(RelationalStrings.NullTypeMappingInSqlTree(expression.Print())); - } - - return unnestExpression.Update( - _sqlExpressionFactory.ApplyTypeMapping(unnestExpression.Array, collectionTypeMapping)); - } - - default: - return base.VisitExtension(expression); - } - } - } } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs index 056e73f0c..c309220f8 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs @@ -261,7 +261,7 @@ protected virtual SqlExpression VisitAny(PgAnyExpression anyExpression, bool all _sqlExpressionFactory.IsNotNull( _sqlExpressionFactory.Function( "array_position", - new[] { array, _sqlExpressionFactory.Constant(null, item.TypeMapping) }, + new[] { array, _sqlExpressionFactory.Constant(null, item.Type, item.TypeMapping) }, nullable: true, argumentsPropagateNullability: FalseArrays[2], typeof(int))))); diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index e6194f099..6234f7097 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -411,7 +411,9 @@ private bool TryTranslateStartsEndsWithContains( // simple LIKE translation = patternConstant.Value switch { - null => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant(null, stringTypeMapping)), + null => _sqlExpressionFactory.Like( + translatedInstance, + _sqlExpressionFactory.Constant(null, typeof(string), stringTypeMapping)), // In .NET, all strings start with/end with/contain the empty string, but SQL LIKE return false for empty patterns. // Return % which always matches instead. diff --git a/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs new file mode 100644 index 000000000..ba50c0577 --- /dev/null +++ b/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs @@ -0,0 +1,60 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class NpgsqlTypeMappingPostprocessor : RelationalTypeMappingPostprocessor +{ + private readonly IRelationalTypeMappingSource _typeMappingSource; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public NpgsqlTypeMappingPostprocessor( + QueryTranslationPostprocessorDependencies dependencies, + RelationalQueryTranslationPostprocessorDependencies relationalDependencies, + RelationalQueryCompilationContext queryCompilationContext) + : base(dependencies, relationalDependencies, queryCompilationContext) + { + _typeMappingSource = relationalDependencies.TypeMappingSource; + _sqlExpressionFactory = relationalDependencies.SqlExpressionFactory; + } + + /// + /// 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 Expression VisitExtension(Expression expression) + { + switch (expression) + { + case PgUnnestExpression unnestExpression + when TryGetInferredTypeMapping(unnestExpression.Alias, unnestExpression.ColumnName, out var elementTypeMapping): + { + var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, Model, elementTypeMapping); + + if (collectionTypeMapping is null) + { + throw new InvalidOperationException(RelationalStrings.NullTypeMappingInSqlTree(expression.Print())); + } + + return unnestExpression.Update( + _sqlExpressionFactory.ApplyTypeMapping(unnestExpression.Array, collectionTypeMapping)); + } + + default: + return base.VisitExtension(expression); + } + } +} diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 91d898672..368ddc238 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -540,7 +540,7 @@ bool TryGetRowValueValues(SqlExpression e, [NotNullWhen(true)] out IReadOnlyList for (var i = 0; i < v.Length; i++) { - v[i] = Constant(constantTuple[i]); + v[i] = Constant(constantTuple[i], typeof(object)); } values = v; @@ -651,7 +651,7 @@ private SqlExpression ApplyTypeMappingOnArrayIndex( // If a (non-null) type mapping is being applied, it's to the element being indexed. // Infer the array's mapping from that. var (_, array) = typeMapping is not null - ? ApplyTypeMappingsOnItemAndArray(Constant(null, typeMapping), pgArrayIndexExpression.Array) + ? ApplyTypeMappingsOnItemAndArray(Constant(null, typeMapping.ClrType, typeMapping), pgArrayIndexExpression.Array) : (null, ApplyDefaultTypeMapping(pgArrayIndexExpression.Array)); return new PgArrayIndexExpression( diff --git a/test/Directory.Build.props b/test/Directory.Build.props index e4c47baca..8648a51f4 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,7 +3,7 @@ - net8.0 + net9.0 false false diff --git a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj index d842ad664..421d004c8 100644 --- a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj +++ b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj @@ -17,6 +17,10 @@ + + + + diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 190860809..2277793c9 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -391,40 +391,35 @@ public override async Task Drop_table() { await base.Drop_table(); - AssertSql( - @"DROP TABLE ""People"";"); + AssertSql("""DROP TABLE "People";"""); } public override async Task Alter_table_add_comment() { await base.Alter_table_add_comment(); - AssertSql( - @"COMMENT ON TABLE ""People"" IS 'Table comment';"); + AssertSql("""COMMENT ON TABLE "People" IS 'Table comment';"""); } public override async Task Alter_table_add_comment_non_default_schema() { await base.Alter_table_add_comment_non_default_schema(); - AssertSql( - @"COMMENT ON TABLE ""SomeOtherSchema"".""People"" IS 'Table comment';"); + AssertSql("""COMMENT ON TABLE "SomeOtherSchema"."People" IS 'Table comment';"""); } public override async Task Alter_table_change_comment() { await base.Alter_table_change_comment(); - AssertSql( - @"COMMENT ON TABLE ""People"" IS 'Table comment2';"); + AssertSql("""COMMENT ON TABLE "People" IS 'Table comment2';"""); } public override async Task Alter_table_remove_comment() { await base.Alter_table_remove_comment(); - AssertSql( - @"COMMENT ON TABLE ""People"" IS NULL;"); + AssertSql("""COMMENT ON TABLE "People" IS NULL;"""); } [Fact] @@ -478,8 +473,7 @@ await Test( builder => builder.Entity("People").IsUnlogged(), asserter: null); // We don't scaffold unlogged - AssertSql( - @"ALTER TABLE ""People"" SET UNLOGGED;"); + AssertSql("""ALTER TABLE "People" SET UNLOGGED;"""); } [Fact] @@ -496,8 +490,7 @@ await Test( _ => { }, asserter: null); // We don't scaffold unlogged - AssertSql( - @"ALTER TABLE ""People"" SET LOGGED;"); + AssertSql("""ALTER TABLE "People" SET LOGGED;"""); } public override async Task Rename_table() @@ -505,11 +498,11 @@ public override async Task Rename_table() await base.Rename_table(); AssertSql( - @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";", + """ALTER TABLE "People" DROP CONSTRAINT "PK_People";""", // - @"ALTER TABLE ""People"" RENAME TO ""Persons"";", + """ALTER TABLE "People" RENAME TO "Persons";""", // - @"ALTER TABLE ""Persons"" ADD CONSTRAINT ""PK_Persons"" PRIMARY KEY (""Id"");"); + """ALTER TABLE "Persons" ADD CONSTRAINT "PK_Persons" PRIMARY KEY ("Id");"""); } public override async Task Rename_table_with_primary_key() @@ -517,11 +510,11 @@ public override async Task Rename_table_with_primary_key() await base.Rename_table_with_primary_key(); AssertSql( - @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";", + """ALTER TABLE "People" DROP CONSTRAINT "PK_People";""", // - @"ALTER TABLE ""People"" RENAME TO ""Persons"";", + """ALTER TABLE "People" RENAME TO "Persons";""", // - @"ALTER TABLE ""Persons"" ADD CONSTRAINT ""PK_Persons"" PRIMARY KEY (""Id"");"); + """ALTER TABLE "Persons" ADD CONSTRAINT "PK_Persons" PRIMARY KEY ("Id");"""); } public override async Task Move_table() @@ -594,8 +587,7 @@ public override async Task Add_column_with_defaultValue_string() { await base.Add_column_with_defaultValue_string(); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text NOT NULL DEFAULT 'John Doe';"); + AssertSql("""ALTER TABLE "People" ADD "Name" text NOT NULL DEFAULT 'John Doe';"""); } public override async Task Add_column_with_defaultValue_datetime() @@ -614,8 +606,7 @@ await Test( Assert.False(birthdayColumn.IsNullable); }); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Birthday"" timestamp with time zone NOT NULL DEFAULT TIMESTAMPTZ '2015-04-12T17:05:00Z';"); + AssertSql("""ALTER TABLE "People" ADD "Birthday" timestamp with time zone NOT NULL DEFAULT TIMESTAMPTZ '2015-04-12T17:05:00Z';"""); } [Fact] @@ -623,8 +614,7 @@ public override async Task Add_column_with_defaultValueSql() { await base.Add_column_with_defaultValueSql(); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Sum"" integer NOT NULL DEFAULT (1 + 2);"); + AssertSql("""ALTER TABLE "People" ADD "Sum" integer NOT NULL DEFAULT (1 + 2);"""); } public override async Task Add_column_with_computedSql(bool? stored) @@ -644,40 +634,35 @@ public override async Task Add_column_with_computedSql(bool? stored) await base.Add_column_with_computedSql(stored: true); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Sum"" text GENERATED ALWAYS AS (""X"" + ""Y"") STORED;"); + AssertSql("""ALTER TABLE "People" ADD "Sum" text GENERATED ALWAYS AS ("X" + "Y") STORED;"""); } public override async Task Add_column_with_required() { await base.Add_column_with_required(); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text NOT NULL DEFAULT '';"); + AssertSql("""ALTER TABLE "People" ADD "Name" text NOT NULL DEFAULT '';"""); } public override async Task Add_column_with_ansi() { await base.Add_column_with_ansi(); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text;"); + AssertSql("""ALTER TABLE "People" ADD "Name" text;"""); } public override async Task Add_column_with_max_length() { await base.Add_column_with_max_length(); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" character varying(30);"); + AssertSql("""ALTER TABLE "People" ADD "Name" character varying(30);"""); } public override async Task Add_column_with_unbounded_max_length() { await base.Add_column_with_unbounded_max_length(); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text;"); + AssertSql("""ALTER TABLE "People" ADD "Name" text;"""); } public override async Task Add_column_with_max_length_on_derived() @@ -691,7 +676,7 @@ public override async Task Add_column_with_fixed_length() { await base.Add_column_with_fixed_length(); - AssertSql(@"ALTER TABLE ""People"" ADD ""Name"" character(100);"); + AssertSql("""ALTER TABLE "People" ADD "Name" character(100);"""); } public override async Task Add_column_with_comment() @@ -709,8 +694,7 @@ public override async Task Add_column_with_collation() { await base.Add_column_with_collation(); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""POSIX"";"); + AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "POSIX";"""); } public override async Task Add_column_computed_with_collation(bool stored) @@ -730,8 +714,7 @@ public override async Task Add_column_computed_with_collation(bool stored) await base.Add_column_computed_with_collation(stored); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""POSIX"" GENERATED ALWAYS AS ('hello') STORED;"); + AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "POSIX" GENERATED ALWAYS AS ('hello') STORED;"""); } #pragma warning disable CS0618 @@ -754,8 +737,7 @@ await Test( Assert.Equal("POSIX", nameColumn.Collation); }); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""POSIX"";"); + AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "POSIX";"""); } [ConditionalFact] @@ -778,8 +760,7 @@ await Test( Assert.Equal("C", nameColumn.Collation); }); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text COLLATE ""C"";"); + AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "C";"""); } #pragma warning restore CS0618 @@ -834,8 +815,7 @@ await Test( Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]); }); - AssertSql( - @"ALTER TABLE ""People"" ADD ""SomeColumn"" integer GENERATED BY DEFAULT AS IDENTITY;"); + AssertSql("""ALTER TABLE "People" ADD "SomeColumn" integer GENERATED BY DEFAULT AS IDENTITY;"""); } [Fact] @@ -859,8 +839,7 @@ await Test( Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]); }); - AssertSql( - @"ALTER TABLE ""People"" ADD ""SomeColumn"" integer GENERATED ALWAYS AS IDENTITY;"); + AssertSql("""ALTER TABLE "People" ADD "SomeColumn" integer GENERATED ALWAYS AS IDENTITY;"""); } [Fact] @@ -898,7 +877,9 @@ await Test( }); AssertSql( - @"ALTER TABLE ""People"" ADD ""SomeColumn"" integer GENERATED BY DEFAULT AS IDENTITY (START WITH 5 INCREMENT BY 2 MINVALUE 3 MAXVALUE 2000 CYCLE CACHE 10);"); + """ +ALTER TABLE "People" ADD "SomeColumn" integer GENERATED BY DEFAULT AS IDENTITY (START WITH 5 INCREMENT BY 2 MINVALUE 3 MAXVALUE 2000 CYCLE CACHE 10); +"""); } [Fact] @@ -935,8 +916,7 @@ await Test( Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]); }); - AssertSql( - @"ALTER TABLE ""People"" ADD ""SomeColumn"" serial NOT NULL;"); + AssertSql("""ALTER TABLE "People" ADD "SomeColumn" serial NOT NULL;"""); } [Fact] @@ -960,8 +940,7 @@ await Test( Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]); }); - AssertSql( - @"ALTER TABLE ""People"" ADD ""SomeColumn"" integer GENERATED BY DEFAULT AS IDENTITY;"); + AssertSql("""ALTER TABLE "People" ADD "SomeColumn" integer GENERATED BY DEFAULT AS IDENTITY;"""); } [Fact] @@ -1008,8 +987,7 @@ await Test( Assert.Equal("text", column.StoreType); }); - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" text;"); + AssertSql("""ALTER TABLE "People" ADD "Name" text;"""); } [Fact] @@ -1031,20 +1009,14 @@ await Test( Assert.Equal("pglz", column[NpgsqlAnnotationNames.CompressionMethod]); }); - AssertSql( - """ -ALTER TABLE "Blogs" ADD "Title" text COMPRESSION pglz; -"""); + AssertSql("""ALTER TABLE "Blogs" ADD "Title" text COMPRESSION pglz;"""); } public override async Task Alter_column_change_type() { await base.Alter_column_change_type(); - AssertSql( - """ -ALTER TABLE "People" ALTER COLUMN "SomeColumn" TYPE bigint; -"""); + AssertSql("""ALTER TABLE "People" ALTER COLUMN "SomeColumn" TYPE bigint;"""); } public override async Task Alter_column_make_required() @@ -1113,9 +1085,9 @@ public override async Task Alter_column_make_computed(bool? stored) await base.Alter_column_make_computed(stored); AssertSql( - @"ALTER TABLE ""People"" DROP COLUMN ""Sum"";", + """ALTER TABLE "People" DROP COLUMN "Sum";""", // - @"ALTER TABLE ""People"" ADD ""Sum"" integer GENERATED ALWAYS AS (""X"" + ""Y"") STORED NOT NULL;"); + """ALTER TABLE "People" ADD "Sum" integer GENERATED ALWAYS AS ("X" + "Y") STORED NOT NULL;"""); } public override async Task Alter_column_change_computed() @@ -1150,9 +1122,9 @@ await Test( }); AssertSql( - @"ALTER TABLE ""People"" DROP COLUMN ""Sum"";", + """ALTER TABLE "People" DROP COLUMN "Sum";""", // - @"ALTER TABLE ""People"" ADD ""Sum"" integer GENERATED ALWAYS AS (""X"" - ""Y"") STORED NOT NULL;"); + """ALTER TABLE "People" ADD "Sum" integer GENERATED ALWAYS AS ("X" - "Y") STORED NOT NULL;"""); } public override async Task Alter_column_change_computed_recreates_indexes() @@ -1195,11 +1167,11 @@ await Test( }); AssertSql( - @"ALTER TABLE ""People"" DROP COLUMN ""Sum"";", + """ALTER TABLE "People" DROP COLUMN "Sum";""", // - @"ALTER TABLE ""People"" ADD ""Sum"" integer GENERATED ALWAYS AS (""X"" - ""Y"") STORED NOT NULL;", + """ALTER TABLE "People" ADD "Sum" integer GENERATED ALWAYS AS ("X" - "Y") STORED NOT NULL;""", // - @"CREATE INDEX ""IX_People_Sum"" ON ""People"" (""Sum"");"); + """CREATE INDEX "IX_People_Sum" ON "People" ("Sum");"""); } public override Task Alter_column_change_computed_type() @@ -1233,17 +1205,16 @@ await Test( }); AssertSql( - @"ALTER TABLE ""People"" DROP COLUMN ""Sum"";", + """ALTER TABLE "People" DROP COLUMN "Sum";""", // - @"ALTER TABLE ""People"" ADD ""Sum"" integer NOT NULL;"); + """ALTER TABLE "People" ADD "Sum" integer NOT NULL;"""); } public override async Task Alter_column_add_comment() { await base.Alter_column_add_comment(); - AssertSql( - @"COMMENT ON COLUMN ""People"".""Id"" IS 'Some comment';"); + AssertSql("""COMMENT ON COLUMN "People"."Id" IS 'Some comment';"""); } public override async Task Alter_computed_column_add_comment() @@ -1273,16 +1244,14 @@ await Test( } }); - AssertSql( - @"COMMENT ON COLUMN ""People"".""SomeColumn"" IS 'Some comment';"); + AssertSql("""COMMENT ON COLUMN "People"."SomeColumn" IS 'Some comment';"""); } public override async Task Alter_column_change_comment() { await base.Alter_column_change_comment(); - AssertSql( - @"COMMENT ON COLUMN ""People"".""Id"" IS 'Some comment2';"); + AssertSql("""COMMENT ON COLUMN "People"."Id" IS 'Some comment2';"""); } public override async Task Alter_column_remove_comment() @@ -1290,7 +1259,7 @@ public override async Task Alter_column_remove_comment() await base.Alter_column_remove_comment(); AssertSql( - @"COMMENT ON COLUMN ""People"".""Id"" IS NULL;"); + """COMMENT ON COLUMN "People"."Id" IS NULL;"""); } [Fact] @@ -1337,8 +1306,7 @@ await Test( Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]); }); - AssertSql( - @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" SET GENERATED ALWAYS;"); + AssertSql("""ALTER TABLE "People" ALTER COLUMN "Id" SET GENERATED ALWAYS;"""); } [Fact] @@ -1610,7 +1578,7 @@ await Test( }); AssertSql( - @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" SET GENERATED ALWAYS;"); + """ALTER TABLE "People" ALTER COLUMN "Id" SET GENERATED ALWAYS;"""); } [Fact] @@ -1668,8 +1636,7 @@ await Test( Assert.Null(column[NpgsqlAnnotationNames.IdentityOptions]); }); - AssertSql( - @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" TYPE bigint;"); + AssertSql("""ALTER TABLE "People" ALTER COLUMN "Id" TYPE bigint;"""); } [Fact] @@ -1693,8 +1660,7 @@ await Test( Assert.Equal(10, options.StartValue); // Restarting doesn't change the scaffolded start value }); - AssertSql( - @"ALTER TABLE ""People"" ALTER COLUMN ""Id"" RESTART WITH 20;"); + AssertSql("""ALTER TABLE "People" ALTER COLUMN "Id" RESTART WITH 20;"""); } [Fact] @@ -1702,8 +1668,7 @@ public override async Task Alter_column_set_collation() { await base.Alter_column_set_collation(); - AssertSql( - @"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""POSIX"";"); + AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name" TYPE text COLLATE "POSIX";"""); } [Fact] @@ -1711,8 +1676,7 @@ public override async Task Alter_column_reset_collation() { await base.Alter_column_reset_collation(); - AssertSql( - @"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""default"";"); + AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name" TYPE text COLLATE "default";"""); } public override async Task Convert_string_column_to_a_json_column_containing_reference() @@ -1759,8 +1723,7 @@ await Test( // Assert.Equal("C", nameColumn.Collation); }); - AssertSql( - @"ALTER TABLE ""People"" ALTER COLUMN ""Name"" TYPE text COLLATE ""C"";"); + AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name" TYPE text COLLATE "C";"""); } #pragma warning restore CS0618 @@ -1789,8 +1752,7 @@ await Test( Assert.Equal(NonDefaultCollation, computedColumn.Collation); }); - AssertSql( - @"ALTER TABLE ""People"" ALTER COLUMN ""Name2"" TYPE text COLLATE ""POSIX"";"); + AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name2" TYPE text COLLATE "POSIX";"""); } [Fact] @@ -1812,8 +1774,7 @@ await Test( Assert.Equal("pglz", column[NpgsqlAnnotationNames.CompressionMethod]); }); - AssertSql( - @"ALTER TABLE ""Blogs"" ALTER COLUMN ""Title"" SET COMPRESSION pglz"); + AssertSql("""ALTER TABLE "Blogs" ALTER COLUMN "Title" SET COMPRESSION pglz"""); } [Fact] @@ -1835,16 +1796,14 @@ await Test( Assert.Null(column[NpgsqlAnnotationNames.CompressionMethod]); }); - AssertSql( - @"ALTER TABLE ""Blogs"" ALTER COLUMN ""Title"" SET COMPRESSION default"); + AssertSql("""ALTER TABLE "Blogs" ALTER COLUMN "Title" SET COMPRESSION default"""); } public override async Task Drop_column() { await base.Drop_column(); - AssertSql( - @"ALTER TABLE ""People"" DROP COLUMN ""SomeColumn"";"); + AssertSql("""ALTER TABLE "People" DROP COLUMN "SomeColumn";"""); } public override async Task Drop_column_primary_key() @@ -1852,9 +1811,9 @@ public override async Task Drop_column_primary_key() await base.Drop_column_primary_key(); AssertSql( - @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";", + """ALTER TABLE "People" DROP CONSTRAINT "PK_People";""", // - @"ALTER TABLE ""People"" DROP COLUMN ""Id"";"); + """ALTER TABLE "People" DROP COLUMN "Id";"""); } public override async Task Drop_column_computed_and_non_computed_with_dependency() @@ -1880,9 +1839,9 @@ await Test( }); AssertSql( - @"ALTER TABLE ""People"" DROP COLUMN ""Y"";", + """ALTER TABLE "People" DROP COLUMN "Y";""", // - @"ALTER TABLE ""People"" DROP COLUMN ""X"";"); + """ALTER TABLE "People" DROP COLUMN "X";"""); } [Fact] @@ -1918,8 +1877,7 @@ public override async Task Rename_column() { await base.Rename_column(); - AssertSql( - @"ALTER TABLE ""People"" RENAME COLUMN ""SomeColumn"" TO ""SomeOtherColumn"";"); + AssertSql("""ALTER TABLE "People" RENAME COLUMN "SomeColumn" TO "SomeOtherColumn";"""); } #endregion @@ -1930,24 +1888,21 @@ public override async Task Create_index_unique() { await base.Create_index_unique(); - AssertSql( - @"CREATE UNIQUE INDEX ""IX_People_FirstName_LastName"" ON ""People"" (""FirstName"", ""LastName"");"); + AssertSql("""CREATE UNIQUE INDEX "IX_People_FirstName_LastName" ON "People" ("FirstName", "LastName");"""); } public override async Task Create_index_descending() { await base.Create_index_descending(); - AssertSql( - @"CREATE INDEX ""IX_People_X"" ON ""People"" (""X"" DESC);"); + AssertSql("""CREATE INDEX "IX_People_X" ON "People" ("X" DESC);"""); } public override async Task Create_index_descending_mixed() { await base.Create_index_descending_mixed(); - AssertSql( - @"CREATE INDEX ""IX_People_X_Y_Z"" ON ""People"" (""X"", ""Y"" DESC, ""Z"");"); + AssertSql("""CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");"""); } #pragma warning disable CS0618 // HasSortOrder is obsolete @@ -1974,8 +1929,7 @@ await Test( Assert.Collection(index.IsDescending, Assert.False, Assert.True, Assert.False); }); - AssertSql( - @"CREATE INDEX ""IX_People_X_Y_Z"" ON ""People"" (""X"", ""Y"" DESC, ""Z"");"); + AssertSql("""CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");"""); } #pragma warning restore CS0618 @@ -2002,24 +1956,21 @@ await Test( Assert.Collection(index.IsDescending, Assert.False, Assert.True, Assert.False); }); - AssertSql( - @"CREATE INDEX ""IX_People_X_Y_Z"" ON ""People"" (""X"", ""Y"" DESC, ""Z"");"); + AssertSql("""CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");"""); } public override async Task Create_index_with_filter() { await base.Create_index_with_filter(); - AssertSql( - @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE ""Name"" IS NOT NULL;"); + AssertSql("""CREATE INDEX "IX_People_Name" ON "People" ("Name") WHERE "Name" IS NOT NULL;"""); } public override async Task Create_unique_index_with_filter() { await base.Create_unique_index_with_filter(); - AssertSql( - @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE ""Name"" IS NOT NULL AND ""Name"" <> '';"); + AssertSql("""CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name") WHERE "Name" IS NOT NULL AND "Name" <> '';"""); } [Fact] @@ -2061,8 +2012,8 @@ await Test( AssertSql( TestEnvironment.PostgresVersion.AtLeast(11) - ? @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") INCLUDE (""FirstName"", last_name);" - : @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"");"); + ? """CREATE INDEX "IX_People_Name" ON "People" ("Name") INCLUDE ("FirstName", last_name);""" + : """CREATE INDEX "IX_People_Name" ON "People" ("Name");"""); } [Fact] @@ -2106,8 +2057,8 @@ await Test( AssertSql( TestEnvironment.PostgresVersion.AtLeast(11) - ? @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") INCLUDE (""FirstName"", ""LastName"") WHERE ""Name"" IS NOT NULL;" - : @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE ""Name"" IS NOT NULL;"); + ? """CREATE INDEX "IX_People_Name" ON "People" ("Name") INCLUDE ("FirstName", "LastName") WHERE "Name" IS NOT NULL;""" + : """CREATE INDEX "IX_People_Name" ON "People" ("Name") WHERE "Name" IS NOT NULL;"""); } [Fact] @@ -2151,8 +2102,8 @@ await Test( AssertSql( TestEnvironment.PostgresVersion.AtLeast(11) - ? @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"") INCLUDE (""FirstName"", ""LastName"");" - : @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"");"); + ? """CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name") INCLUDE ("FirstName", "LastName");""" + : """CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name");"""); } [Fact] @@ -2198,8 +2149,8 @@ await Test( AssertSql( TestEnvironment.PostgresVersion.AtLeast(11) - ? @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"") INCLUDE (""FirstName"", ""LastName"") WHERE ""Name"" IS NOT NULL;" - : @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE ""Name"" IS NOT NULL;"); + ? """CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name") INCLUDE ("FirstName", "LastName") WHERE "Name" IS NOT NULL;""" + : """CREATE UNIQUE INDEX "IX_People_Name" ON "People" ("Name") WHERE "Name" IS NOT NULL;"""); } [Fact] @@ -2217,8 +2168,7 @@ await Test( .IsCreatedConcurrently(), asserter: null); // No scaffolding for IsCreatedConcurrently - AssertSql( - @"CREATE INDEX CONCURRENTLY ""IX_People_Age"" ON ""People"" (""Age"");"); + AssertSql("""CREATE INDEX CONCURRENTLY "IX_People_Age" ON "People" ("Age");"""); } [Fact] @@ -2241,8 +2191,7 @@ await Test( Assert.Equal("hash", index[NpgsqlAnnotationNames.IndexMethod]); }); - AssertSql( - @"CREATE INDEX ""IX_People_Age"" ON ""People"" USING hash (""Age"");"); + AssertSql("""CREATE INDEX "IX_People_Age" ON "People" USING hash ("Age");"""); } [Fact] @@ -2266,8 +2215,7 @@ await Test( Assert.Equal(new[] { "text_pattern_ops", null }, index[NpgsqlAnnotationNames.IndexOperators]); }); - AssertSql( - @"CREATE INDEX ""IX_People_FirstName_LastName"" ON ""People"" (""FirstName"" text_pattern_ops, ""LastName"");"); + AssertSql("""CREATE INDEX "IX_People_FirstName_LastName" ON "People" ("FirstName" text_pattern_ops, "LastName");"""); } [Fact] @@ -2288,10 +2236,7 @@ await Test( Assert.Equal("some_collation", Assert.Single((IReadOnlyList)index[RelationalAnnotationNames.Collation]!)); }); - AssertSql( - """ -CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation); -"""); + AssertSql("""CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation);"""); } [Fact] // #3027 @@ -2315,10 +2260,7 @@ await Test( Assert.Equal("some_collation", Assert.Single((IReadOnlyList)index[RelationalAnnotationNames.Collation]!)); }); - AssertSql( - """ -CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation text_pattern_ops); -"""); + AssertSql("""CREATE INDEX "IX_People_Name" ON "People" ("Name" COLLATE some_collation text_pattern_ops);"""); } [Fact] @@ -2346,7 +2288,7 @@ await Test( }); AssertSql( - @"CREATE INDEX ""IX_People_FirstName_MiddleName_LastName"" ON ""People"" (""FirstName"" NULLS FIRST, ""MiddleName"", ""LastName"" NULLS LAST);"); + """CREATE INDEX "IX_People_FirstName_MiddleName_LastName" ON "People" ("FirstName" NULLS FIRST, "MiddleName", "LastName" NULLS LAST);"""); } [Fact] @@ -2366,7 +2308,7 @@ await Test( _ => { }); AssertSql( - @"CREATE INDEX ""IX_Blogs_Title_Description"" ON ""Blogs"" (to_tsvector('simple', ""Title"" || ' ' || coalesce(""Description"", '')));"); + """CREATE INDEX "IX_Blogs_Title_Description" ON "Blogs" (to_tsvector('simple', "Title" || ' ' || coalesce("Description", '')));"""); } [Fact] @@ -2387,7 +2329,7 @@ await Test( _ => { }); AssertSql( - @"CREATE INDEX ""IX_Blogs_Title_Description"" ON ""Blogs"" USING GIN (to_tsvector('simple', ""Title"" || ' ' || coalesce(""Description"", '')));"); + """CREATE INDEX "IX_Blogs_Title_Description" ON "Blogs" USING GIN (to_tsvector('simple', "Title" || ' ' || coalesce("Description", '')));"""); } [ConditionalFact] @@ -2425,13 +2367,9 @@ await Test( }); AssertSql( - """ -CREATE UNIQUE INDEX "IX_NullsDistinct" ON "People" ("Age"); -""", + """CREATE UNIQUE INDEX "IX_NullsDistinct" ON "People" ("Age");""", // - """ -CREATE UNIQUE INDEX "IX_NullsNotDistinct" ON "People" ("Age") NULLS NOT DISTINCT; -"""); + """CREATE UNIQUE INDEX "IX_NullsNotDistinct" ON "People" ("Age") NULLS NOT DISTINCT;"""); } [Fact] @@ -2460,8 +2398,7 @@ await Test( Assert.Equal("70", storageParameter.Value); }); - AssertSql( - @"CREATE INDEX ""IX_People_Age"" ON ""People"" (""Age"") WITH (fillfactor=70);"); + AssertSql("""CREATE INDEX "IX_People_Age" ON "People" ("Age") WITH (fillfactor=70);"""); } public override async Task Alter_index_change_sort_order() @@ -2469,25 +2406,23 @@ public override async Task Alter_index_change_sort_order() await base.Alter_index_change_sort_order(); AssertSql( - @"DROP INDEX ""IX_People_X_Y_Z"";", + """DROP INDEX "IX_People_X_Y_Z";""", // - @"CREATE INDEX ""IX_People_X_Y_Z"" ON ""People"" (""X"", ""Y"" DESC, ""Z"");"); + """CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");"""); } public override async Task Drop_index() { await base.Drop_index(); - AssertSql( - @"DROP INDEX ""IX_People_SomeField"";"); + AssertSql("""DROP INDEX "IX_People_SomeField";"""); } public override async Task Rename_index() { await base.Rename_index(); - AssertSql( - @"ALTER INDEX ""Foo"" RENAME TO foo;"); + AssertSql("""ALTER INDEX "Foo" RENAME TO foo;"""); } #endregion @@ -2504,14 +2439,16 @@ public override async Task Add_primary_key_int() ALTER TABLE "People" ALTER COLUMN "SomeField" ADD GENERATED BY DEFAULT AS IDENTITY; """, // - @"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_People"" PRIMARY KEY (""SomeField"");"); + """ +ALTER TABLE "People" ADD CONSTRAINT "PK_People" PRIMARY KEY ("SomeField"); +"""); } public override async Task Add_primary_key_string() { await base.Add_primary_key_string(); - AssertSql(@"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_People"" PRIMARY KEY (""SomeField"");"); + AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "PK_People" PRIMARY KEY ("SomeField");"""); } public override async Task Add_primary_key_with_name() @@ -2525,15 +2462,16 @@ public override async Task Add_primary_key_with_name() ALTER TABLE "People" ALTER COLUMN "SomeField" SET DEFAULT ''; """, // - @"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_Foo"" PRIMARY KEY (""SomeField"");"); + """ +ALTER TABLE "People" ADD CONSTRAINT "PK_Foo" PRIMARY KEY ("SomeField"); +"""); } public override async Task Add_primary_key_composite_with_name() { await base.Add_primary_key_composite_with_name(); - AssertSql( - @"ALTER TABLE ""People"" ADD CONSTRAINT ""PK_Foo"" PRIMARY KEY (""SomeField1"", ""SomeField2"");"); + AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "PK_Foo" PRIMARY KEY ("SomeField1", "SomeField2");"""); } public override async Task Drop_primary_key_int() @@ -2541,17 +2479,16 @@ public override async Task Drop_primary_key_int() await base.Drop_primary_key_int(); AssertSql( - @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";", + """ALTER TABLE "People" DROP CONSTRAINT "PK_People";""", // - @"ALTER TABLE ""People"" ALTER COLUMN ""SomeField"" DROP IDENTITY;"); + """ALTER TABLE "People" ALTER COLUMN "SomeField" DROP IDENTITY;"""); } public override async Task Drop_primary_key_string() { await base.Drop_primary_key_string(); - AssertSql( - @"ALTER TABLE ""People"" DROP CONSTRAINT ""PK_People"";"); + AssertSql("""ALTER TABLE "People" DROP CONSTRAINT "PK_People";"""); } public override Task Add_foreign_key() @@ -2562,9 +2499,9 @@ public override async Task Add_foreign_key_with_name() await base.Add_foreign_key_with_name(); AssertSql( - @"CREATE INDEX ""IX_Orders_CustomerId"" ON ""Orders"" (""CustomerId"");", + """CREATE INDEX "IX_Orders_CustomerId" ON "Orders" ("CustomerId");""", // - @"ALTER TABLE ""Orders"" ADD CONSTRAINT ""FK_Foo"" FOREIGN KEY (""CustomerId"") REFERENCES ""Customers"" (""Id"") ON DELETE CASCADE;"); + """ALTER TABLE "Orders" ADD CONSTRAINT "FK_Foo" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") ON DELETE CASCADE;"""); } public override async Task Drop_foreign_key() @@ -2572,49 +2509,44 @@ public override async Task Drop_foreign_key() await base.Drop_foreign_key(); AssertSql( - @"ALTER TABLE ""Orders"" DROP CONSTRAINT ""FK_Orders_Customers_CustomerId"";", + """ALTER TABLE "Orders" DROP CONSTRAINT "FK_Orders_Customers_CustomerId";""", // - @"DROP INDEX ""IX_Orders_CustomerId"";"); + """DROP INDEX "IX_Orders_CustomerId";"""); } public override async Task Add_unique_constraint() { await base.Add_unique_constraint(); - AssertSql( - @"ALTER TABLE ""People"" ADD CONSTRAINT ""AK_People_AlternateKeyColumn"" UNIQUE (""AlternateKeyColumn"");"); + AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "AK_People_AlternateKeyColumn" UNIQUE ("AlternateKeyColumn");"""); } public override async Task Add_unique_constraint_composite_with_name() { await base.Add_unique_constraint_composite_with_name(); - AssertSql( - @"ALTER TABLE ""People"" ADD CONSTRAINT ""AK_Foo"" UNIQUE (""AlternateKeyColumn1"", ""AlternateKeyColumn2"");"); + AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "AK_Foo" UNIQUE ("AlternateKeyColumn1", "AlternateKeyColumn2");"""); } public override async Task Drop_unique_constraint() { await base.Drop_unique_constraint(); - AssertSql( - @"ALTER TABLE ""People"" DROP CONSTRAINT ""AK_People_AlternateKeyColumn"";"); + AssertSql("""ALTER TABLE "People" DROP CONSTRAINT "AK_People_AlternateKeyColumn";"""); } public override async Task Add_check_constraint_with_name() { await base.Add_check_constraint_with_name(); - AssertSql( - @"ALTER TABLE ""People"" ADD CONSTRAINT ""CK_People_Foo"" CHECK (""DriverLicense"" > 0);"); + AssertSql("""ALTER TABLE "People" ADD CONSTRAINT "CK_People_Foo" CHECK ("DriverLicense" > 0);"""); } public override async Task Drop_check_constraint() { await base.Drop_check_constraint(); - AssertSql( - @"ALTER TABLE ""People"" DROP CONSTRAINT ""CK_People_Foo"";"); + AssertSql("""ALTER TABLE "People" DROP CONSTRAINT "CK_People_Foo";"""); } #endregion @@ -2625,10 +2557,7 @@ public override async Task Create_sequence() { await base.Create_sequence(); - AssertSql( - """ -CREATE SEQUENCE "TestSequence" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE; -"""); + AssertSql("""CREATE SEQUENCE "TestSequence" AS integer START WITH 1 INCREMENT BY 1 NO CYCLE;"""); } public override async Task Create_sequence_all_settings() @@ -2645,7 +2574,9 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'dbo2') THEN END $EF$; """, // - @"CREATE SEQUENCE dbo2.""TestSequence"" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE CACHE 20;"); + """ +CREATE SEQUENCE dbo2."TestSequence" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE CACHE 20; +"""); } public override async Task Create_sequence_nocache() @@ -2693,10 +2624,7 @@ await Test( Assert.Equal("smallint", sequence.StoreType); }); - AssertSql( - """ -CREATE SEQUENCE "TestSequence" AS smallint START WITH 1 INCREMENT BY 1 NO CYCLE; -"""); + AssertSql("""CREATE SEQUENCE "TestSequence" AS smallint START WITH 1 INCREMENT BY 1 NO CYCLE;"""); } [Fact] @@ -2719,32 +2647,28 @@ public override async Task Alter_sequence_increment_by() { await base.Alter_sequence_increment_by(); - AssertSql( - @"ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"); + AssertSql("ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"); } public override async Task Alter_sequence_default_cache_to_cache() { await base.Alter_sequence_default_cache_to_cache(); - AssertSql( - """ALTER SEQUENCE "Delta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); + AssertSql("""ALTER SEQUENCE "Delta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); } public override async Task Alter_sequence_default_cache_to_nocache() { await base.Alter_sequence_default_cache_to_nocache(); - AssertSql( - """ALTER SEQUENCE "Epsilon" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + AssertSql("""ALTER SEQUENCE "Epsilon" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); } public override async Task Alter_sequence_cache_to_nocache() { await base.Alter_sequence_cache_to_nocache(); - AssertSql( - """ALTER SEQUENCE "Zeta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + AssertSql("""ALTER SEQUENCE "Zeta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); } public override async Task Alter_sequence_cache_to_default_cache() @@ -2762,18 +2686,14 @@ await Test( Assert.Null(sequence.CacheSize); }); - AssertSql( - """ALTER SEQUENCE "Eta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + AssertSql("""ALTER SEQUENCE "Eta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); } public override async Task Alter_sequence_nocache_to_cache() { await base.Alter_sequence_nocache_to_cache(); - AssertSql( - """ -ALTER SEQUENCE "Theta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20; -"""); + AssertSql("""ALTER SEQUENCE "Theta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); } public override async Task Alter_sequence_nocache_to_default_cache() @@ -2791,8 +2711,7 @@ await Test( Assert.Null(sequence.CacheSize); }); - AssertSql( - """ALTER SEQUENCE "Iota" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + AssertSql("""ALTER SEQUENCE "Iota" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); } public override async Task Alter_sequence_restart_with() @@ -2810,16 +2729,14 @@ public override async Task Drop_sequence() { await base.Drop_sequence(); - AssertSql( - @"DROP SEQUENCE ""TestSequence"";"); + AssertSql("""DROP SEQUENCE "TestSequence";"""); } public override async Task Rename_sequence() { await base.Rename_sequence(); - AssertSql( - @"ALTER SEQUENCE ""TestSequence"" RENAME TO testsequence;"); + AssertSql("""ALTER SEQUENCE "TestSequence" RENAME TO testsequence;"""); } public override async Task Move_sequence() @@ -2836,7 +2753,9 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'TestSequenceSchema') T END $EF$; """, // - @"ALTER SEQUENCE ""TestSequence"" SET SCHEMA ""TestSequenceSchema"";"); + """ +ALTER SEQUENCE "TestSequence" SET SCHEMA "TestSequenceSchema"; +"""); } #endregion @@ -2993,7 +2912,6 @@ SELECT setval( #endregion Data seeding - [ConditionalFact] public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table() { await base.Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core("ARRAY[3, 2, 1]"); @@ -3016,8 +2934,7 @@ await Test( Assert.Equal("public", citext.Schema); }); - AssertSql( - @"CREATE EXTENSION IF NOT EXISTS citext;"); + AssertSql("CREATE EXTENSION IF NOT EXISTS citext;"); } [Fact] @@ -3065,8 +2982,7 @@ await Test( l => Assert.Equal("Sad", l)); }); - AssertSql( - @"CREATE TYPE ""Mood"" AS ENUM ('Happy', 'Sad');"); + AssertSql("""CREATE TYPE "Mood" AS ENUM ('Happy', 'Sad');"""); } [Fact] @@ -3096,7 +3012,9 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'some_schema') THEN END $EF$; """, // - @"CREATE TYPE some_schema.""Mood"" AS ENUM ('Happy', 'Sad');"); + """ +CREATE TYPE some_schema."Mood" AS ENUM ('Happy', 'Sad'); +"""); } [Fact] @@ -3107,8 +3025,7 @@ await Test( _ => { }, model => Assert.Empty(model.GetPostgresEnums())); - AssertSql( - @"DROP TYPE ""Mood"";"); + AssertSql("""DROP TYPE "Mood";"""); } [Fact] // #979 @@ -3120,8 +3037,7 @@ await Test( builder => builder.HasPostgresEnum("Enum2", ["X", "Y"]), model => Assert.Equal(2, model.GetPostgresEnums().Count())); - AssertSql( - @"CREATE TYPE ""Enum2"" AS ENUM ('X', 'Y');"); + AssertSql("""CREATE TYPE "Enum2" AS ENUM ('X', 'Y');"""); } [Fact] @@ -3140,8 +3056,7 @@ await Test( l => Assert.Equal("Angry", l)); }); - AssertSql( - @"ALTER TYPE ""Mood"" ADD VALUE 'Angry';"); + AssertSql("""ALTER TYPE "Mood" ADD VALUE 'Angry';"""); } [Fact] @@ -3160,8 +3075,7 @@ await Test( l => Assert.Equal("Sad", l)); }); - AssertSql( - @"ALTER TYPE ""Mood"" ADD VALUE 'Angry' BEFORE 'Sad';"); + AssertSql("""ALTER TYPE "Mood" ADD VALUE 'Angry' BEFORE 'Sad';"""); } [Fact] @@ -3240,8 +3154,7 @@ await Test( _ => { }, model => Assert.Empty(PostgresCollation.GetCollations(model))); - AssertSql( - @"DROP COLLATION dummy;"); + AssertSql("""DROP COLLATION dummy;"""); } [Fact] @@ -3273,8 +3186,7 @@ await Test( Assert.Equal("tsvector", column.StoreType); }); - AssertSql( - @"ALTER TABLE ""Blogs"" ADD ""SearchColumn"" tsvector GENERATED ALWAYS AS (to_tsvector('english', ""TextColumn"")) STORED;"); + AssertSql("""ALTER TABLE "Blogs" ADD "SearchColumn" tsvector GENERATED ALWAYS AS (to_tsvector('english', "TextColumn")) STORED;"""); } [Fact] @@ -3298,7 +3210,7 @@ await Test( }); AssertSql( - @"ALTER TABLE ""People"" ADD ""SearchColumn"" tsvector GENERATED ALWAYS AS (jsonb_to_tsvector('english', ""JsonbColumn"", '""all""')) STORED;"); + """ALTER TABLE "People" ADD "SearchColumn" tsvector GENERATED ALWAYS AS (jsonb_to_tsvector('english', "JsonbColumn", '"all"')) STORED;"""); } [Fact] @@ -3329,7 +3241,7 @@ await Test( }); AssertSql( - @"ALTER TABLE ""People"" ADD ""SearchColumn"" tsvector GENERATED ALWAYS AS (to_tsvector('english', ""RequiredTextColumn"" || ' ' || coalesce(""OptionalTextColumn"", '')) || jsonb_to_tsvector('english', ""RequiredJsonbColumn"", '""all""') || json_to_tsvector('english', coalesce(""OptionalJsonColumn"", '{}'), '""all""')) STORED;"); + """ALTER TABLE "People" ADD "SearchColumn" tsvector GENERATED ALWAYS AS (to_tsvector('english', "RequiredTextColumn" || ' ' || coalesce("OptionalTextColumn", '')) || jsonb_to_tsvector('english', "RequiredJsonbColumn", '"all"') || json_to_tsvector('english', coalesce("OptionalJsonColumn", '{}'), '"all"')) STORED;"""); } [Fact] @@ -3362,13 +3274,138 @@ await Test( }); AssertSql( - @"ALTER TABLE ""Blogs"" DROP COLUMN ""TsVector"";", + """ALTER TABLE "Blogs" DROP COLUMN "TsVector";""", // - @"ALTER TABLE ""Blogs"" ADD ""TsVector"" tsvector GENERATED ALWAYS AS (to_tsvector('english', ""Title"" || ' ' || coalesce(""Description"", ''))) STORED;"); + """ALTER TABLE "Blogs" ADD "TsVector" tsvector GENERATED ALWAYS AS (to_tsvector('english', "Title" || ' ' || coalesce("Description", ''))) STORED;"""); } #endregion PostgreSQL full-text search + public override async Task Add_required_primitive_collection_to_existing_table() + { + await base.Add_required_primitive_collection_to_existing_table(); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL;"""); + } + + public override async Task Add_required_primitive_collection_with_custom_default_value_to_existing_table() + { + await base.Add_required_primitive_collection_with_custom_default_value_to_existing_table(); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL DEFAULT ARRAY[1,2,3]::integer[];"""); + } + + public override async Task Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table() + { + await base.Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table_core("ARRAY[1,2,3]"); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL DEFAULT (ARRAY[1,2,3]);"""); + } + + [ConditionalFact(Skip = "issue #33038")] + public override async Task Add_required_primitive_collection_with_custom_converter_to_existing_table() + { + await base.Add_required_primitive_collection_with_custom_converter_to_existing_table(); + + AssertSql( + """ +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing'; +"""); + } + + public override async Task Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table() + { + await base.Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table(); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" text NOT NULL DEFAULT 'some numbers';"""); + } + + public override async Task Add_optional_primitive_collection_to_existing_table() + { + await base.Add_optional_primitive_collection_to_existing_table(); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[];"""); + } + + public override async Task Create_table_with_required_primitive_collection() + { + await base.Create_table_with_required_primitive_collection(); + + AssertSql( + """ +CREATE TABLE "Customers" ( + "Id" integer GENERATED BY DEFAULT AS IDENTITY, + "Name" text, + "Numbers" integer[] NOT NULL, + CONSTRAINT "PK_Customers" PRIMARY KEY ("Id") +); +"""); + } + + public override async Task Create_table_with_optional_primitive_collection() + { + await base.Create_table_with_optional_primitive_collection(); + + AssertSql( + """ +CREATE TABLE "Customers" ( + "Id" integer GENERATED BY DEFAULT AS IDENTITY, + "Name" text, + "Numbers" integer[], + CONSTRAINT "PK_Customers" PRIMARY KEY ("Id") +); +"""); + } + + public override async Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH() + { + await base.Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH(); + + AssertSql( + """ +CREATE TABLE "Contacts" ( + "Id" integer GENERATED BY DEFAULT AS IDENTITY, + "Discriminator" character varying(8) NOT NULL, + "Name" text, + "Number" integer, + "MyComplex_Prop" text, + "MyComplex_MyNestedComplex_Bar" timestamp with time zone, + "MyComplex_MyNestedComplex_Foo" integer, + CONSTRAINT "PK_Contacts" PRIMARY KEY ("Id") +); +"""); + } + + public override async Task Add_required_primitve_collection_to_existing_table() + { + await base.Add_required_primitve_collection_to_existing_table(); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL;"""); + } + + public override async Task Add_required_primitve_collection_with_custom_default_value_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_default_value_to_existing_table(); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" integer[] NOT NULL DEFAULT ARRAY[1,2,3]::integer[];"""); + } + + [ConditionalFact(Skip = "issue #33038")] + public override async Task Add_required_primitve_collection_with_custom_converter_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_converter_to_existing_table(); + + AssertSql("ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing';"); + } + + public override async Task Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table() + { + await base.Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table(); + + AssertSql("""ALTER TABLE "Customers" ADD "Numbers" text NOT NULL DEFAULT 'some numbers';"""); + } + + protected override string NonDefaultCollation => "POSIX"; diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs index dd80b867a..0a2e11cce 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs @@ -16,4 +16,9 @@ public override Task Query_generates_correct_datetime2_parameter_definition(int? public override Task Query_generates_correct_datetimeoffset_parameter_definition(int? fractionalSeconds, string postfix) => Assert.ThrowsAsync( () => base.Query_generates_correct_datetime2_parameter_definition(fractionalSeconds, postfix)); + + // Cannot write DateTimeOffset with Offset=10:00:00 to PostgreSQL type 'timestamp with time zone', only offset 0 (UTC) is supported. + public override Task Projecting_one_of_two_similar_complex_types_picks_the_correct_one() + => Assert.ThrowsAsync( + () => base.Projecting_one_of_two_similar_complex_types_picks_the_correct_one()); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs index 6cedca28b..9ca70651d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs @@ -1023,6 +1023,29 @@ public override async Task Same_complex_type_projected_twice_with_pushdown_as_pa AssertSql(""); } + public override async Task Entity_with_complex_type_with_group_by_and_first(bool async) + { + await base.Entity_with_complex_type_with_group_by_and_first(async); + + AssertSql( + """ +SELECT c3."Id", c3."Name", c3."BillingAddress_AddressLine1", c3."BillingAddress_AddressLine2", c3."BillingAddress_Tags", c3."BillingAddress_ZipCode", c3."BillingAddress_Country_Code", c3."BillingAddress_Country_FullName", c3."ShippingAddress_AddressLine1", c3."ShippingAddress_AddressLine2", c3."ShippingAddress_Tags", c3."ShippingAddress_ZipCode", c3."ShippingAddress_Country_Code", c3."ShippingAddress_Country_FullName" +FROM ( + SELECT c."Id" + FROM "Customer" AS c + GROUP BY c."Id" +) AS c1 +LEFT JOIN ( + SELECT c2."Id", c2."Name", c2."BillingAddress_AddressLine1", c2."BillingAddress_AddressLine2", c2."BillingAddress_Tags", c2."BillingAddress_ZipCode", c2."BillingAddress_Country_Code", c2."BillingAddress_Country_FullName", c2."ShippingAddress_AddressLine1", c2."ShippingAddress_AddressLine2", c2."ShippingAddress_Tags", c2."ShippingAddress_ZipCode", c2."ShippingAddress_Country_Code", c2."ShippingAddress_Country_FullName" + FROM ( + SELECT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName", ROW_NUMBER() OVER(PARTITION BY c0."Id" ORDER BY c0."Id" NULLS FIRST) AS row + FROM "Customer" AS c0 + ) AS c2 + WHERE c2.row <= 1 +) AS c3 ON c1."Id" = c3."Id" +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs index 84b0e0a29..c6acdf2f5 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs @@ -4,10 +4,12 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; +#nullable enable + public class NpgsqlTestStore : RelationalTestStore { - private readonly string _scriptPath; - private readonly string _additionalSql; + private readonly string? _scriptPath; + private readonly string? _additionalSql; private const string Northwind = "Northwind"; @@ -17,55 +19,53 @@ public class NpgsqlTestStore : RelationalTestStore public static NpgsqlTestStore GetNorthwindStore() => (NpgsqlTestStore)NpgsqlNorthwindTestStoreFactory.Instance - .GetOrCreate(NpgsqlNorthwindTestStoreFactory.Name).Initialize(null, (Func)null); + .GetOrCreate(NpgsqlNorthwindTestStoreFactory.Name).Initialize(null, (Func?)null); // ReSharper disable once UnusedMember.Global public static NpgsqlTestStore GetOrCreateInitialized(string name) - => new NpgsqlTestStore(name).InitializeNpgsql(null, (Func)null, null); + => new NpgsqlTestStore(name).InitializeNpgsql(null, (Func?)null, null); public static NpgsqlTestStore GetOrCreate( string name, - string scriptPath = null, - string additionalSql = null, - string connectionStringOptions = null) + string? scriptPath = null, + string? additionalSql = null, + string? connectionStringOptions = null) => new(name, scriptPath, additionalSql, connectionStringOptions); - public static NpgsqlTestStore Create(string name, string connectionStringOptions = null) + public static NpgsqlTestStore Create(string name, string? connectionStringOptions = null) => new(name, connectionStringOptions: connectionStringOptions, shared: false); public static NpgsqlTestStore CreateInitialized(string name) => new NpgsqlTestStore(name, shared: false) - .InitializeNpgsql(null, (Func)null, null); + .InitializeNpgsql(null, (Func?)null, null); private NpgsqlTestStore( string name, - string scriptPath = null, - string additionalSql = null, - string connectionStringOptions = null, + string? scriptPath = null, + string? additionalSql = null, + string? connectionStringOptions = null, bool shared = true) - : base(name, shared) + : base(name, shared, CreateConnection(name, connectionStringOptions)) { Name = name; if (scriptPath is not null) { // ReSharper disable once AssignNullToNotNullAttribute - _scriptPath = Path.Combine(Path.GetDirectoryName(typeof(NpgsqlTestStore).GetTypeInfo().Assembly.Location), scriptPath); + _scriptPath = Path.Combine(Path.GetDirectoryName(typeof(NpgsqlTestStore).GetTypeInfo().Assembly.Location)!, scriptPath); } _additionalSql = additionalSql; - - // ReSharper disable VirtualMemberCallInConstructor - ConnectionString = CreateConnectionString(Name, connectionStringOptions); - Connection = new NpgsqlConnection(ConnectionString); - // ReSharper restore VirtualMemberCallInConstructor } + private static NpgsqlConnection CreateConnection(string name, string? connectionStringOptions) + => new(CreateConnectionString(name, connectionStringOptions)); + // ReSharper disable once MemberCanBePrivate.Global public NpgsqlTestStore InitializeNpgsql( - IServiceProvider serviceProvider, - Func createContext, - Action seed) + IServiceProvider? serviceProvider, + Func? createContext, + Action? seed) => (NpgsqlTestStore)Initialize(serviceProvider, createContext, seed); // ReSharper disable once UnusedMember.Global @@ -75,7 +75,7 @@ public NpgsqlTestStore InitializeNpgsql( Action seed) => InitializeNpgsql(serviceProvider, () => createContext(this), seed); - protected override void Initialize(Func createContext, Action seed, Action clean) + protected override void Initialize(Func createContext, Action? seed, Action? clean) { if (CreateDatabase(clean)) { @@ -123,7 +123,7 @@ private static string GetScratchDbName() return name; } - private bool CreateDatabase(Action clean) + private bool CreateDatabase(Action? clean) { using (var master = new NpgsqlConnection(CreateAdminConnectionString())) { @@ -273,34 +273,34 @@ public T ExecuteScalar(string sql, params object[] parameters) => ExecuteScalar(Connection, sql, parameters); private static T ExecuteScalar(DbConnection connection, string sql, params object[] parameters) - => Execute(connection, command => (T)command.ExecuteScalar(), sql, false, parameters); + => Execute(connection, command => (T)command.ExecuteScalar()!, sql, false, parameters); // ReSharper disable once UnusedMember.Global public Task ExecuteScalarAsync(string sql, params object[] parameters) => ExecuteScalarAsync(Connection, sql, parameters); - private static Task ExecuteScalarAsync(DbConnection connection, string sql, object[] parameters = null) - => ExecuteAsync(connection, async command => (T)await command.ExecuteScalarAsync(), sql, false, parameters); + private static Task ExecuteScalarAsync(DbConnection connection, string sql, object[]? parameters = null) + => ExecuteAsync(connection, async command => (T)(await command.ExecuteScalarAsync())!, sql, false, parameters); // ReSharper disable once UnusedMethodReturnValue.Global public int ExecuteNonQuery(string sql, params object[] parameters) => ExecuteNonQuery(Connection, sql, parameters); - private static int ExecuteNonQuery(DbConnection connection, string sql, object[] parameters = null) + private static int ExecuteNonQuery(DbConnection connection, string sql, object[]? parameters = null) => Execute(connection, command => command.ExecuteNonQuery(), sql, false, parameters); // ReSharper disable once UnusedMember.Global public Task ExecuteNonQueryAsync(string sql, params object[] parameters) => ExecuteNonQueryAsync(Connection, sql, parameters); - private static Task ExecuteNonQueryAsync(DbConnection connection, string sql, object[] parameters = null) + private static Task ExecuteNonQueryAsync(DbConnection connection, string sql, object[]? parameters = null) => ExecuteAsync(connection, command => command.ExecuteNonQueryAsync(), sql, false, parameters); // ReSharper disable once UnusedMember.Global public IEnumerable Query(string sql, params object[] parameters) => Query(Connection, sql, parameters); - private static IEnumerable Query(DbConnection connection, string sql, object[] parameters = null) + private static IEnumerable Query(DbConnection connection, string sql, object[]? parameters = null) => Execute( connection, command => { @@ -320,7 +320,7 @@ private static IEnumerable Query(DbConnection connection, string sql, obje public Task> QueryAsync(string sql, params object[] parameters) => QueryAsync(Connection, sql, parameters); - private static Task> QueryAsync(DbConnection connection, string sql, object[] parameters = null) + private static Task> QueryAsync(DbConnection connection, string sql, object[]? parameters = null) => ExecuteAsync( connection, async command => { @@ -341,7 +341,7 @@ private static T Execute( Func execute, string sql, bool useTransaction = false, - object[] parameters = null) + object[]? parameters = null) => ExecuteCommand(connection, execute, sql, useTransaction, parameters); private static T ExecuteCommand( @@ -349,7 +349,7 @@ private static T ExecuteCommand( Func execute, string sql, bool useTransaction, - object[] parameters) + object[]? parameters) { if (connection.State != ConnectionState.Closed) { @@ -388,7 +388,7 @@ private static Task ExecuteAsync( Func> executeAsync, string sql, bool useTransaction = false, - IReadOnlyList parameters = null) + IReadOnlyList? parameters = null) => ExecuteCommandAsync(connection, executeAsync, sql, useTransaction, parameters); private static async Task ExecuteCommandAsync( @@ -396,7 +396,7 @@ private static async Task ExecuteCommandAsync( Func> executeAsync, string sql, bool useTransaction, - IReadOnlyList parameters) + IReadOnlyList? parameters) { if (connection.State != ConnectionState.Closed) { @@ -432,7 +432,7 @@ private static async Task ExecuteCommandAsync( private static DbCommand CreateCommand( DbConnection connection, string commandText, - IReadOnlyList parameters = null) + IReadOnlyList? parameters = null) { var command = (NpgsqlCommand)connection.CreateCommand(); @@ -450,7 +450,7 @@ private static DbCommand CreateCommand( return command; } - public static string CreateConnectionString(string name, string options = null) + public static string CreateConnectionString(string name, string? options = null) { var builder = new NpgsqlConnectionStringBuilder(TestEnvironment.DefaultConnection) { Database = name }; diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs index 134e4e820..1fab81f8c 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs @@ -340,43 +340,35 @@ await AssertQuery( t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).ToInstant() == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - // TODO: https://github.com/dotnet/efcore/pull/33241 - Assert.Throws( - () => - AssertSql( - """ + AssertSql( + """ @__ToInstant_0='2018-04-20T08:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."LocalDateTime" AT TIME ZONE 'Europe/Berlin' = @__ToInstant_0 -""")); +"""); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task LocalDateTime_InZoneLeniently_ToInstant_with_column_time_zone(bool async) { - // TODO: https://github.com/dotnet/efcore/pull/33241 - await Assert.ThrowsAsync( - async () => - { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb[t.TimeZoneId]).ToInstant() - == new ZonedDateTime( - new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - - AssertSql( - """ + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb[t.TimeZoneId]).ToInstant() + == new ZonedDateTime( + new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ @__ToInstant_0='2018-04-20T08:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."LocalDateTime" AT TIME ZONE n."TimeZoneId" = @__ToInstant_0 """); - }); } [ConditionalFact] @@ -1501,25 +1493,20 @@ WHERE @__dateRange_0 @> n."LocalDate" [MemberData(nameof(IsAsyncData))] public async Task Instance_InUtc(bool async) { - // TODO: https://github.com/dotnet/efcore/pull/33241 - await Assert.ThrowsAsync( - async () => - { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.Instant.InUtc() - == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero))); - - AssertSql( - """ + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.Instant.InUtc() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero))); + + AssertSql( + """ @__p_0='2018-04-20T10:31:33 UTC (+00)' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."Instant" = @__p_0 """); - }); } [ConditionalTheory] @@ -1812,25 +1799,20 @@ await AssertQuery( [MemberData(nameof(IsAsyncData))] public async Task ZonedDateTime_ToInstant(bool async) { - // TODO: https://github.com/dotnet/efcore/pull/33241 - await Assert.ThrowsAsync( - async () => - { - await AssertQuery( - async, - ss => ss.Set().Where( - t => t.ZonedDateTime.ToInstant() - == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); - - AssertSql( - """ + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.ZonedDateTime.ToInstant() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ @__ToInstant_0='2018-04-20T10:31:33Z' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" FROM "NodaTimeTypes" AS n WHERE n."ZonedDateTime" = @__ToInstant_0 """); - }); } [ConditionalFact] @@ -1838,19 +1820,15 @@ public async Task ZonedDateTime_Distance() { await using var context = CreateContext(); - // TODO: https://github.com/dotnet/efcore/pull/33241 - await Assert.ThrowsAsync( - async () => - { - var closest = await context.NodaTimeTypes - .OrderBy( - t => EF.Functions.Distance( - t.ZonedDateTime, - new ZonedDateTime(new LocalDateTime(2018, 4, 1, 0, 0, 0), DateTimeZone.Utc, Offset.Zero))).FirstAsync(); - Assert.Equal(1, closest.Id); - - AssertSql( - """ + var closest = await context.NodaTimeTypes + .OrderBy( + t => EF.Functions.Distance( + t.ZonedDateTime, + new ZonedDateTime(new LocalDateTime(2018, 4, 1, 0, 0, 0), DateTimeZone.Utc, Offset.Zero))).FirstAsync(); + Assert.Equal(1, closest.Id); + + AssertSql( + """ @__p_1='2018-04-01T00:00:00 UTC (+00)' (DbType = DateTime) SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" @@ -1858,7 +1836,6 @@ await Assert.ThrowsAsync( ORDER BY n."ZonedDateTime" <-> @__p_1 NULLS FIRST LIMIT 1 """); - }); } #endregion ZonedDateTime diff --git a/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj b/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj index f828e9b41..db1b1e6d9 100644 --- a/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj +++ b/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj @@ -18,6 +18,10 @@ + + + + From 7b25fdf48ff73ec51b7b4e060c7a2a396e74f8f1 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 12 Apr 2024 16:46:04 +0200 Subject: [PATCH 038/107] Bump version to 9.0.0-preview.4 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5fb8849de..59efc0cbe 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0-preview.3 + 9.0.0-preview.4 latest true latest From f69fc1d8e7f447dec0386dd7ef1fc3030cbed5fc Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 11 May 2024 11:26:26 +0300 Subject: [PATCH 039/107] Use Npgsql 8.0.3 (#3166) --- Directory.Packages.props | 2 +- src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0fb184004..11a01c9ad 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ 9.0.0-preview.3.24172.4 9.0.0-preview.3.24172.9 - 8.0.2 + 8.0.3 diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index d6661e7fb..5c03e6868 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -362,6 +362,7 @@ public virtual void LoadUserDefinedTypeMappings( NpgsqlDataSource? dataSource) => SetupEnumMappings(sqlGenerationHelper, dataSource); +#pragma warning disable NPG9001 /// /// Gets all global enum mappings from the ADO.NET layer and creates mappings for them /// @@ -407,6 +408,7 @@ is PropertyInfo globalEnumTypeMappingsProperty } } } +#pragma warning restore NPG9001 /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to From d5de6314940e00e4d094b66b2f7361a8dcdf52c7 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 16 May 2024 15:36:44 +0300 Subject: [PATCH 040/107] Remove direct dependency on EFCore.Abstractions (#3171) Closes #3170 --- src/EFCore.PG/EFCore.PG.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EFCore.PG/EFCore.PG.csproj b/src/EFCore.PG/EFCore.PG.csproj index 1fb3139f4..030379ded 100644 --- a/src/EFCore.PG/EFCore.PG.csproj +++ b/src/EFCore.PG/EFCore.PG.csproj @@ -16,7 +16,6 @@ - From 6b3ca689df69e3ec9135bc14570612ff232c3792 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 18 May 2024 19:42:23 +0200 Subject: [PATCH 041/107] Major work around DbDataSource management, enum handling and plugins (#3167) Fixes #2891 Fixes #3063 Fixes #1026 Co-authored-by: Nino Floris --- EFCore.PG.sln | 15 - ...pgsqlNetTopologySuiteDesignTimeServices.cs | 2 +- ...ySuiteDbContextOptionsBuilderExtensions.cs | 23 +- ...opologySuiteServiceCollectionExtensions.cs | 8 +- .../INpgsqlNetTopologySuiteOptions.cs | 14 - ...INpgsqlNetTopologySuiteSingletonOptions.cs | 41 + ...ologySuiteDataSourceConfigurationPlugin.cs | 26 + .../NpgsqlNetTopologySuiteOptionsExtension.cs | 76 +- ...NpgsqlNetTopologySuiteSingletonOptions.cs} | 14 +- ...NetTopologySuiteTypeMappingSourcePlugin.cs | 4 +- ...daTimeDbContextOptionsBuilderExtensions.cs | 5 - ...gsqlNodaTimeServiceCollectionExtensions.cs | 5 +- .../NodaTimeDataSourceConfigurationPlugin.cs | 19 + .../Properties/AssemblyInfo.cs | 2 +- ...sqlCSharpRuntimeAnnotationCodeGenerator.cs | 36 +- .../NpgsqlModelBuilderExtensions.cs | 55 + .../NpgsqlModelExtensions.cs | 13 + .../NpgsqlServiceCollectionExtensions.cs | 3 +- .../EntityFrameworkNpgsqlServicesBuilder.cs | 36 + .../INpgsqlDataSourceConfigurationPlugin.cs | 24 + .../Infrastructure/Internal/EnumDefinition.cs | 148 ++ .../Internal/INpgsqlSingletonOptions.cs | 10 +- .../Internal/NpgsqlOptionsExtension.cs | 101 +- .../Internal/UserRangeDefinition.cs | 74 + .../NpgsqlDbContextOptionsBuilder.cs | 27 + .../Internal/NpgsqlSingletonOptions.cs | 27 +- .../Conventions/NpgsqlConventionSetBuilder.cs | 5 +- ...NpgsqlPostgresModelFinalizingConvention.cs | 26 +- src/EFCore.PG/Metadata/PostgresEnum.cs | 43 + .../NpgsqlMigrationsSqlGenerator.cs | 4 +- .../Internal/NpgsqlStringMethodTranslator.cs | 4 +- .../Mapping/NpgsqlArrayTypeMapping.cs | 20 +- .../Internal/Mapping/NpgsqlEnumTypeMapping.cs | 84 +- .../Mapping/NpgsqlRangeTypeMapping.cs | 8 +- .../Internal/NpgsqlDataSourceManager.cs | 178 ++ .../Internal/NpgsqlRelationalConnection.cs | 11 +- .../Internal/NpgsqlTypeMappingSource.cs | 126 +- .../BuiltInDataTypesNpgsqlTest.cs | 18 +- .../EFCore.PG.FunctionalTests.csproj | 1 + .../JsonTypesNpgsqlTest.cs | 13 +- .../Query/EnumQueryTest.cs | 31 +- .../LegacyNpgsqlNodaTimeTypeMappingTest.cs | 105 + .../Query/LegacyTimestampQueryTest.cs | 2 +- .../Query/NodaTimeQueryNpgsqlTest.cs | 2028 +++++++++++++++++ .../Query/SpatialQueryNpgsqlFixture.cs | 11 +- .../Query/TimestampQueryTest.cs | 2 +- .../SpatialNpgsqlFixture.cs | 11 +- .../SpatialNpgsqlTest.cs | 5 + .../NpgsqlNorthwindTestStoreFactory.cs | 7 +- .../TestUtilities/NpgsqlTestStore.cs | 46 +- .../TestUtilities/NpgsqlTestStoreFactory.cs | 23 +- .../NodaTimeQueryNpgsqlTest.cs | 12 +- .../NpgsqlDbContextOptionsExtensionsTest.cs | 2 +- .../NpgsqlRelationalConnectionTest.cs | 90 +- .../Storage}/NpgsqlNodaTimeTypeMappingTest.cs | 3 +- .../Storage/NpgsqlTypeMappingSourceTest.cs | 2 +- .../Storage/NpgsqlTypeMappingTest.cs | 12 +- 57 files changed, 3386 insertions(+), 355 deletions(-) delete mode 100644 src/EFCore.PG.NTS/Infrastructure/Internal/INpgsqlNetTopologySuiteOptions.cs create mode 100644 src/EFCore.PG.NTS/Infrastructure/Internal/INpgsqlNetTopologySuiteSingletonOptions.cs create mode 100644 src/EFCore.PG.NTS/Infrastructure/Internal/NetTopologySuiteDataSourceConfigurationPlugin.cs rename src/EFCore.PG.NTS/Internal/{NpgsqlNetTopologySuiteOptions.cs => NpgsqlNetTopologySuiteSingletonOptions.cs} (54%) create mode 100644 src/EFCore.PG.NodaTime/Infrastructure/Internal/NodaTimeDataSourceConfigurationPlugin.cs create mode 100644 src/EFCore.PG/Infrastructure/EntityFrameworkNpgsqlServicesBuilder.cs create mode 100644 src/EFCore.PG/Infrastructure/INpgsqlDataSourceConfigurationPlugin.cs create mode 100644 src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs create mode 100644 src/EFCore.PG/Infrastructure/Internal/UserRangeDefinition.cs create mode 100644 src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/LegacyNpgsqlNodaTimeTypeMappingTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs rename test/{EFCore.PG.NodaTime.FunctionalTests => EFCore.PG.Tests/Storage}/NpgsqlNodaTimeTypeMappingTest.cs (99%) diff --git a/EFCore.PG.sln b/EFCore.PG.sln index dd965ca0d..9ccd15d74 100644 --- a/EFCore.PG.sln +++ b/EFCore.PG.sln @@ -23,8 +23,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.PG.Tests", "test\EFC EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.PG.FunctionalTests", "test\EFCore.PG.FunctionalTests\EFCore.PG.FunctionalTests.csproj", "{05A7D0B7-4AE1-4BC8-A1BE-2389F1593B2D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.PG.NodaTime.FunctionalTests", "test\EFCore.PG.NodaTime.FunctionalTests\EFCore.PG.NodaTime.FunctionalTests.csproj", "{B78A7825-BE72-4509-B0AD-01EEC67A9624}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.PG.NodaTime", "src\EFCore.PG.NodaTime\EFCore.PG.NodaTime.csproj", "{77F0608F-6D0C-481C-9108-D5176E2EAD69}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFCore.PG.NTS", "src\EFCore.PG.NTS\EFCore.PG.NTS.csproj", "{D7106D61-C7CA-4005-B31F-43281BB397AD}" @@ -70,18 +68,6 @@ Global {05A7D0B7-4AE1-4BC8-A1BE-2389F1593B2D}.Release|Any CPU.Build.0 = Release|Any CPU {05A7D0B7-4AE1-4BC8-A1BE-2389F1593B2D}.Release|x64.ActiveCfg = Release|Any CPU {05A7D0B7-4AE1-4BC8-A1BE-2389F1593B2D}.Release|x86.ActiveCfg = Release|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Debug|x64.ActiveCfg = Debug|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Debug|x64.Build.0 = Debug|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Debug|x86.ActiveCfg = Debug|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Debug|x86.Build.0 = Debug|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Release|Any CPU.Build.0 = Release|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Release|x64.ActiveCfg = Release|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Release|x64.Build.0 = Release|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Release|x86.ActiveCfg = Release|Any CPU - {B78A7825-BE72-4509-B0AD-01EEC67A9624}.Release|x86.Build.0 = Release|Any CPU {77F0608F-6D0C-481C-9108-D5176E2EAD69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {77F0608F-6D0C-481C-9108-D5176E2EAD69}.Debug|Any CPU.Build.0 = Debug|Any CPU {77F0608F-6D0C-481C-9108-D5176E2EAD69}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -114,7 +100,6 @@ Global {FADDA2D1-03B4-4DEF-8D24-DD1CA4E81F4A} = {8537E50E-CF7F-49CB-B4EF-3E2A1B11F050} {E1D99AD4-D88B-42BA-86DF-90B98B2E9A01} = {ED612DB1-AB32-4603-95E7-891BACA71C39} {05A7D0B7-4AE1-4BC8-A1BE-2389F1593B2D} = {ED612DB1-AB32-4603-95E7-891BACA71C39} - {B78A7825-BE72-4509-B0AD-01EEC67A9624} = {ED612DB1-AB32-4603-95E7-891BACA71C39} {77F0608F-6D0C-481C-9108-D5176E2EAD69} = {8537E50E-CF7F-49CB-B4EF-3E2A1B11F050} {D7106D61-C7CA-4005-B31F-43281BB397AD} = {8537E50E-CF7F-49CB-B4EF-3E2A1B11F050} {26203F54-A3C1-43AD-A101-8F589D2D67AD} = {4A5A60DD-41B6-40BF-B677-227A921ECCC8} diff --git a/src/EFCore.PG.NTS/Design/Internal/NpgsqlNetTopologySuiteDesignTimeServices.cs b/src/EFCore.PG.NTS/Design/Internal/NpgsqlNetTopologySuiteDesignTimeServices.cs index f5550ba0a..8f3301540 100644 --- a/src/EFCore.PG.NTS/Design/Internal/NpgsqlNetTopologySuiteDesignTimeServices.cs +++ b/src/EFCore.PG.NTS/Design/Internal/NpgsqlNetTopologySuiteDesignTimeServices.cs @@ -25,5 +25,5 @@ public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollec => serviceCollection .AddSingleton() .AddSingleton() - .TryAddSingleton(); + .TryAddSingleton(); } diff --git a/src/EFCore.PG.NTS/Extensions/NpgsqlNetTopologySuiteDbContextOptionsBuilderExtensions.cs b/src/EFCore.PG.NTS/Extensions/NpgsqlNetTopologySuiteDbContextOptionsBuilderExtensions.cs index a78ee485b..2374de24a 100644 --- a/src/EFCore.PG.NTS/Extensions/NpgsqlNetTopologySuiteDbContextOptionsBuilderExtensions.cs +++ b/src/EFCore.PG.NTS/Extensions/NpgsqlNetTopologySuiteDbContextOptionsBuilderExtensions.cs @@ -22,19 +22,26 @@ public static NpgsqlDbContextOptionsBuilder UseNetTopologySuite( Ordinates handleOrdinates = Ordinates.None, bool geographyAsDefault = false) { - Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - - // TODO: Global-only setup at the ADO.NET level for now, optionally allow per-connection? -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - NpgsqlConnection.GlobalTypeMapper.UseNetTopologySuite( - coordinateSequenceFactory, precisionModel, handleOrdinates, geographyAsDefault); -#pragma warning restore CS0618 - var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)optionsBuilder).OptionsBuilder; var extension = coreOptionsBuilder.Options.FindExtension() ?? new NpgsqlNetTopologySuiteOptionsExtension(); + if (coordinateSequenceFactory is not null) + { + extension = extension.WithCoordinateSequenceFactory(coordinateSequenceFactory); + } + + if (precisionModel is not null) + { + extension = extension.WithPrecisionModel(precisionModel); + } + + if (handleOrdinates is not Ordinates.None) + { + extension = extension.WithHandleOrdinates(handleOrdinates); + } + if (geographyAsDefault) { extension = extension.WithGeographyDefault(); diff --git a/src/EFCore.PG.NTS/Extensions/NpgsqlNetTopologySuiteServiceCollectionExtensions.cs b/src/EFCore.PG.NTS/Extensions/NpgsqlNetTopologySuiteServiceCollectionExtensions.cs index 779c0a0c6..c7a9b2708 100644 --- a/src/EFCore.PG.NTS/Extensions/NpgsqlNetTopologySuiteServiceCollectionExtensions.cs +++ b/src/EFCore.PG.NTS/Extensions/NpgsqlNetTopologySuiteServiceCollectionExtensions.cs @@ -1,3 +1,4 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal; @@ -21,15 +22,16 @@ public static IServiceCollection AddEntityFrameworkNpgsqlNetTopologySuite( { Check.NotNull(serviceCollection, nameof(serviceCollection)); - new EntityFrameworkRelationalServicesBuilder(serviceCollection) - .TryAdd(p => p.GetRequiredService()) + new EntityFrameworkNpgsqlServicesBuilder(serviceCollection) + .TryAdd() + .TryAdd(p => p.GetRequiredService()) .TryAdd() .TryAdd() .TryAdd() .TryAdd() .TryAdd() .TryAddProviderSpecificServices( - x => x.TryAddSingleton()); + x => x.TryAddSingleton()); return serviceCollection; } diff --git a/src/EFCore.PG.NTS/Infrastructure/Internal/INpgsqlNetTopologySuiteOptions.cs b/src/EFCore.PG.NTS/Infrastructure/Internal/INpgsqlNetTopologySuiteOptions.cs deleted file mode 100644 index 7c6e317cc..000000000 --- a/src/EFCore.PG.NTS/Infrastructure/Internal/INpgsqlNetTopologySuiteOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ReSharper disable once CheckNamespace - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; - -/// -/// Represents options for Npgsql NetTopologySuite that can only be set at the singleton level. -/// -public interface INpgsqlNetTopologySuiteOptions : ISingletonOptions -{ - /// - /// True if geography is to be used by default instead of geometry - /// - bool IsGeographyDefault { get; } -} diff --git a/src/EFCore.PG.NTS/Infrastructure/Internal/INpgsqlNetTopologySuiteSingletonOptions.cs b/src/EFCore.PG.NTS/Infrastructure/Internal/INpgsqlNetTopologySuiteSingletonOptions.cs new file mode 100644 index 000000000..d8e5d7abc --- /dev/null +++ b/src/EFCore.PG.NTS/Infrastructure/Internal/INpgsqlNetTopologySuiteSingletonOptions.cs @@ -0,0 +1,41 @@ +// ReSharper disable once CheckNamespace + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + +/// +/// Represents options for Npgsql NetTopologySuite that can only be set at the singleton level. +/// +public interface INpgsqlNetTopologySuiteSingletonOptions : ISingletonOptions +{ + /// + /// 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. + /// + CoordinateSequenceFactory? CoordinateSequenceFactory { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + PrecisionModel? PrecisionModel { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + Ordinates HandleOrdinates { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IsGeographyDefault { get; } +} diff --git a/src/EFCore.PG.NTS/Infrastructure/Internal/NetTopologySuiteDataSourceConfigurationPlugin.cs b/src/EFCore.PG.NTS/Infrastructure/Internal/NetTopologySuiteDataSourceConfigurationPlugin.cs new file mode 100644 index 000000000..d258c2889 --- /dev/null +++ b/src/EFCore.PG.NTS/Infrastructure/Internal/NetTopologySuiteDataSourceConfigurationPlugin.cs @@ -0,0 +1,26 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class NetTopologySuiteDataSourceConfigurationPlugin(INpgsqlNetTopologySuiteSingletonOptions options) + : INpgsqlDataSourceConfigurationPlugin +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public void Configure(NpgsqlDataSourceBuilder npgsqlDataSourceBuilder) + => npgsqlDataSourceBuilder.UseNetTopologySuite( + options.CoordinateSequenceFactory, + options.PrecisionModel, + options.HandleOrdinates, + options.IsGeographyDefault); +} diff --git a/src/EFCore.PG.NTS/Infrastructure/Internal/NpgsqlNetTopologySuiteOptionsExtension.cs b/src/EFCore.PG.NTS/Infrastructure/Internal/NpgsqlNetTopologySuiteOptionsExtension.cs index 3d0205650..a217be224 100644 --- a/src/EFCore.PG.NTS/Infrastructure/Internal/NpgsqlNetTopologySuiteOptionsExtension.cs +++ b/src/EFCore.PG.NTS/Infrastructure/Internal/NpgsqlNetTopologySuiteOptionsExtension.cs @@ -14,6 +14,30 @@ public class NpgsqlNetTopologySuiteOptionsExtension : IDbContextOptionsExtension { private DbContextOptionsExtensionInfo? _info; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual CoordinateSequenceFactory? CoordinateSequenceFactory { get; private set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PrecisionModel? PrecisionModel { get; private set; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Ordinates HandleOrdinates { get; private set; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -68,6 +92,52 @@ public virtual void ApplyServices(IServiceCollection services) public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual NpgsqlNetTopologySuiteOptionsExtension WithCoordinateSequenceFactory( + CoordinateSequenceFactory? coordinateSequenceFactory) + { + var clone = Clone(); + + clone.CoordinateSequenceFactory = coordinateSequenceFactory; + + return clone; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual NpgsqlNetTopologySuiteOptionsExtension WithPrecisionModel(PrecisionModel? precisionModel) + { + var clone = Clone(); + + clone.PrecisionModel = precisionModel; + + return clone; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual NpgsqlNetTopologySuiteOptionsExtension WithHandleOrdinates(Ordinates handleOrdinates) + { + var clone = Clone(); + + clone.HandleOrdinates = handleOrdinates; + + return clone; + } + /// /// 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 @@ -123,7 +193,11 @@ public override int GetServiceProviderHashCode() => Extension.IsGeographyDefault.GetHashCode(); public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) - => true; + => other is ExtensionInfo otherInfo + && ReferenceEquals(Extension.CoordinateSequenceFactory, otherInfo.Extension.CoordinateSequenceFactory) + && ReferenceEquals(Extension.PrecisionModel, otherInfo.Extension.PrecisionModel) + && Extension.HandleOrdinates == otherInfo.Extension.HandleOrdinates + && Extension.IsGeographyDefault == otherInfo.Extension.IsGeographyDefault; public override void PopulateDebugInfo(IDictionary debugInfo) { diff --git a/src/EFCore.PG.NTS/Internal/NpgsqlNetTopologySuiteOptions.cs b/src/EFCore.PG.NTS/Internal/NpgsqlNetTopologySuiteSingletonOptions.cs similarity index 54% rename from src/EFCore.PG.NTS/Internal/NpgsqlNetTopologySuiteOptions.cs rename to src/EFCore.PG.NTS/Internal/NpgsqlNetTopologySuiteSingletonOptions.cs index cd559f8a0..f34dc4aa1 100644 --- a/src/EFCore.PG.NTS/Internal/NpgsqlNetTopologySuiteOptions.cs +++ b/src/EFCore.PG.NTS/Internal/NpgsqlNetTopologySuiteSingletonOptions.cs @@ -4,8 +4,17 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Internal; /// -public class NpgsqlNetTopologySuiteOptions : INpgsqlNetTopologySuiteOptions +public class NpgsqlNetTopologySuiteSingletonOptions : INpgsqlNetTopologySuiteSingletonOptions { + /// + public virtual CoordinateSequenceFactory? CoordinateSequenceFactory { get; set; } + + /// + public virtual PrecisionModel? PrecisionModel { get; set; } + + /// + public virtual Ordinates HandleOrdinates { get; set; } + /// public virtual bool IsGeographyDefault { get; set; } @@ -15,6 +24,9 @@ public virtual void Initialize(IDbContextOptions options) var npgsqlNtsOptions = options.FindExtension() ?? new NpgsqlNetTopologySuiteOptionsExtension(); + CoordinateSequenceFactory = npgsqlNtsOptions.CoordinateSequenceFactory; + PrecisionModel = npgsqlNtsOptions.PrecisionModel; + HandleOrdinates = npgsqlNtsOptions.HandleOrdinates; IsGeographyDefault = npgsqlNtsOptions.IsGeographyDefault; } diff --git a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlNetTopologySuiteTypeMappingSourcePlugin.cs b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlNetTopologySuiteTypeMappingSourcePlugin.cs index 786a1c1f4..2dcbd5a77 100644 --- a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlNetTopologySuiteTypeMappingSourcePlugin.cs +++ b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlNetTopologySuiteTypeMappingSourcePlugin.cs @@ -15,7 +15,7 @@ public class NpgsqlNetTopologySuiteTypeMappingSourcePlugin : IRelationalTypeMapp { // Note: we reference the options rather than copying IsGeographyDefault out, because that field is initialized // rather late by SingletonOptionsInitializer - private readonly INpgsqlNetTopologySuiteOptions _options; + private readonly INpgsqlNetTopologySuiteSingletonOptions _options; private static bool TryGetClrType(string subtypeName, [NotNullWhen(true)] out Type? clrType) { @@ -41,7 +41,7 @@ private static bool TryGetClrType(string subtypeName, [NotNullWhen(true)] out Ty /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public NpgsqlNetTopologySuiteTypeMappingSourcePlugin(INpgsqlNetTopologySuiteOptions options) + public NpgsqlNetTopologySuiteTypeMappingSourcePlugin(INpgsqlNetTopologySuiteSingletonOptions options) { _options = Check.NotNull(options, nameof(options)); } diff --git a/src/EFCore.PG.NodaTime/Extensions/NpgsqlNodaTimeDbContextOptionsBuilderExtensions.cs b/src/EFCore.PG.NodaTime/Extensions/NpgsqlNodaTimeDbContextOptionsBuilderExtensions.cs index 407291d94..3af53dc7c 100644 --- a/src/EFCore.PG.NodaTime/Extensions/NpgsqlNodaTimeDbContextOptionsBuilderExtensions.cs +++ b/src/EFCore.PG.NodaTime/Extensions/NpgsqlNodaTimeDbContextOptionsBuilderExtensions.cs @@ -18,11 +18,6 @@ public static NpgsqlDbContextOptionsBuilder UseNodaTime( { Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - // TODO: Global-only setup at the ADO.NET level for now, optionally allow per-connection? -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - NpgsqlConnection.GlobalTypeMapper.UseNodaTime(); -#pragma warning restore CS0618 - var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)optionsBuilder).OptionsBuilder; var extension = coreOptionsBuilder.Options.FindExtension() diff --git a/src/EFCore.PG.NodaTime/Extensions/NpgsqlNodaTimeServiceCollectionExtensions.cs b/src/EFCore.PG.NodaTime/Extensions/NpgsqlNodaTimeServiceCollectionExtensions.cs index f070cc10c..377673736 100644 --- a/src/EFCore.PG.NodaTime/Extensions/NpgsqlNodaTimeServiceCollectionExtensions.cs +++ b/src/EFCore.PG.NodaTime/Extensions/NpgsqlNodaTimeServiceCollectionExtensions.cs @@ -1,3 +1,5 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime.Query.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; @@ -19,7 +21,8 @@ public static IServiceCollection AddEntityFrameworkNpgsqlNodaTime( { Check.NotNull(serviceCollection, nameof(serviceCollection)); - new EntityFrameworkRelationalServicesBuilder(serviceCollection) + new EntityFrameworkNpgsqlServicesBuilder(serviceCollection) + .TryAdd() .TryAdd() .TryAdd() .TryAdd() diff --git a/src/EFCore.PG.NodaTime/Infrastructure/Internal/NodaTimeDataSourceConfigurationPlugin.cs b/src/EFCore.PG.NodaTime/Infrastructure/Internal/NodaTimeDataSourceConfigurationPlugin.cs new file mode 100644 index 000000000..b6fb4e58d --- /dev/null +++ b/src/EFCore.PG.NodaTime/Infrastructure/Internal/NodaTimeDataSourceConfigurationPlugin.cs @@ -0,0 +1,19 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class NodaTimeDataSourceConfigurationPlugin : INpgsqlDataSourceConfigurationPlugin +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public void Configure(NpgsqlDataSourceBuilder npgsqlDataSourceBuilder) + => npgsqlDataSourceBuilder.UseNodaTime(); +} diff --git a/src/EFCore.PG.NodaTime/Properties/AssemblyInfo.cs b/src/EFCore.PG.NodaTime/Properties/AssemblyInfo.cs index f3bb15a10..75fa2f835 100644 --- a/src/EFCore.PG.NodaTime/Properties/AssemblyInfo.cs +++ b/src/EFCore.PG.NodaTime/Properties/AssemblyInfo.cs @@ -2,7 +2,7 @@ [assembly: InternalsVisibleTo( - "Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime.FunctionalTests, PublicKey=" + "Npgsql.EntityFrameworkCore.PostgreSQL.FunctionalTests, PublicKey=" + "0024000004800000940000000602000000240000525341310004000001000100" + "2b3c590b2a4e3d347e6878dc0ff4d21eb056a50420250c6617044330701d35c9" + "8078a5df97a62d83c9a2db2d072523a8fc491398254c6b89329b8c1dcef43a1e" diff --git a/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs index daae6db8b..2cbdaa588 100644 --- a/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs @@ -82,15 +82,40 @@ public override bool Create( switch (typeMapping) { -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete case NpgsqlEnumTypeMapping enumTypeMapping: - if (enumTypeMapping.NameTranslator != NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator) + var code = Dependencies.CSharpHelper; + mainBuilder.AppendLine(";"); + + mainBuilder.AppendLine( + $"{parameters.TargetName}.TypeMapping = ((NpgsqlEnumTypeMapping){parameters.TargetName}.TypeMapping).Clone(") + .IncrementIndent(); + + mainBuilder + .Append("unquotedStoreType: ") + .Append(code.Literal(enumTypeMapping.UnquotedStoreType)) + .AppendLine(",") + .AppendLine("labels: new Dictionary()") + .AppendLine("{") + .IncrementIndent(); + + foreach (var (enumValue, label) in enumTypeMapping.Labels) { - throw new NotSupportedException( - "Mapped enums are only supported in the compiled model if they use the default name translator"); + mainBuilder + .Append('[') + .Append(code.UnknownLiteral(enumValue)) + .Append(']') + .Append(" = ") + .Append(code.Literal(label)) + .AppendLine(","); } + + mainBuilder + .Append("}") + .DecrementIndent() + .Append(")") + .DecrementIndent(); + break; -#pragma warning restore CS0618 case NpgsqlRangeTypeMapping rangeTypeMapping: { @@ -127,7 +152,6 @@ public override bool Create( break; } - } return result; diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs index 6cfd490c1..f38fda9af 100644 --- a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs @@ -443,6 +443,61 @@ public static ModelBuilder HasPostgresEnum( return modelBuilder; } + /// + /// Registers a user-defined enum type in the model. + /// + /// The model builder in which to create the enum type. + /// The schema in which to create the enum type. + /// The name of the enum type to create. + /// The enum label values. + /// + /// The updated . + /// + /// + /// See: https://www.postgresql.org/docs/current/static/datatype-enum.html + /// + /// builder + public static IConventionModelBuilder HasPostgresEnum( + this IConventionModelBuilder modelBuilder, + string? schema, + string name, + string[] labels) + { + Check.NotNull(modelBuilder, nameof(modelBuilder)); + Check.NotEmpty(name, nameof(name)); + Check.NotNull(labels, nameof(labels)); + + if (modelBuilder.CanSetPostgresEnum(schema, name)) + { + modelBuilder.Metadata.GetOrAddPostgresEnum(schema, name, labels); + } + return modelBuilder; + } + + /// + /// Returns a value indicating whether the given PostgreSQL extension can be registered in the model. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The model builder. + /// The schema in which to create the extension. + /// The name of the extension to create. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the default increment for SQL Server IDENTITY. + public static bool CanSetPostgresEnum( + this IConventionModelBuilder modelBuilder, + string? schema, + string name, + bool fromDataAnnotation = false) + { + var annotationName = PostgresExtension.BuildAnnotationName(schema, name); + + return modelBuilder.CanSetAnnotation(annotationName, $"{schema},{name}", fromDataAnnotation); + } + /// /// Registers a user-defined enum type in the model. /// diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs index 018c77f5f..35228a506 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs @@ -321,6 +321,19 @@ public static PostgresEnum GetOrAddPostgresEnum( string[] labels) => PostgresEnum.GetOrAddPostgresEnum(model, schema, name, labels); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static PostgresEnum GetOrAddPostgresEnum( + this IConventionModel model, + string? schema, + string name, + string[] labels) + => PostgresEnum.GetOrAddPostgresEnum(model, schema, name, labels); + /// /// 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 diff --git a/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs b/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs index 6335c3429..2cd339ca0 100644 --- a/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs +++ b/src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs @@ -87,7 +87,7 @@ public static IServiceCollection AddEntityFrameworkNpgsql(this IServiceCollectio { Check.NotNull(serviceCollection, nameof(serviceCollection)); - new EntityFrameworkRelationalServicesBuilder(serviceCollection) + new EntityFrameworkNpgsqlServicesBuilder(serviceCollection) .TryAdd() .TryAdd>() .TryAdd(p => p.GetRequiredService()) @@ -124,6 +124,7 @@ public static IServiceCollection AddEntityFrameworkNpgsql(this IServiceCollectio .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() + .TryAddSingleton() .TryAddScoped()) .TryAddCoreServices(); diff --git a/src/EFCore.PG/Infrastructure/EntityFrameworkNpgsqlServicesBuilder.cs b/src/EFCore.PG/Infrastructure/EntityFrameworkNpgsqlServicesBuilder.cs new file mode 100644 index 000000000..d6f25b764 --- /dev/null +++ b/src/EFCore.PG/Infrastructure/EntityFrameworkNpgsqlServicesBuilder.cs @@ -0,0 +1,36 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; + +/// +/// A builder API designed for Npgsql when registering services. +/// +public class EntityFrameworkNpgsqlServicesBuilder : EntityFrameworkRelationalServicesBuilder +{ + private static readonly IDictionary NpgsqlServices + = new Dictionary + { + { + typeof(INpgsqlDataSourceConfigurationPlugin), + new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) + } + }; + + /// + /// Used by relational database providers to create a new for + /// registration of provider services. + /// + /// The collection to which services will be registered. + public EntityFrameworkNpgsqlServicesBuilder(IServiceCollection serviceCollection) + : base(serviceCollection) + { + } + + /// + /// Gets the for the given service type. + /// + /// The type that defines the service API. + /// The for the type or if it's not an EF service. + protected override ServiceCharacteristics? TryGetServiceCharacteristics(Type serviceType) + => NpgsqlServices.TryGetValue(serviceType, out var characteristics) + ? characteristics + : base.TryGetServiceCharacteristics(serviceType); +} diff --git a/src/EFCore.PG/Infrastructure/INpgsqlDataSourceConfigurationPlugin.cs b/src/EFCore.PG/Infrastructure/INpgsqlDataSourceConfigurationPlugin.cs new file mode 100644 index 000000000..6a76b1dee --- /dev/null +++ b/src/EFCore.PG/Infrastructure/INpgsqlDataSourceConfigurationPlugin.cs @@ -0,0 +1,24 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; + +/// +/// Represents a plugin that configures an via . +/// +/// +/// +/// The service lifetime is and multiple registrations +/// are allowed. This means a single instance of each service is used by many +/// instances. The implementation must be thread-safe. +/// This service cannot depend on services registered as . +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +/// +public interface INpgsqlDataSourceConfigurationPlugin +{ + /// + /// Applies the plugin configuration on the given . + /// + void Configure(NpgsqlDataSourceBuilder npgsqlDataSourceBuilder); +} diff --git a/src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs b/src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs new file mode 100644 index 000000000..b34be200e --- /dev/null +++ b/src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs @@ -0,0 +1,148 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public sealed class EnumDefinition : IEquatable +{ + /// + /// Maps the CLR member values to the PostgreSQL value labels. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public IReadOnlyDictionary Labels { get; } + + /// + /// The name of the PostgreSQL enum type to be mapped. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public string StoreTypeName { get; } + + /// + /// The PostgreSQL schema in which the enum is defined. If null, the default schema is used + /// (which is public unless changed on the model). + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public string? StoreTypeSchema { get; } + + /// + /// The CLR type of the enum. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public Type ClrType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public INpgsqlNameTranslator NameTranslator { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public EnumDefinition( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type clrType, + string name, + string? schema, + INpgsqlNameTranslator nameTranslator) + { + if (clrType is not { IsEnum: true, IsClass: false }) + { + throw new ArgumentException($"Enum type mappings require a CLR enum. {clrType.FullName} is not an enum."); + } + + StoreTypeName = name; + StoreTypeSchema = schema; + ClrType = clrType; + + NameTranslator = nameTranslator; + Labels = clrType.GetFields(BindingFlags.Static | BindingFlags.Public) + .ToDictionary( + x => x.GetValue(null)!, + x => x.GetCustomAttribute()?.PgName ?? nameTranslator.TranslateMemberName(x.Name)); + } + + /// + public override bool Equals(object? obj) + => obj is EnumDefinition other && Equals(other); + + /// + public bool Equals(EnumDefinition? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(other, this)) + { + return true; + } + + if (StoreTypeName != other.StoreTypeName + || StoreTypeSchema != other.StoreTypeSchema + || ClrType != other.ClrType + || !ReferenceEquals(NameTranslator, other.NameTranslator) + || Labels.Count != other.Labels.Count) + { + return false; + } + + foreach (var (key, value) in Labels) + { + if (!other.Labels.TryGetValue(key, out var otherValue) + || value != otherValue) + { + return false; + } + } + + return true; + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(StoreTypeName); + hashCode.Add(StoreTypeSchema); + hashCode.Add(ClrType); + hashCode.Add(NameTranslator); + foreach (var (key, value) in Labels) + { + hashCode.Add(key); + hashCode.Add(value); + } + + return hashCode.ToHashCode(); + } +} diff --git a/src/EFCore.PG/Infrastructure/Internal/INpgsqlSingletonOptions.cs b/src/EFCore.PG/Infrastructure/Internal/INpgsqlSingletonOptions.cs index 7618ae040..97910f632 100644 --- a/src/EFCore.PG/Infrastructure/Internal/INpgsqlSingletonOptions.cs +++ b/src/EFCore.PG/Infrastructure/Internal/INpgsqlSingletonOptions.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; @@ -28,17 +29,12 @@ public interface INpgsqlSingletonOptions : ISingletonOptions bool ReverseNullOrderingEnabled { get; } /// - /// The data source being used, or if a connection string or connection was provided directly. + /// The collection of enum mappings. /// - DbDataSource? DataSource { get; } + IReadOnlyList EnumDefinitions { get; } /// /// The collection of range mappings. /// IReadOnlyList UserRangeDefinitions { get; } - - /// - /// The root service provider for the application, if available. />. - /// - IServiceProvider? ApplicationServiceProvider { get; } } diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index 1ed164bb4..60c5cb59a 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -2,6 +2,8 @@ using System.Globalization; using System.Net.Security; using System.Text; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; @@ -12,6 +14,7 @@ public class NpgsqlOptionsExtension : RelationalOptionsExtension { private DbContextOptionsExtensionInfo? _info; private readonly List _userRangeDefinitions; + private readonly List _enumDefinitions; private Version? _postgresVersion; @@ -57,6 +60,12 @@ public virtual bool IsPostgresVersionSet public virtual IReadOnlyList UserRangeDefinitions => _userRangeDefinitions; + /// + /// The list of range mappings specified by the user. + /// + public virtual IReadOnlyList EnumDefinitions + => _enumDefinitions; + /// /// The specified . /// @@ -85,6 +94,7 @@ public virtual IReadOnlyList UserRangeDefinitions public NpgsqlOptionsExtension() { _userRangeDefinitions = []; + _enumDefinitions = []; } // NB: When adding new options, make sure to update the copy ctor below. @@ -100,6 +110,7 @@ public NpgsqlOptionsExtension(NpgsqlOptionsExtension copyFrom) _postgresVersion = copyFrom._postgresVersion; UseRedshift = copyFrom.UseRedshift; _userRangeDefinitions = [..copyFrom._userRangeDefinitions]; + _enumDefinitions = [..copyFrom._enumDefinitions]; ProvideClientCertificatesCallback = copyFrom.ProvideClientCertificatesCallback; RemoteCertificateValidationCallback = copyFrom.RemoteCertificateValidationCallback; ProvidePasswordCallback = copyFrom.ProvidePasswordCallback; @@ -180,6 +191,31 @@ public virtual NpgsqlOptionsExtension WithUserRangeDefinition( return clone; } + /// + /// Returns a copy of the current instance configured with the specified range mapping. + /// + public virtual NpgsqlOptionsExtension WithEnumMapping( + Type clrType, + string enumName, + string? schemaName, + INpgsqlNameTranslator? nameTranslator) + { + if (clrType is not { IsEnum: true, IsClass: false}) + { + throw new ArgumentException($"Enum type mappings require a CLR enum. {clrType.FullName} is not an enum."); + } + + var clone = (NpgsqlOptionsExtension)Clone(); + +#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete + nameTranslator ??= NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator; +#pragma warning restore CS0618 + + clone._enumDefinitions.Add(new EnumDefinition(clrType, enumName, schemaName, nameTranslator)); + + return clone; + } + /// /// Returns a copy of the current instance configured to use the specified administrative database. /// @@ -397,12 +433,12 @@ public override string LogFragment { builder.Append(item.SubtypeClrType).Append("=>"); - if (item.SchemaName is not null) + if (item.StoreTypeSchema is not null) { - builder.Append(item.SchemaName).Append("."); + builder.Append(item.StoreTypeSchema).Append("."); } - builder.Append(item.RangeName); + builder.Append(item.StoreTypeName); if (item.SubtypeName is not null) { @@ -453,8 +489,8 @@ public override int GetServiceProviderHashCode() public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) => other is ExtensionInfo otherInfo && Extension.PostgresVersion == otherInfo.Extension.PostgresVersion - && ReferenceEquals(Extension.DataSource, otherInfo.Extension.DataSource) && Extension.ReverseNullOrdering == otherInfo.Extension.ReverseNullOrdering + && Extension.EnumDefinitions.SequenceEqual(otherInfo.Extension.EnumDefinitions) && Extension.UserRangeDefinitions.SequenceEqual(otherInfo.Extension.UserRangeDefinitions) && Extension.UseRedshift == otherInfo.Extension.UseRedshift; @@ -482,6 +518,16 @@ public override void PopulateDebugInfo(IDictionary debugInfo) debugInfo["Npgsql.EntityFrameworkCore.PostgreSQL:" + nameof(NpgsqlDbContextOptionsBuilder.ProvidePasswordCallback)] = (Extension.ProvidePasswordCallback?.GetHashCode() ?? 0).ToString(CultureInfo.InvariantCulture); + foreach (var enumDefinition in Extension._enumDefinitions) + { + debugInfo[ + "Npgsql.EntityFrameworkCore.PostgreSQL:" + + nameof(NpgsqlDbContextOptionsBuilder.MapEnum) + + ":" + + enumDefinition.ClrType.Name] + = enumDefinition.GetHashCode().ToString(CultureInfo.InvariantCulture); + } + foreach (var rangeDefinition in Extension._userRangeDefinitions) { debugInfo[ @@ -496,50 +542,3 @@ public override void PopulateDebugInfo(IDictionary debugInfo) #endregion Infrastructure } - -/// -/// A definition for a user-defined PostgreSQL range to be mapped. -/// -public record UserRangeDefinition -{ - /// - /// The name of the PostgreSQL range type to be mapped. - /// - public virtual string RangeName { get; } - - /// - /// The PostgreSQL schema in which the range is defined. If null, the default schema is used - /// (which is public unless changed on the model). - /// - public virtual string? SchemaName { get; } - - /// - /// The CLR type of the range's subtype (or element). - /// The actual mapped type will be an over this type. - /// - public virtual Type SubtypeClrType { get; } - - /// - /// Optionally, the name of the range's PostgreSQL subtype (or element). - /// This is usually not needed - the subtype will be inferred based on . - /// - public virtual string? SubtypeName { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public UserRangeDefinition( - string rangeName, - string? schemaName, - Type subtypeClrType, - string? subtypeName) - { - RangeName = Check.NotEmpty(rangeName, nameof(rangeName)); - SchemaName = schemaName; - SubtypeClrType = Check.NotNull(subtypeClrType, nameof(subtypeClrType)); - SubtypeName = subtypeName; - } -} diff --git a/src/EFCore.PG/Infrastructure/Internal/UserRangeDefinition.cs b/src/EFCore.PG/Infrastructure/Internal/UserRangeDefinition.cs new file mode 100644 index 000000000..8c5908a28 --- /dev/null +++ b/src/EFCore.PG/Infrastructure/Internal/UserRangeDefinition.cs @@ -0,0 +1,74 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + +/// +/// A definition for a user-defined PostgreSQL range to be mapped. +/// +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public sealed record UserRangeDefinition +{ + /// + /// The name of the PostgreSQL range type to be mapped. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public string StoreTypeName { get; } + + /// + /// The PostgreSQL schema in which the range is defined. If null, the default schema is used + /// (which is public unless changed on the model). + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public string? StoreTypeSchema { get; } + + /// + /// The CLR type of the range's subtype (or element). + /// The actual mapped type will be an over this type. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public Type SubtypeClrType { get; } + + /// + /// Optionally, the name of the range's PostgreSQL subtype (or element). + /// This is usually not needed - the subtype will be inferred based on . + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public string? SubtypeName { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public UserRangeDefinition(string name, string? schema, Type subtypeClrType, string? subtypeName) + { + StoreTypeName = name; + StoreTypeSchema = schema; + SubtypeClrType = subtypeClrType; + SubtypeName = subtypeName; + } +} diff --git a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs index 4a43c55b0..b0a8e9921 100644 --- a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs +++ b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs @@ -95,6 +95,33 @@ public virtual NpgsqlDbContextOptionsBuilder MapRange( string? subtypeName = null) => WithOption(e => e.WithUserRangeDefinition(rangeName, schemaName, subtypeClrType, subtypeName)); + /// + /// Maps a PostgreSQL enum type for use. + /// + /// The name of the PostgreSQL enum type to be mapped. + /// The name of the PostgreSQL schema in which the range is defined. + /// The name translator used to map enum value names to PostgreSQL enum values. + public virtual NpgsqlDbContextOptionsBuilder MapEnum( + string enumName, + string? schemaName = null, + INpgsqlNameTranslator? nameTranslator = null) + where T : struct, Enum + => MapEnum(typeof(T), enumName, schemaName, nameTranslator); + + /// + /// Maps a PostgreSQL enum type for use. + /// + /// The CLR type of the enum. + /// The name of the PostgreSQL enum type to be mapped. + /// The name of the PostgreSQL schema in which the range is defined. + /// The name translator used to map enum value names to PostgreSQL enum values. + public virtual NpgsqlDbContextOptionsBuilder MapEnum( + Type clrType, + string enumName, + string? schemaName = null, + INpgsqlNameTranslator? nameTranslator = null) + => WithOption(e => e.WithEnumMapping(clrType, enumName, schemaName, nameTranslator)); + /// /// Appends NULLS FIRST to all ORDER BY clauses. This is important for the tests which were written /// for SQL Server. Note that to fully implement null-first ordering indexes also need to be generated diff --git a/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs b/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs index e0fa14260..46c9f9a7c 100644 --- a/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs +++ b/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs @@ -1,6 +1,7 @@ using System.Data.Common; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Internal; @@ -45,7 +46,7 @@ public class NpgsqlSingletonOptions : INpgsqlSingletonOptions /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual DbDataSource? DataSource { get; private set; } + public IReadOnlyList EnumDefinitions { get; private set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -55,14 +56,6 @@ public class NpgsqlSingletonOptions : INpgsqlSingletonOptions /// public virtual IReadOnlyList UserRangeDefinitions { get; private set; } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IServiceProvider? ApplicationServiceProvider { get; private set; } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -71,6 +64,7 @@ public class NpgsqlSingletonOptions : INpgsqlSingletonOptions /// public NpgsqlSingletonOptions() { + EnumDefinitions = []; UserRangeDefinitions = []; } @@ -78,20 +72,13 @@ public NpgsqlSingletonOptions() public virtual void Initialize(IDbContextOptions options) { var npgsqlOptions = options.FindExtension() ?? new NpgsqlOptionsExtension(); - var coreOptions = options.FindExtension() ?? new CoreOptionsExtension(); PostgresVersion = npgsqlOptions.PostgresVersion; IsPostgresVersionSet = npgsqlOptions.IsPostgresVersionSet; UseRedshift = npgsqlOptions.UseRedshift; ReverseNullOrderingEnabled = npgsqlOptions.ReverseNullOrdering; + EnumDefinitions = npgsqlOptions.EnumDefinitions; UserRangeDefinitions = npgsqlOptions.UserRangeDefinitions; - - // TODO: Remove after https://github.com/dotnet/efcore/pull/29950 - ApplicationServiceProvider = coreOptions.ApplicationServiceProvider; - - DataSource = npgsqlOptions.DataSource ?? (npgsqlOptions.ConnectionString is null && npgsqlOptions.Connection is null - ? coreOptions.ApplicationServiceProvider?.GetService() - : null); } /// @@ -123,10 +110,12 @@ public virtual void Validate(IDbContextOptions options) nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); } - if (npgsqlOptions.DataSource is not null && !ReferenceEquals(DataSource, npgsqlOptions.DataSource)) + if (!EnumDefinitions.SequenceEqual(npgsqlOptions.EnumDefinitions)) { throw new InvalidOperationException( - NpgsqlStrings.TwoDataSourcesInSameServiceProvider(nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); + CoreStrings.SingletonOptionChanged( + nameof(NpgsqlDbContextOptionsBuilder.MapEnum), + nameof(DbContextOptionsBuilder.UseInternalServiceProvider))); } if (!UserRangeDefinitions.SequenceEqual(npgsqlOptions.UserRangeDefinitions)) diff --git a/src/EFCore.PG/Metadata/Conventions/NpgsqlConventionSetBuilder.cs b/src/EFCore.PG/Metadata/Conventions/NpgsqlConventionSetBuilder.cs index 8b9098fbd..6b296e878 100644 --- a/src/EFCore.PG/Metadata/Conventions/NpgsqlConventionSetBuilder.cs +++ b/src/EFCore.PG/Metadata/Conventions/NpgsqlConventionSetBuilder.cs @@ -1,4 +1,5 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Conventions; @@ -19,6 +20,7 @@ public class NpgsqlConventionSetBuilder : RelationalConventionSetBuilder { private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly Version _postgresVersion; + private readonly IReadOnlyList _enumDefinitions; /// /// Creates a new instance. @@ -36,6 +38,7 @@ public NpgsqlConventionSetBuilder( { _typeMappingSource = typeMappingSource; _postgresVersion = npgsqlSingletonOptions.PostgresVersion; + _enumDefinitions = npgsqlSingletonOptions.EnumDefinitions; } /// @@ -70,7 +73,7 @@ public override ConventionSet CreateConventionSet() conventionSet.PropertyAnnotationChangedConventions, (RelationalValueGenerationConvention)valueGenerationConvention); conventionSet.ModelFinalizingConventions.Add(valueGenerationStrategyConvention); - conventionSet.ModelFinalizingConventions.Add(new NpgsqlPostgresModelFinalizingConvention(_typeMappingSource)); + conventionSet.ModelFinalizingConventions.Add(new NpgsqlPostgresModelFinalizingConvention(_typeMappingSource, _enumDefinitions)); ReplaceConvention(conventionSet.ModelFinalizingConventions, storeGenerationConvention); ReplaceConvention( conventionSet.ModelFinalizingConventions, diff --git a/src/EFCore.PG/Metadata/Conventions/NpgsqlPostgresModelFinalizingConvention.cs b/src/EFCore.PG/Metadata/Conventions/NpgsqlPostgresModelFinalizingConvention.cs index b7ed9e0ae..6298c6dca 100644 --- a/src/EFCore.PG/Metadata/Conventions/NpgsqlPostgresModelFinalizingConvention.cs +++ b/src/EFCore.PG/Metadata/Conventions/NpgsqlPostgresModelFinalizingConvention.cs @@ -1,3 +1,5 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Conventions; /// @@ -9,14 +11,19 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Conventions; public class NpgsqlPostgresModelFinalizingConvention : IModelFinalizingConvention { private readonly IRelationalTypeMappingSource _typeMappingSource; + private readonly IReadOnlyList _enumDefinitions; /// /// Creates a new instance of . /// /// The type mapping source to use. - public NpgsqlPostgresModelFinalizingConvention(IRelationalTypeMappingSource typeMappingSource) + /// + public NpgsqlPostgresModelFinalizingConvention( + IRelationalTypeMappingSource typeMappingSource, + IReadOnlyList enumDefinitions) { _typeMappingSource = typeMappingSource; + _enumDefinitions = enumDefinitions; } /// @@ -36,6 +43,22 @@ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, } } } + + SetupEnums(modelBuilder); + } + + /// + /// Configures the model to create PostgreSQL enums based on the user's enum definitions in the context options. + /// + protected virtual void SetupEnums(IConventionModelBuilder modelBuilder) + { + foreach (var enumDefinition in _enumDefinitions) + { + modelBuilder.HasPostgresEnum( + enumDefinition.StoreTypeSchema, + enumDefinition.StoreTypeName, + enumDefinition.Labels.Values.Order(StringComparer.Ordinal).ToArray()); + } } /// @@ -46,6 +69,7 @@ protected virtual void DiscoverPostgresExtensions( RelationalTypeMapping typeMapping, IConventionModelBuilder modelBuilder) { + // TODO: does not work if CREATE EXTENSION was done on a non-default schema. #3177 switch (typeMapping.StoreType) { case "hstore": diff --git a/src/EFCore.PG/Metadata/PostgresEnum.cs b/src/EFCore.PG/Metadata/PostgresEnum.cs index f31cd651a..0d373c0cc 100644 --- a/src/EFCore.PG/Metadata/PostgresEnum.cs +++ b/src/EFCore.PG/Metadata/PostgresEnum.cs @@ -70,6 +70,49 @@ public static PostgresEnum GetOrAddPostgresEnum( return new PostgresEnum(annotatable, annotationName) { Labels = labels }; } + /// + /// Gets or adds a from or to the . + /// + /// The annotatable from which to get or add the enum. + /// The enum schema or null to use the model's default schema. + /// The enum name. + /// The enum labels. + /// + /// The from the . + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static PostgresEnum GetOrAddPostgresEnum( + IConventionAnnotatable annotatable, + string? schema, + string name, + string[] labels) + { + Check.NotNull(annotatable, nameof(annotatable)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotEmpty(name, nameof(name)); + Check.NotNull(labels, nameof(labels)); + + if (FindPostgresEnum(annotatable, schema, name) is { } enumType) + { + return enumType; + } + + var annotationName = BuildAnnotationName(schema, name); + + return new PostgresEnum(annotatable, annotationName) { Labels = labels }; + } + /// /// Gets or adds a from or to the . /// diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 03fc496de..f2f1e6405 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -1262,9 +1262,9 @@ protected virtual void GenerateEnumStatements(AlterDatabaseOperation operation, GenerateDropEnum(enumTypeToDrop, model, builder); } - foreach (var (newEnum, oldEnum) in operation.GetPostgresEnums() + foreach (var (newEnum, oldEnum) in operation.GetPostgresEnums().OrderBy(e => e.Schema).ThenBy(e => e.Name) .Join( - operation.GetOldPostgresEnums(), + operation.GetOldPostgresEnums().OrderBy(e => e.Schema).ThenBy(e => e.Name), e => new { e.Name, e.Schema }, e => new { e.Name, e.Schema }, (ne, oe) => (New: ne, Old: oe))) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs index 4cf7a59de..27a02bb17 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs @@ -438,7 +438,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I new[] { arguments[1], arguments[2] }, nullable: true, argumentsPropagateNullability: new[] { true, true }, - typeof(DateOnly?), + typeof(DateOnly), _typeMappingSource.FindMapping(typeof(DateOnly)) ); } @@ -450,7 +450,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I new[] { arguments[1], arguments[2] }, nullable: true, argumentsPropagateNullability: new[] { true, true }, - typeof(DateTime?), + typeof(DateTime), _typeMappingSource.FindMapping(typeof(DateTime)) ); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index b986d9122..ef0e1aa0d 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -234,7 +234,25 @@ public override DbParameter CreateParameter( } } - return base.CreateParameter(command, name, value, nullable, direction); + var param = base.CreateParameter(command, name, value, nullable, direction); + if (param is not NpgsqlParameter npgsqlParameter) + { + throw new InvalidOperationException( + $"Npgsql-specific type mapping {GetType().Name} being used with non-Npgsql parameter type {param.GetType().Name}"); + } + + // Enums and user-defined ranges require setting NpgsqlParameter.DataTypeName to specify the PostgreSQL type name. + // Make this work for arrays over these types as well. + switch (ElementTypeMapping) + { + case NpgsqlEnumTypeMapping enumTypeMapping: + npgsqlParameter.DataTypeName = enumTypeMapping.UnquotedStoreType + "[]"; + break; + case NpgsqlRangeTypeMapping { UnquotedStoreType: string unquotedStoreType }: + npgsqlParameter.DataTypeName = unquotedStoreType + "[]"; + break; + } + return param; } /// diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs index 94f1ead19..faf8cc37b 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Data.Common; +using System.Text.Json; using Microsoft.EntityFrameworkCore.Storage.Json; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; @@ -12,17 +13,26 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; public class NpgsqlEnumTypeMapping : RelationalTypeMapping { /// - /// Translates the CLR member value to the PostgreSQL value label. + /// Maps the CLR member values to the PostgreSQL value labels. /// - private readonly Dictionary _members; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyDictionary Labels { get; } /// + /// The unquoted store type, used for setting on . + /// + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static NpgsqlEnumTypeMapping Default { get; } = new(); + /// + public virtual string UnquotedStoreType { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -30,7 +40,7 @@ public class NpgsqlEnumTypeMapping : RelationalTypeMapping /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual INpgsqlNameTranslator NameTranslator { get; } + public static NpgsqlEnumTypeMapping Default { get; } = new(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -38,9 +48,13 @@ public class NpgsqlEnumTypeMapping : RelationalTypeMapping /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public NpgsqlEnumTypeMapping(string storeType, Type enumType, INpgsqlNameTranslator? nameTranslator = null) + public NpgsqlEnumTypeMapping( + string quotedStoreType, + string unquotedStoreType, + Type enumType, + IReadOnlyDictionary labels) : base( - storeType, + quotedStoreType, enumType, jsonValueReaderWriter: (JsonValueReaderWriter?)Activator.CreateInstance( typeof(JsonPgEnumReaderWriter<>).MakeGenericType(enumType))) @@ -50,12 +64,8 @@ public NpgsqlEnumTypeMapping(string storeType, Type enumType, INpgsqlNameTransla throw new ArgumentException($"Enum type mappings require a CLR enum. {enumType.FullName} is not an enum."); } -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - nameTranslator ??= NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator; -#pragma warning restore CS0618 - - NameTranslator = nameTranslator; - _members = CreateValueMapping(enumType, nameTranslator); + UnquotedStoreType = unquotedStoreType; + Labels = labels; } /// @@ -66,11 +76,12 @@ public NpgsqlEnumTypeMapping(string storeType, Type enumType, INpgsqlNameTransla /// protected NpgsqlEnumTypeMapping( RelationalTypeMappingParameters parameters, - INpgsqlNameTranslator nameTranslator) + string unquotedStoreType, + IReadOnlyDictionary labels) : base(parameters) { - NameTranslator = nameTranslator; - _members = CreateValueMapping(parameters.CoreParameters.ClrType, nameTranslator); + UnquotedStoreType = unquotedStoreType; + Labels = labels; } // This constructor exists only to support the static Default property above, which is necessary to allow code generation for compiled @@ -78,10 +89,8 @@ protected NpgsqlEnumTypeMapping( private NpgsqlEnumTypeMapping() : base("some_enum", typeof(int)) { -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - NameTranslator = NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator; -#pragma warning restore CS0618 - _members = null!; + UnquotedStoreType = "some_enum"; + Labels = new Dictionary(); } /// @@ -91,7 +100,19 @@ private NpgsqlEnumTypeMapping() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) - => new NpgsqlEnumTypeMapping(parameters, NameTranslator); + => new NpgsqlEnumTypeMapping(parameters, UnquotedStoreType, Labels); + + /// + /// This method exists only to support the compiled model. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual NpgsqlEnumTypeMapping Clone(string unquotedStoreType, IReadOnlyDictionary labels) + => new(Parameters, unquotedStoreType, labels); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -99,8 +120,16 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p /// 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 string GenerateNonNullSqlLiteral(object value) - => $"'{_members[value]}'::{StoreType}"; + protected override void ConfigureParameter(DbParameter parameter) + { + if (parameter is not NpgsqlParameter npgsqlParameter) + { + throw new InvalidOperationException( + $"Npgsql-specific type mapping {GetType().Name} being used with non-Npgsql parameter type {parameter.GetType().Name}"); + } + + npgsqlParameter.DataTypeName = UnquotedStoreType; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -108,11 +137,8 @@ protected override string GenerateNonNullSqlLiteral(object value) /// 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. /// - private static Dictionary CreateValueMapping(Type enumType, INpgsqlNameTranslator nameTranslator) - => enumType.GetFields(BindingFlags.Static | BindingFlags.Public) - .ToDictionary( - x => x.GetValue(null)!, - x => x.GetCustomAttribute()?.PgName ?? nameTranslator.TranslateMemberName(x.Name)); + protected override string GenerateNonNullSqlLiteral(object value) + => $"'{Labels[value]}'::{StoreType}"; // This is public for the compiled model /// diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs index d8e5d62ae..e738b8170 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs @@ -42,7 +42,7 @@ public class NpgsqlRangeTypeMapping : NpgsqlTypeMapping /// For user-defined ranges, we have no and so the PG type name is set on /// instead. /// - private string? PgDataTypeName { get; init; } + public virtual string? UnquotedStoreType { get; init; } /// /// Constructs an instance of the class for a built-in range type which has a @@ -74,7 +74,7 @@ public static NpgsqlRangeTypeMapping CreatUserDefinedRangeMapping( RelationalTypeMapping subtypeMapping) => new(quotedRangeStoreType, rangeClrType, rangeNpgsqlDbType: NpgsqlDbType.Unknown, subtypeMapping) { - PgDataTypeName = unquotedRangeStoreType + UnquotedStoreType = unquotedRangeStoreType }; private NpgsqlRangeTypeMapping( @@ -139,7 +139,7 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p protected override void ConfigureParameter(DbParameter parameter) { // Built-in range types have an NpgsqlDbType, so we just do the normal thing. - if (PgDataTypeName is null) + if (UnquotedStoreType is null) { Check.DebugAssert(NpgsqlDbType is not NpgsqlDbType.Unknown, "NpgsqlDbType is Unknown but no PgDataTypeName is configured"); base.ConfigureParameter(parameter); @@ -154,7 +154,7 @@ protected override void ConfigureParameter(DbParameter parameter) $"Npgsql-specific type mapping {GetType().Name} being used with non-Npgsql parameter type {parameter.GetType().Name}"); } - npgsqlParameter.DataTypeName = PgDataTypeName; + npgsqlParameter.DataTypeName = UnquotedStoreType; } /// diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs b/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs new file mode 100644 index 000000000..9859e2cd0 --- /dev/null +++ b/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs @@ -0,0 +1,178 @@ +using System.Collections.Concurrent; +using System.Data.Common; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; + +/// +/// Manages resolving and creating instances. +/// +/// +/// +/// 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. +/// +/// +/// The service lifetime is . This means a single instance +/// is used by many instances. The implementation must be thread-safe. +/// This service cannot depend on services registered as . +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +/// +public class NpgsqlDataSourceManager : IDisposable, IAsyncDisposable +{ + private readonly IEnumerable _plugins; + private readonly ConcurrentDictionary _dataSources = new(); + private volatile int _isDisposed; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public NpgsqlDataSourceManager(IEnumerable plugins) + => _plugins = plugins.ToArray(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DbDataSource? GetDataSource(NpgsqlOptionsExtension? npgsqlOptionsExtension, IServiceProvider? applicationServiceProvider) + => npgsqlOptionsExtension switch + { + // If the user has explicitly passed in a data source via UseNpgsql(), use that. + // Note that in this case, the data source is scoped (not singleton), and so can change between different + // DbContext instances using the same internal service provider. + { DataSource: DbDataSource dataSource } => dataSource, + + // If the user has passed in a DbConnection, never use a data source - even if e.g. MapEnum() was called. + // This is to avoid blocking and allow continuing using enums in conjunction with DbConnections (which + // must be manually set up by the user for the enum, of course). + { Connection: not null } => null, + + // If the user hasn't configured anything in UseNpgsql (no data source, no connection, no connection string), check the + // application service provider to see if a data source is registered there, and return that. + { ConnectionString: null } when applicationServiceProvider?.GetService() is DbDataSource dataSource + => dataSource, + + // Otherwise if there's no connection string, abort: a connection string is required to create a data source in any case. + { ConnectionString: null } or null => null, + + // The following are features which require an NpgsqlDataSource, since they require configuration on NpgsqlDataSourceBuilder. + { EnumDefinitions.Count: > 0 } => GetSingletonDataSource(npgsqlOptionsExtension), + _ when _plugins.Any() => GetSingletonDataSource(npgsqlOptionsExtension), + + // If there's no configured feature which requires us to use a data source internally, don't use one; this causes + // NpgsqlRelationalConnection to use the connection string as before (no data source), allowing switching connection strings + // with the same service provider etc. + _ => null + }; + + private DbDataSource GetSingletonDataSource(NpgsqlOptionsExtension npgsqlOptionsExtension) + { + var connectionString = npgsqlOptionsExtension.ConnectionString; + Check.DebugAssert(connectionString is not null, "Connection string can't be null"); + + if (_dataSources.TryGetValue(connectionString, out var dataSource)) + { + return dataSource; + } + + var newDataSource = CreateDataSource(npgsqlOptionsExtension); + + var addedDataSource = _dataSources.GetOrAdd(connectionString, newDataSource); + if (!ReferenceEquals(addedDataSource, newDataSource)) + { + newDataSource.Dispose(); + } + else if (_isDisposed == 1) + { + newDataSource.Dispose(); + throw new ObjectDisposedException(nameof(NpgsqlDataSourceManager)); + } + + return addedDataSource; + } + + /// + /// 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 virtual NpgsqlDataSource CreateDataSource(NpgsqlOptionsExtension npgsqlOptionsExtension) + { + var dataSourceBuilder = new NpgsqlDataSourceBuilder(npgsqlOptionsExtension.ConnectionString); + + foreach (var enumDefinition in npgsqlOptionsExtension.EnumDefinitions) + { + dataSourceBuilder.MapEnum( + enumDefinition.ClrType, + enumDefinition.StoreTypeSchema is null + ? enumDefinition.StoreTypeName + : enumDefinition.StoreTypeSchema + "." + enumDefinition.StoreTypeName, + enumDefinition.NameTranslator); + } + + foreach (var plugin in _plugins) + { + plugin.Configure(dataSourceBuilder); + } + + // Legacy authentication-related callbacks at the EF level; apply these when building a data source as well. + if (npgsqlOptionsExtension.ProvideClientCertificatesCallback is not null) + { + dataSourceBuilder.UseClientCertificatesCallback(x => npgsqlOptionsExtension.ProvideClientCertificatesCallback(x)); + } + + if (npgsqlOptionsExtension.RemoteCertificateValidationCallback is not null) + { + dataSourceBuilder.UseUserCertificateValidationCallback(npgsqlOptionsExtension.RemoteCertificateValidationCallback); + } + + return dataSourceBuilder.Build(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public void Dispose() + { + if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) + { + foreach (var dataSource in _dataSources.Values) + { + dataSource.Dispose(); + } + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public async ValueTask DisposeAsync() + { + if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) + { + foreach (var dataSource in _dataSources.Values) + { + await dataSource.DisposeAsync().ConfigureAwait(false); + } + } + } +} diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs index 77b3f81e2..d4363b4d7 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs @@ -35,8 +35,15 @@ protected override bool SupportsAmbientTransactions /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public NpgsqlRelationalConnection(RelationalConnectionDependencies dependencies, INpgsqlSingletonOptions options) - : this(dependencies, options.DataSource) + public NpgsqlRelationalConnection( + RelationalConnectionDependencies dependencies, + NpgsqlDataSourceManager dataSourceManager, + IDbContextOptions options) + : this( + dependencies, + dataSourceManager.GetDataSource( + options.FindExtension(), + options.FindExtension()?.ApplicationServiceProvider)) { } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 5c03e6868..cff84bdb2 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -57,6 +57,7 @@ static NpgsqlTypeMappingSource() /// protected virtual ConcurrentDictionary ClrTypeMappings { get; } + private readonly IReadOnlyList _enumDefinitions; private readonly IReadOnlyList _userRangeDefinitions; private readonly bool _supportsMultiranges; @@ -348,68 +349,10 @@ public NpgsqlTypeMappingSource( StoreTypeMappings = new ConcurrentDictionary(storeTypeMappings, StringComparer.OrdinalIgnoreCase); ClrTypeMappings = new ConcurrentDictionary(clrTypeMappings); - LoadUserDefinedTypeMappings(sqlGenerationHelper, options.DataSource as NpgsqlDataSource); - + _enumDefinitions = options.EnumDefinitions; _userRangeDefinitions = options.UserRangeDefinitions; } - /// - /// To be used in case user-defined mappings are added late, after this TypeMappingSource has already been initialized. - /// This is basically only for test usage. - /// - public virtual void LoadUserDefinedTypeMappings( - ISqlGenerationHelper sqlGenerationHelper, - NpgsqlDataSource? dataSource) - => SetupEnumMappings(sqlGenerationHelper, dataSource); - -#pragma warning disable NPG9001 - /// - /// Gets all global enum mappings from the ADO.NET layer and creates mappings for them - /// - protected virtual void SetupEnumMappings(ISqlGenerationHelper sqlGenerationHelper, NpgsqlDataSource? dataSource) - { - List? adoEnumMappings = null; - - if (dataSource is not null - && typeof(NpgsqlDataSource).GetField("_hackyEnumTypeMappings", BindingFlags.NonPublic | BindingFlags.Instance) is - { } dataSourceTypeMappingsFieldInfo - && dataSourceTypeMappingsFieldInfo.GetValue(dataSource) is List dataSourceEnumMappings) - { - // Note that the data source's enum mappings also include any global ones that were configured when the data source was created. - // So we don't need to also collect mappings from GlobalTypeMapper below. - adoEnumMappings = dataSourceEnumMappings; - } -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - else if (NpgsqlConnection.GlobalTypeMapper.GetType().GetProperty( - "HackyEnumTypeMappings", BindingFlags.NonPublic | BindingFlags.Instance) - is PropertyInfo globalEnumTypeMappingsProperty - && globalEnumTypeMappingsProperty.GetValue(NpgsqlConnection.GlobalTypeMapper) is List - globalEnumMappings) - { - adoEnumMappings = globalEnumMappings; - } -#pragma warning restore CS0618 - - if (adoEnumMappings is not null) - { - foreach (var adoEnumMapping in adoEnumMappings) - { - // TODO: update with schema per https://github.com/npgsql/npgsql/issues/2121 - var components = adoEnumMapping.PgTypeName.Split('.'); - var schema = components.Length > 1 ? components.First() : null; - var name = components.Length > 1 ? string.Join(null, components.Skip(1)) : adoEnumMapping.PgTypeName; - - var mapping = new NpgsqlEnumTypeMapping( - sqlGenerationHelper.DelimitIdentifier(name, schema), - adoEnumMapping.EnumClrType, - adoEnumMapping.NameTranslator); - ClrTypeMappings[adoEnumMapping.EnumClrType] = mapping; - StoreTypeMappings[mapping.StoreType] = [mapping]; - } - } - } -#pragma warning restore NPG9001 - /// /// 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 @@ -420,6 +363,7 @@ is PropertyInfo globalEnumTypeMappingsProperty // First, try any plugins, allowing them to override built-in mappings (e.g. NodaTime) => base.FindMapping(mappingInfo) ?? FindBaseMapping(mappingInfo)?.Clone(mappingInfo) + ?? FindEnumMapping(mappingInfo) ?? FindRowValueMapping(mappingInfo)?.Clone(mappingInfo) ?? FindUserRangeMapping(mappingInfo); @@ -816,6 +760,58 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) ? new NpgsqlRowValueTypeMapping(clrType) : null; + /// + /// 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 virtual RelationalTypeMapping? FindEnumMapping(in RelationalTypeMappingInfo mappingInfo) + { + var storeType = mappingInfo.StoreTypeName; + var clrType = mappingInfo.ClrType; + + if (clrType is not null and not { IsEnum: true, IsClass: false }) + { + return null; + } + + // Try to find an enum definition (defined by the user on their context options), based on the + // incoming MappingInfo's StoreType or ClrType + EnumDefinition? enumDefinition; + if (storeType is null) + { + enumDefinition = _enumDefinitions.SingleOrDefault(m => m.ClrType == clrType); + } + else + { + // TODO: Not sure what to do about quoting. Is the user expected to configure properties + // TODO: with a quoted (schema-qualified) store type or not? + var dot = storeType.IndexOf('.'); + enumDefinition = dot is -1 + ? _enumDefinitions.SingleOrDefault(m => m.StoreTypeName == storeType) + : _enumDefinitions.SingleOrDefault(m => m.StoreTypeName == storeType[(dot + 1)..] && m.StoreTypeSchema == storeType[..dot]); + } + + if (enumDefinition is null) + { + return null; + } + + // We now have an enum definition from the context options. + + // We need the following store type names: + // 1. The quoted type name is used in migrations, where quoting is needed + // 2. The unquoted type name is set on NpgsqlParameter.DataTypeName + // (though see https://github.com/npgsql/npgsql/issues/5710). + var (name, schema) = (enumDefinition.StoreTypeName, enumDefinition.StoreTypeSchema); + return new NpgsqlEnumTypeMapping( + _sqlGenerationHelper.DelimitIdentifier(name, schema), + schema is null ? name : schema + "." + name, + enumDefinition.ClrType, + enumDefinition.Labels); + } + /// /// 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 @@ -838,7 +834,7 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) // incoming MappingInfo's StoreType or ClrType if (rangeStoreType is not null) { - rangeDefinition = _userRangeDefinitions.SingleOrDefault(m => m.RangeName == rangeStoreType); + rangeDefinition = _userRangeDefinitions.SingleOrDefault(m => m.StoreTypeName == rangeStoreType); if (rangeDefinition is null) { @@ -875,16 +871,16 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) if (subtypeMapping is null) { - throw new Exception($"Could not map range {rangeDefinition.RangeName}, no mapping was found its subtype"); + throw new Exception($"Could not map range {rangeDefinition.StoreTypeName}, no mapping was found its subtype"); } // We need to store types for the user-defined range: // 1. The quoted type name is used in migrations, where quoting is needed // 2. The unquoted type name is set on NpgsqlParameter.DataTypeName - var quotedRangeStoreType = _sqlGenerationHelper.DelimitIdentifier(rangeDefinition.RangeName, rangeDefinition.SchemaName); - var unquotedRangeStoreType = rangeDefinition.SchemaName is null - ? rangeDefinition.RangeName - : rangeDefinition.SchemaName + '.' + rangeDefinition.RangeName; + var quotedRangeStoreType = _sqlGenerationHelper.DelimitIdentifier(rangeDefinition.StoreTypeName, rangeDefinition.StoreTypeSchema); + var unquotedRangeStoreType = rangeDefinition.StoreTypeSchema is null + ? rangeDefinition.StoreTypeName + : rangeDefinition.StoreTypeSchema + '.' + rangeDefinition.StoreTypeName; return NpgsqlRangeTypeMapping.CreatUserDefinedRangeMapping( quotedRangeStoreType, unquotedRangeStoreType, rangeClrType, subtypeMapping); diff --git a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs index 4d372c8e1..69125d02e 100644 --- a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs @@ -962,8 +962,10 @@ public override bool SupportsDecimalComparisons public override bool PreservesDateTimeKind => false; + // We instruct the test store to pass a connection string to UseNpgsql() instead of a DbConnection - that's required to allow + // EF's MapEnum() to function properly and instantiate an NpgsqlDataSource internally. protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; + => new NpgsqlTestStoreFactory(useConnectionString: true); protected override bool ShouldLogCategory(string logCategory) => logCategory == DbLoggerCategory.Query.Name; @@ -971,23 +973,13 @@ protected override bool ShouldLogCategory(string logCategory) public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ServiceProvider.GetRequiredService(); - static BuiltInDataTypesNpgsqlFixture() - { -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - NpgsqlConnection.GlobalTypeMapper.MapEnum(); -#pragma warning restore CS0618 - } + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).UseNpgsql(o => o.MapEnum("mood")); protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); - // TODO: Switch to using data source - ((NpgsqlTypeMappingSource)context.GetService()).LoadUserDefinedTypeMappings( - context.GetService(), dataSource: null); - - modelBuilder.HasPostgresEnum("mood", ["happy", "sad"]); - MakeRequired(modelBuilder); // We default to mapping DateTime to 'timestamp with time zone', but the seeding data has Unspecified DateTimes which aren't diff --git a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj index 421d004c8..ce5bd43a8 100644 --- a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj +++ b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj @@ -9,6 +9,7 @@ + diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index c02e580e1..593c41c26 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -461,16 +461,11 @@ protected override IServiceCollection AddServices(IServiceCollection serviceColl protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) { - new NpgsqlDbContextOptionsBuilder(builder).UseNetTopologySuite(); - return builder; - } - - static JsonTypesNpgsqlTest() - { -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete // Note that the enum doesn't actually need to be created in the database, since Can_read_and_write_JSON_value doesn't access // the database. We just need the mapping to be picked up by EFCore.PG from the ADO.NET layer. - NpgsqlConnection.GlobalTypeMapper.MapEnum("test.mapped_enum"); -#pragma warning restore CS0618 + new NpgsqlDbContextOptionsBuilder(builder) + .MapEnum("mapped_enum", "test") + .UseNetTopologySuite(); + return builder; } } diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index 479506646..f73103648 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -1,4 +1,5 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; @@ -223,12 +224,7 @@ public class EnumContext(DbContextOptions options) : PoolableDbContext(options) public DbSet SomeEntities { get; set; } protected override void OnModelCreating(ModelBuilder builder) - => builder - .HasPostgresEnum("mapped_enum", ["happy", "sad"]) - .HasPostgresEnum() - .HasPostgresEnum() - .HasDefaultSchema("test") - .HasPostgresEnum(); + => builder.HasDefaultSchema("test"); public static void Seed(EnumContext context) { @@ -292,20 +288,25 @@ public class EnumFixture : SharedStoreFixtureBase, IQueryFixtureBas protected override string StoreName => "EnumQueryTest"; + // We instruct the test store to pass a connection string to UseNpgsql() instead of a DbConnection - that's required to allow + // EF's UseNodaTime() to function properly and instantiate an NpgsqlDataSource internally. protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; + => new NpgsqlTestStoreFactory(useConnectionString: true); public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - static EnumFixture() + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) { -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - NpgsqlConnection.GlobalTypeMapper.MapEnum("test.mapped_enum"); - NpgsqlConnection.GlobalTypeMapper.MapEnum("test.inferred_enum"); - NpgsqlConnection.GlobalTypeMapper.MapEnum("test.byte_enum"); - NpgsqlConnection.GlobalTypeMapper.MapEnum("test.schema_qualified_enum"); -#pragma warning restore CS0618 + var optionsBuilder = base.AddOptions(builder); + + new NpgsqlDbContextOptionsBuilder(optionsBuilder) + .MapEnum("mapped_enum", "test") + .MapEnum("inferred_enum", "test") + .MapEnum("byte_enum", "test") + .MapEnum("schema_qualified_enum", "test"); + + return optionsBuilder; } private EnumData _expectedData; diff --git a/test/EFCore.PG.FunctionalTests/Query/LegacyNpgsqlNodaTimeTypeMappingTest.cs b/test/EFCore.PG.FunctionalTests/Query/LegacyNpgsqlNodaTimeTypeMappingTest.cs new file mode 100644 index 000000000..1d1d41641 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/LegacyNpgsqlNodaTimeTypeMappingTest.cs @@ -0,0 +1,105 @@ +using Microsoft.EntityFrameworkCore.Storage.Json; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; + +#if DEBUG + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query +{ + [Collection("LegacyNodaTimeTest")] + public class LegacyNpgsqlNodaTimeTypeMappingTest + : IClassFixture + { + [Fact] + public void Timestamp_maps_to_Instant_by_default() + => Assert.Same(typeof(Instant), GetMapping("timestamp without time zone").ClrType); + + [Fact] + public void Timestamptz_maps_to_Instant_by_default() + => Assert.Same(typeof(Instant), GetMapping("timestamp with time zone").ClrType); + + [Fact] + public void LocalDateTime_does_not_map_to_timestamptz() + => Assert.Null(GetMapping(typeof(LocalDateTime), "timestamp with time zone")); + + [Fact] + public void GenerateSqlLiteral_returns_instant_literal() + { + var mapping = GetMapping(typeof(Instant)); + Assert.Equal("timestamp without time zone", mapping.StoreType); + + var instant = (new LocalDateTime(2018, 4, 20, 10, 31, 33, 666) + Period.FromTicks(6660)).InUtc().ToInstant(); + Assert.Equal("TIMESTAMP '2018-04-20T10:31:33.666666Z'", mapping.GenerateSqlLiteral(instant)); + } + + [Fact] + public void GenerateSqlLiteral_returns_instant_infinity_literal() + { + var mapping = GetMapping(typeof(Instant)); + Assert.Equal(typeof(Instant), mapping.ClrType); + Assert.Equal("timestamp without time zone", mapping.StoreType); + + Assert.Equal("TIMESTAMP '-infinity'", mapping.GenerateSqlLiteral(Instant.MinValue)); + Assert.Equal("TIMESTAMP 'infinity'", mapping.GenerateSqlLiteral(Instant.MaxValue)); + } + + [Fact] + public void GenerateSqlLiteral_returns_instant_range_in_legacy_mode() + { + var mapping = (NpgsqlRangeTypeMapping)GetMapping(typeof(NpgsqlRange)); + Assert.Equal("tsrange", mapping.StoreType); + Assert.Equal("timestamp without time zone", mapping.SubtypeMapping.StoreType); + + var value = new NpgsqlRange( + new LocalDateTime(2020, 1, 1, 12, 0, 0).InUtc().ToInstant(), + new LocalDateTime(2020, 1, 2, 12, 0, 0).InUtc().ToInstant()); + Assert.Equal(@"'[""2020-01-01T12:00:00Z"",""2020-01-02T12:00:00Z""]'::tsrange", mapping.GenerateSqlLiteral(value)); + } + + #region Support + + private static readonly NpgsqlTypeMappingSource Mapper = new( + new TypeMappingSourceDependencies( + new ValueConverterSelector(new ValueConverterSelectorDependencies()), + new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), + []), + new RelationalTypeMappingSourceDependencies( + new IRelationalTypeMappingSourcePlugin[] + { + new NpgsqlNodaTimeTypeMappingSourcePlugin( + new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies())) + }), + new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), + new NpgsqlSingletonOptions() + ); + + private static RelationalTypeMapping GetMapping(string storeType) + => Mapper.FindMapping(storeType); + + private static RelationalTypeMapping GetMapping(Type clrType) + => Mapper.FindMapping(clrType); + + private static RelationalTypeMapping GetMapping(Type clrType, string storeType) + => Mapper.FindMapping(clrType, storeType); + + private class LegacyNpgsqlNodaTimeTypeMappingFixture : IDisposable + { + public LegacyNpgsqlNodaTimeTypeMappingFixture() + { + NpgsqlNodaTimeTypeMappingSourcePlugin.LegacyTimestampBehavior = true; + } + + public void Dispose() + => NpgsqlNodaTimeTypeMappingSourcePlugin.LegacyTimestampBehavior = false; + } + + #endregion Support + } + + [CollectionDefinition("LegacyNodaTimeTest", DisableParallelization = true)] + public class EventSourceTestCollection; +} + +#endif diff --git a/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs index 02ec1ffab..13a4dcb34 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs @@ -144,7 +144,7 @@ protected override void Seed(TimestampQueryContext context) } [CollectionDefinition("LegacyTimestampQueryTest", DisableParallelization = true)] - public class EventSourceTestCollection; + public class NodaTimeEventSourceTestCollection; } #endif diff --git a/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs new file mode 100644 index 000000000..c6fa0961b --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs @@ -0,0 +1,2028 @@ +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class NodaTimeQueryNpgsqlTest : QueryTestBase +{ + public NodaTimeQueryNpgsqlTest(NodaTimeQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Operator(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate < new LocalDate(2018, 4, 21))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."LocalDate" < DATE '2018-04-21' +"""); + } + + #region Addition and subtraction + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Add_LocalDate_Period(bool async) + { + // Note: requires some special type inference logic because we're adding things of different types + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate + Period.FromMonths(1) > t.LocalDate)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."LocalDate" + INTERVAL 'P1M' > n."LocalDate" +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_Instant(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Instant + Duration.FromDays(1) - t.Instant == Duration.FromDays(1))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE (n."Instant" + INTERVAL '1 00:00:00') - n."Instant" = INTERVAL '1 00:00:00' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_LocalDateTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime + Period.FromDays(1) - t.LocalDateTime == Period.FromDays(1))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE (n."LocalDateTime" + INTERVAL 'P1D') - n."LocalDateTime" = INTERVAL 'P1D' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_ZonedDateTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime + Duration.FromDays(1) - t.ZonedDateTime == Duration.FromDays(1))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE (n."ZonedDateTime" + INTERVAL '1 00:00:00') - n."ZonedDateTime" = INTERVAL '1 00:00:00' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_LocalDate(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate2 - t.LocalDate == Period.FromDays(1))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE make_interval(days => n."LocalDate2" - n."LocalDate") = INTERVAL 'P1D' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_LocalDate_parameter(bool async) + { + var date = new LocalDate(2018, 4, 20); + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate2 - date == Period.FromDays(1))); + + AssertSql( + """ +@__date_0='Friday, 20 April 2018' (DbType = Date) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE make_interval(days => n."LocalDate2" - @__date_0) = INTERVAL 'P1D' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_LocalDate_constant(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate2 - new LocalDate(2018, 4, 20) == Period.FromDays(1))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE make_interval(days => n."LocalDate2" - DATE '2018-04-20') = INTERVAL 'P1D' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Subtract_LocalTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalTime + Period.FromHours(1) - t.LocalTime == Period.FromHours(1))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE (n."LocalTime" + INTERVAL 'PT1H') - n."LocalTime" = INTERVAL 'PT1H' +"""); + } + + #endregion + + #region LocalDateTime + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_Year(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.Year == 2018)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('year', n."LocalDateTime")::int = 2018 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_Month(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.Month == 4)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('month', n."LocalDateTime")::int = 4 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_DayOfYear(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.DayOfYear == 110)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('doy', n."LocalDateTime")::int = 110 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_Day(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.Day == 20)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('day', n."LocalDateTime")::int = 20 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_Hour(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.Hour == 10)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('hour', n."LocalDateTime")::int = 10 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_Minute(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.Minute == 31)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('minute', n."LocalDateTime")::int = 31 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_Second(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.Second == 33)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE floor(date_part('second', n."LocalDateTime"))::int = 33 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_Date(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.Date == new LocalDate(2018, 4, 20))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."LocalDateTime"::date = DATE '2018-04-20' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_Time(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.TimeOfDay == new LocalTime(10, 31, 33, 666))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."LocalDateTime"::time = TIME '10:31:33.666' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_DayOfWeek(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDateTime.DayOfWeek == IsoDayOfWeek.Friday)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE CASE floor(date_part('dow', n."LocalDateTime"))::int + WHEN 0 THEN 7 + ELSE floor(date_part('dow', n."LocalDateTime"))::int +END = 5 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_InZoneLeniently_ToInstant(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).ToInstant() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ +@__ToInstant_0='2018-04-20T08:31:33Z' (DbType = DateTime) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."LocalDateTime" AT TIME ZONE 'Europe/Berlin' = @__ToInstant_0 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDateTime_InZoneLeniently_ToInstant_with_column_time_zone(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb[t.TimeZoneId]).ToInstant() + == new ZonedDateTime( + new LocalDateTime(2018, 4, 20, 8, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ +@__ToInstant_0='2018-04-20T08:31:33Z' (DbType = DateTime) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."LocalDateTime" AT TIME ZONE n."TimeZoneId" = @__ToInstant_0 +"""); + } + + [ConditionalFact] + public async Task LocalDateTime_Distance() + { + await using var context = CreateContext(); + var closest = await context.NodaTimeTypes + .OrderBy(t => EF.Functions.Distance(t.LocalDateTime, new LocalDateTime(2018, 4, 1, 0, 0, 0))).FirstAsync(); + + Assert.Equal(1, closest.Id); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +ORDER BY n."LocalDateTime" <-> TIMESTAMP '2018-04-01T00:00:00' NULLS FIRST +LIMIT 1 +"""); + } + + #endregion LocalDateTime + + #region LocalDate + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDate_Year(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate.Year == 2018)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('year', n."LocalDate")::int = 2018 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDate_Month(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate.Month == 4)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('month', n."LocalDate")::int = 4 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDate_DayOrYear(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate.DayOfYear == 110)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('doy', n."LocalDate")::int = 110 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalDate_Day(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalDate.Day == 20)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('day', n."LocalDate")::int = 20 +"""); + } + + [ConditionalFact] + public async Task LocalDate_Distance() + { + await using var context = CreateContext(); + var closest = await context.NodaTimeTypes.OrderBy(t => EF.Functions.Distance(t.LocalDate, new LocalDate(2018, 4, 1))).FirstAsync(); + + Assert.Equal(1, closest.Id); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +ORDER BY n."LocalDate" <-> DATE '2018-04-01' NULLS FIRST +LIMIT 1 +"""); + } + + #endregion LocalDate + + #region LocalTime + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalTime_Hour(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalTime.Hour == 10)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('hour', n."LocalTime")::int = 10 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalTime_Minute(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalTime.Minute == 31)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('minute', n."LocalTime")::int = 31 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task LocalTime_Second(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.LocalTime.Second == 33)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE floor(date_part('second', n."LocalTime"))::int = 33 +"""); + } + + #endregion LocalTime + + #region Period + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_Years(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Period.Years == 2018)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('year', n."Period")::int = 2018 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_Months(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Period.Months == 4)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('month', n."Period")::int = 4 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_Days(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Period.Days == 20)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('day', n."Period")::int = 20 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_Hours(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Period.Hours == 10)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('hour', n."Period")::int = 10 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_Minutes(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Period.Minutes == 31)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('minute', n."Period")::int = 31 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_Seconds(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Period.Seconds == 23)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE floor(date_part('second', n."Period"))::int = 23 +"""); + } + + // PostgreSQL does not support extracting weeks from intervals + [ConditionalFact] + public Task Period_Weeks_is_not_translated() + { + using var ctx = CreateContext(); + + return AssertTranslationFailed( + () => ctx.Set().Where(t => t.Period.Weeks == 0).ToListAsync()); + } + + [ConditionalFact] + public Task Period_Milliseconds_is_not_translated() + { + using var ctx = CreateContext(); + + return AssertTranslationFailed( + () => ctx.Set().Where(t => t.Period.Nanoseconds == 0).ToListAsync()); + } + + [ConditionalFact] + public Task Period_Nanoseconds_is_not_translated() + { + using var ctx = CreateContext(); + + return AssertTranslationFailed( + () => ctx.Set().Where(t => t.Period.Nanoseconds == 0).ToListAsync()); + } + + [ConditionalFact] + public Task Period_Ticks_is_not_translated() + { + using var ctx = CreateContext(); + + return AssertTranslationFailed( + () => ctx.Set().Where(t => t.Period.Ticks == 0).ToListAsync()); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromYears(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromYears(t.Id).Years == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('year', make_interval(years => n."Id"))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromMonths(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromMonths(t.Id).Months == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('month', make_interval(months => n."Id"))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromWeeks(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromWeeks(t.Id).Days == 7), + ss => ss.Set().Where(t => Period.FromWeeks(t.Id).Normalize().Days == 7)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('day', make_interval(weeks => n."Id"))::int = 7 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromDays(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromDays(t.Id).Days == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('day', make_interval(days => n."Id"))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromHours_int(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromHours(t.Id).Hours == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('hour', make_interval(hours => n."Id"))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromHours_long(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromHours(t.Long).Hours == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('hour', make_interval(hours => n."Long"::int))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromMinutes_int(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromMinutes(t.Id).Minutes == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('minute', make_interval(mins => n."Id"))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromMinutes_long(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromMinutes(t.Long).Minutes == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('minute', make_interval(mins => n."Long"::int))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromSeconds_int(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromSeconds(t.Id).Seconds == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE floor(date_part('second', make_interval(secs => n."Id"::bigint::double precision)))::int = 1 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Period_FromSeconds_long(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => Period.FromSeconds(t.Long).Seconds == 1)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE floor(date_part('second', make_interval(secs => n."Long"::double precision)))::int = 1 +"""); + } + + [ConditionalFact] + public Task Period_FromMilliseconds_is_not_translated() + { + using var ctx = CreateContext(); + + return AssertTranslationFailed( + () => ctx.Set().Where(t => Period.FromMilliseconds(t.Id).Seconds == 1).ToListAsync()); + } + + [ConditionalFact] + public Task Period_FromNanoseconds_is_not_translated() + { + using var ctx = CreateContext(); + + return AssertTranslationFailed( + () => ctx.Set().Where(t => Period.FromNanoseconds(t.Id).Seconds == 1).ToListAsync()); + } + + [ConditionalFact] + public Task Period_FromTicks_is_not_translated() + { + using var ctx = CreateContext(); + + return AssertTranslationFailed( + () => ctx.Set().Where(t => Period.FromNanoseconds(t.Id).Seconds == 1).ToListAsync()); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task GroupBy_Property_Select_Sum_over_Period(bool async) + { + await using var ctx = CreateContext(); + + // Note: Unlike Duration, Period can't be converted to total ticks (because its absolute time varies). + var query = ctx.Set() + .GroupBy(o => o.Id) + .Select(g => EF.Functions.Sum(g.Select(o => o.Period))); + + _ = async + ? await query.ToListAsync() + : query.ToList(); + + AssertSql( + """ +SELECT sum(n."Period") +FROM "NodaTimeTypes" AS n +GROUP BY n."Id" +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task GroupBy_Property_Select_Average_over_Period(bool async) + { + await using var ctx = CreateContext(); + + // Note: Unlike Duration, Period can't be converted to total ticks (because its absolute time varies). + var query = ctx.Set() + .GroupBy(o => o.Id) + .Select(g => EF.Functions.Average(g.Select(o => o.Period))); + + _ = async + ? await query.ToListAsync() + : query.ToList(); + + AssertSql( + """ +SELECT avg(n."Period") +FROM "NodaTimeTypes" AS n +GROUP BY n."Id" +"""); + } + + #endregion Period + + #region Duration + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_TotalDays(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.TotalDays > 27)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('epoch', n."Duration") / 86400.0 > 27.0 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_TotalHours(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.TotalHours < 700)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('epoch', n."Duration") / 3600.0 < 700.0 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_TotalMinutes(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.TotalMinutes < 40000)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('epoch', n."Duration") / 60.0 < 40000.0 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_TotalSeconds(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.TotalSeconds == 2365448.02)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('epoch', n."Duration") = 2365448.02 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_TotalMilliseconds(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.TotalMilliseconds == 2365448020)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('epoch', n."Duration") / 0.001 = 2365448020.0 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_Days(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.Days == 27)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('day', n."Duration")::int = 27 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_Hours(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.Hours == 9)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('hour', n."Duration")::int = 9 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_Minutes(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.Minutes == 4)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('minute', n."Duration")::int = 4 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Duration_Seconds(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Duration.Seconds == 8)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE floor(date_part('second', n."Duration"))::int = 8 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task GroupBy_Property_Select_Sum_over_Duration(bool async) + { + await AssertQueryScalar( + async, + ss => ss.Set() + .GroupBy(o => o.Id) + .Select(g => EF.Functions.Sum(g.Select(o => o.Duration))), + expectedQuery: ss => ss.Set() + .GroupBy(o => o.Id) + .Select(g => (Duration?)Duration.FromTicks(g.Sum(o => o.Duration.TotalTicks)))); + + AssertSql( + """ +SELECT sum(n."Duration") +FROM "NodaTimeTypes" AS n +GROUP BY n."Id" +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task GroupBy_Property_Select_Average_over_Duration(bool async) + { + await AssertQueryScalar( + async, + ss => ss.Set() + .GroupBy(o => o.Id) + .Select(g => EF.Functions.Average(g.Select(o => o.Duration))), + expectedQuery: ss => ss.Set() + .GroupBy(o => o.Id) + .Select(g => (Duration?)Duration.FromTicks((long)g.Average(o => o.Duration.TotalTicks)))); + + AssertSql( + """ +SELECT avg(n."Duration") +FROM "NodaTimeTypes" AS n +GROUP BY n."Id" +"""); + } + + #endregion + + #region Interval + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Interval_Start(bool async) + { + await AssertQuery( + async, + ss => ss.Set() + .Where(t => t.Interval.Start == new LocalDateTime(2018, 4, 20, 10, 31, 33, 666).InUtc().ToInstant())); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE lower(n."Interval") = TIMESTAMPTZ '2018-04-20T10:31:33.666Z' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Interval_End(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Interval.End == new LocalDateTime(2018, 4, 25, 10, 31, 33, 666).InUtc().ToInstant())); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE upper(n."Interval") = TIMESTAMPTZ '2018-04-25T10:31:33.666Z' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Interval_HasStart(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Interval.HasStart)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE NOT (lower_inf(n."Interval")) +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Interval_HasEnd(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Interval.HasEnd)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE NOT (upper_inf(n."Interval")) +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Interval_Duration(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Interval.Duration == Duration.FromDays(5))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE upper(n."Interval") - lower(n."Interval") = INTERVAL '5 00:00:00' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Interval_Contains_Instant(bool async) + { + var interval = new Interval( + new LocalDateTime(2018, 01, 01, 0, 0, 0).InUtc().ToInstant(), + new LocalDateTime(2020, 12, 25, 0, 0, 0).InUtc().ToInstant()); + + await AssertQuery( + async, + ss => ss.Set().Where(t => interval.Contains(t.Instant))); + + AssertSql( + """ +@__interval_0='2018-01-01T00:00:00Z/2020-12-25T00:00:00Z' (DbType = Object) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE @__interval_0 @> n."Instant" +"""); + } + + [ConditionalTheory] + [MinimumPostgresVersion(14, 0)] // Multiranges were introduced in PostgreSQL 14 + [MemberData(nameof(IsAsyncData))] + public async Task Interval_RangeAgg(bool async) + { + await using var context = CreateContext(); + + var query = context.NodaTimeTypes + .GroupBy(x => true) + .Select(g => EF.Functions.RangeAgg(g.Select(x => x.Interval))); + + var union = async + ? await query.SingleAsync() + : query.Single(); + + var start = Instant.FromUtc(2018, 4, 20, 10, 31, 33).Plus(Duration.FromMilliseconds(666)); + Assert.Equal([new(start, start + Duration.FromDays(5))], union); + + AssertSql( + """ +SELECT range_agg(n0."Interval") +FROM ( + SELECT n."Interval", TRUE AS "Key" + FROM "NodaTimeTypes" AS n +) AS n0 +GROUP BY n0."Key" +LIMIT 2 +"""); + } + + [ConditionalTheory] + [MinimumPostgresVersion(14, 0)] // range_intersect_agg was introduced in PostgreSQL 14 + [MemberData(nameof(IsAsyncData))] + public async Task Interval_Intersect_aggregate(bool async) + { + await using var context = CreateContext(); + + var query = context.NodaTimeTypes + .GroupBy(x => true) + .Select(g => EF.Functions.RangeIntersectAgg(g.Select(x => x.Interval))); + + var intersection = async + ? await query.SingleAsync() + : query.Single(); + + var start = Instant.FromUtc(2018, 4, 20, 10, 31, 33).Plus(Duration.FromMilliseconds(666)); + Assert.Equal(new Interval(start, start + Duration.FromDays(5)), intersection); + + AssertSql( + """ +SELECT range_intersect_agg(n0."Interval") +FROM ( + SELECT n."Interval", TRUE AS "Key" + FROM "NodaTimeTypes" AS n +) AS n0 +GROUP BY n0."Key" +LIMIT 2 +"""); + } + + #endregion Interval + + #region DateInterval + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_Length(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.DateInterval.Length == 5)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE upper(n."DateInterval") - lower(n."DateInterval") = 5 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_Start(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.DateInterval.Start == new LocalDate(2018, 4, 20))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE lower(n."DateInterval") = DATE '2018-04-20' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_End(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.DateInterval.End == new LocalDate(2018, 4, 24))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE CAST(upper(n."DateInterval") - INTERVAL 'P1D' AS date) = DATE '2018-04-24' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_End_Select(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Select(t => t.DateInterval.End)); + + AssertSql( + """ +SELECT CAST(upper(n."DateInterval") - INTERVAL 'P1D' AS date) +FROM "NodaTimeTypes" AS n +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_Contains_LocalDate(bool async) + { + var dateInterval = new DateInterval(new LocalDate(2018, 01, 01), new LocalDate(2020, 12, 25)); + + await AssertQuery( + async, + ss => ss.Set().Where(t => dateInterval.Contains(t.LocalDate))); + + AssertSql( + """ +@__dateInterval_0='[2018-01-01, 2020-12-25]' (DbType = Object) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE @__dateInterval_0 @> n."LocalDate" +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_Contains_DateInterval(bool async) + { + var dateInterval = new DateInterval(new LocalDate(2018, 4, 22), new LocalDate(2018, 4, 24)); + + await AssertQuery( + async, + ss => ss.Set().Where(t => t.DateInterval.Contains(dateInterval))); + + AssertSql( + """ +@__dateInterval_0='[2018-04-22, 2018-04-24]' (DbType = Object) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."DateInterval" @> @__dateInterval_0 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_Intersection(bool async) + { + var dateInterval = new DateInterval(new LocalDate(2018, 4, 22), new LocalDate(2018, 4, 26)); + + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.DateInterval.Intersection(dateInterval) == new DateInterval(new LocalDate(2018, 4, 22), new LocalDate(2018, 4, 24)))); + + AssertSql( + """ +@__dateInterval_0='[2018-04-22, 2018-04-26]' (DbType = Object) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."DateInterval" * @__dateInterval_0 = '[2018-04-22,2018-04-24]'::daterange +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_Union(bool async) + { + var dateInterval = new DateInterval(new LocalDate(2018, 4, 22), new LocalDate(2018, 4, 26)); + + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.DateInterval.Union(dateInterval) == new DateInterval(new LocalDate(2018, 4, 20), new LocalDate(2018, 4, 26)))); + + AssertSql( + """ +@__dateInterval_0='[2018-04-22, 2018-04-26]' (DbType = Object) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."DateInterval" + @__dateInterval_0 = '[2018-04-20,2018-04-26]'::daterange +"""); + } + + [ConditionalTheory] + [MinimumPostgresVersion(14, 0)] // Multiranges were introduced in PostgreSQL 14 + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_RangeAgg(bool async) + { + await using var context = CreateContext(); + + var query = context.NodaTimeTypes + .GroupBy(x => true) + .Select(g => EF.Functions.RangeAgg(g.Select(x => x.DateInterval))); + + var union = async + ? await query.SingleAsync() + : query.Single(); + + Assert.Equal([new(new LocalDate(2018, 4, 20), new LocalDate(2018, 4, 24))], union); + + AssertSql( + """ +SELECT range_agg(n0."DateInterval") +FROM ( + SELECT n."DateInterval", TRUE AS "Key" + FROM "NodaTimeTypes" AS n +) AS n0 +GROUP BY n0."Key" +LIMIT 2 +"""); + } + + [ConditionalTheory] + [MinimumPostgresVersion(14, 0)] // range_intersect_agg was introduced in PostgreSQL 14 + [MemberData(nameof(IsAsyncData))] + public async Task DateInterval_Intersect_aggregate(bool async) + { + await using var context = CreateContext(); + + var query = context.NodaTimeTypes + .GroupBy(x => true) + .Select(g => EF.Functions.RangeIntersectAgg(g.Select(x => x.DateInterval))); + + var intersection = async + ? await query.SingleAsync() + : query.Single(); + + Assert.Equal(new DateInterval(new LocalDate(2018, 4, 20), new LocalDate(2018, 4, 24)), intersection); + + AssertSql( + """ +SELECT range_intersect_agg(n0."DateInterval") +FROM ( + SELECT n."DateInterval", TRUE AS "Key" + FROM "NodaTimeTypes" AS n +) AS n0 +GROUP BY n0."Key" +LIMIT 2 +"""); + } + + #endregion DateInterval + + #region Range + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task DateRange_Contains(bool async) + { + var dateRange = new DateInterval(new LocalDate(2018, 01, 01), new LocalDate(2020, 12, 26)); + + await AssertQuery( + async, + ss => ss.Set().Where(t => dateRange.Contains(t.LocalDate))); + + AssertSql( + """ +@__dateRange_0='[2018-01-01, 2020-12-26]' (DbType = Object) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE @__dateRange_0 @> n."LocalDate" +"""); + } + + #endregion Range + + #region Instant + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Instance_InUtc(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.Instant.InUtc() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero))); + + AssertSql( + """ +@__p_0='2018-04-20T10:31:33 UTC (+00)' (DbType = DateTime) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."Instant" = @__p_0 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Instance_InZone_constant_LocalDateTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.Instant.InZone(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).LocalDateTime + == new LocalDateTime(2018, 4, 20, 12, 31, 33, 666))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."Instant" AT TIME ZONE 'Europe/Berlin' = TIMESTAMP '2018-04-20T12:31:33.666' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Instance_InZone_constant_Date(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.Instant.InZone(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).Date + == new LocalDate(2018, 4, 20))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE CAST(n."Instant" AT TIME ZONE 'Europe/Berlin' AS date) = DATE '2018-04-20' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Instance_InZone_parameter_LocalDateTime(bool async) + { + var timeZone = DateTimeZoneProviders.Tzdb["Europe/Berlin"]; + + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.Instant.InZone(timeZone).LocalDateTime + == new LocalDateTime(2018, 4, 20, 12, 31, 33, 666))); + + AssertSql( + """ +@__timeZone_0='Europe/Berlin' + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."Instant" AT TIME ZONE @__timeZone_0 = TIMESTAMP '2018-04-20T12:31:33.666' +"""); + } + + [ConditionalFact] + public async Task Instance_InZone_without_LocalDateTime_fails() + { + await using var ctx = CreateContext(); + + await Assert.ThrowsAsync( + () => ctx.Set().Where(t => t.Instant.InZone(DateTimeZoneProviders.Tzdb["Europe/Berlin"]) == default) + .ToListAsync()); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Instance_ToDateTimeUtc(bool async) + { + await AssertQuery( + async, + ss => ss.Set() + .Where(t => t.Instant.ToDateTimeUtc() == new DateTime(2018, 4, 20, 10, 31, 33, 666, DateTimeKind.Utc))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."Instant"::timestamptz = TIMESTAMPTZ '2018-04-20T10:31:33.666Z' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task GetCurrentInstant_from_Instance(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Instant < SystemClock.Instance.GetCurrentInstant())); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."Instant" < NOW() +"""); + } + + [ConditionalFact] + public async Task Instant_Distance() + { + await using var context = CreateContext(); + var closest = await context.NodaTimeTypes + .OrderBy(t => EF.Functions.Distance(t.Instant, new LocalDateTime(2018, 4, 1, 0, 0, 0).InUtc().ToInstant())).FirstAsync(); + + Assert.Equal(1, closest.Id); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +ORDER BY n."Instant" <-> TIMESTAMPTZ '2018-04-01T00:00:00Z' NULLS FIRST +LIMIT 1 +"""); + } + + #endregion + + #region ZonedDateTime + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_Year(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.Year == 2018)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('year', n."ZonedDateTime" AT TIME ZONE 'UTC')::int = 2018 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_Month(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.Month == 4)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('month', n."ZonedDateTime" AT TIME ZONE 'UTC')::int = 4 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_DayOfYear(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.DayOfYear == 110)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('doy', n."ZonedDateTime" AT TIME ZONE 'UTC')::int = 110 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_Day(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.Day == 20)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('day', n."ZonedDateTime" AT TIME ZONE 'UTC')::int = 20 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_Hour(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.Hour == 10)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('hour', n."ZonedDateTime" AT TIME ZONE 'UTC')::int = 10 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_Minute(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.Minute == 31)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE date_part('minute', n."ZonedDateTime" AT TIME ZONE 'UTC')::int = 31 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_Second(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.Second == 33)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE floor(date_part('second', n."ZonedDateTime" AT TIME ZONE 'UTC'))::int = 33 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_Date(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.Date == new LocalDate(2018, 4, 20))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE CAST(n."ZonedDateTime" AT TIME ZONE 'UTC' AS date) = DATE '2018-04-20' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_DayOfWeek(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.ZonedDateTime.DayOfWeek == IsoDayOfWeek.Friday)); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE CASE floor(date_part('dow', n."ZonedDateTime" AT TIME ZONE 'UTC'))::int + WHEN 0 THEN 7 + ELSE floor(date_part('dow', n."ZonedDateTime" AT TIME ZONE 'UTC'))::int +END = 5 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_LocalDateTime(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(t => t.Instant.InUtc().LocalDateTime == new LocalDateTime(2018, 4, 20, 10, 31, 33, 666))); + + AssertSql( + """ +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."Instant" AT TIME ZONE 'UTC' = TIMESTAMP '2018-04-20T10:31:33.666' +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task ZonedDateTime_ToInstant(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where( + t => t.ZonedDateTime.ToInstant() + == new ZonedDateTime(new LocalDateTime(2018, 4, 20, 10, 31, 33, 666), DateTimeZone.Utc, Offset.Zero).ToInstant())); + + AssertSql( + """ +@__ToInstant_0='2018-04-20T10:31:33Z' (DbType = DateTime) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +WHERE n."ZonedDateTime" = @__ToInstant_0 +"""); + } + + [ConditionalFact] + public async Task ZonedDateTime_Distance() + { + await using var context = CreateContext(); + + var closest = await context.NodaTimeTypes + .OrderBy( + t => EF.Functions.Distance( + t.ZonedDateTime, + new ZonedDateTime(new LocalDateTime(2018, 4, 1, 0, 0, 0), DateTimeZone.Utc, Offset.Zero))).FirstAsync(); + Assert.Equal(1, closest.Id); + + AssertSql( + """ +@__p_1='2018-04-01T00:00:00 UTC (+00)' (DbType = DateTime) + +SELECT n."Id", n."DateInterval", n."Duration", n."Instant", n."InstantRange", n."Interval", n."LocalDate", n."LocalDate2", n."LocalDateRange", n."LocalDateTime", n."LocalTime", n."Long", n."OffsetTime", n."Period", n."TimeZoneId", n."ZonedDateTime" +FROM "NodaTimeTypes" AS n +ORDER BY n."ZonedDateTime" <-> @__p_1 NULLS FIRST +LIMIT 1 +"""); + } + + #endregion ZonedDateTime + + #region Support + + private NodaTimeContext CreateContext() + => Fixture.CreateContext(); + + private static readonly Period _defaultPeriod = Period.FromYears(2018) + + Period.FromMonths(4) + + Period.FromDays(20) + + Period.FromHours(10) + + Period.FromMinutes(31) + + Period.FromSeconds(23) + + Period.FromMilliseconds(666); + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + + public class NodaTimeContext(DbContextOptions options) : PoolableDbContext(options) + { + // ReSharper disable once MemberHidesStaticFromOuterClass + // ReSharper disable once UnusedAutoPropertyAccessor.Global + public DbSet NodaTimeTypes { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.HasPostgresExtension("btree_gist"); + } + + public static void Seed(NodaTimeContext context) + { + context.AddRange(NodaTimeData.CreateNodaTimeTypes()); + context.SaveChanges(); + } + } + + public class NodaTimeTypes + { + // ReSharper disable UnusedAutoPropertyAccessor.Global + public int Id { get; set; } + public Instant Instant { get; set; } + public LocalDateTime LocalDateTime { get; set; } + public ZonedDateTime ZonedDateTime { get; set; } + public LocalDate LocalDate { get; set; } + public LocalDate LocalDate2 { get; set; } + public LocalTime LocalTime { get; set; } + public OffsetTime OffsetTime { get; set; } + public Period Period { get; set; } + public Duration Duration { get; set; } + public DateInterval DateInterval { get; set; } + public NpgsqlRange LocalDateRange { get; set; } + public Interval Interval { get; set; } + public NpgsqlRange InstantRange { get; set; } + public long Long { get; set; } + + public string TimeZoneId { get; set; } + // ReSharper restore UnusedAutoPropertyAccessor.Global + } + + public class NodaTimeQueryNpgsqlFixture : SharedStoreFixtureBase, IQueryFixtureBase, ITestSqlLoggerFactory + { + protected override string StoreName + => "NodaTimeQueryTest"; + + // Set the PostgreSQL TimeZone parameter to something local, to ensure that operations which take TimeZone into account + // don't depend on the database's time zone, and also that operations which shouldn't take TimeZone into account indeed + // don't. + // We also instruct the test store to pass a connection string to UseNpgsql() instead of a DbConnection - that's required to allow + // EF's UseNodaTime() to function properly and instantiate an NpgsqlDataSource internally. + protected override ITestStoreFactory TestStoreFactory + => new NpgsqlTestStoreFactory(connectionStringOptions: "-c TimeZone=Europe/Berlin", useConnectionString: true); + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + private NodaTimeData _expectedData; + + protected override IServiceCollection AddServices(IServiceCollection serviceCollection) + => base.AddServices(serviceCollection).AddEntityFrameworkNpgsqlNodaTime(); + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + { + var optionsBuilder = base.AddOptions(builder); + new NpgsqlDbContextOptionsBuilder(optionsBuilder).UseNodaTime(); + + return optionsBuilder; + } + + protected override void Seed(NodaTimeContext context) + => NodaTimeContext.Seed(context); + + public Func GetContextCreator() + => CreateContext; + + public ISetSource GetExpectedData() + => _expectedData ??= new NodaTimeData(); + + public IReadOnlyDictionary EntitySorters + => new Dictionary> { { typeof(NodaTimeTypes), e => ((NodaTimeTypes)e)?.Id } } + .ToDictionary(e => e.Key, e => (object)e.Value); + + public IReadOnlyDictionary EntityAsserters + => new Dictionary> + { + { + typeof(NodaTimeTypes), (e, a) => + { + Assert.Equal(e is null, a is null); + if (a is not null) + { + var ee = (NodaTimeTypes)e; + var aa = (NodaTimeTypes)a; + + Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.LocalDateTime, aa.LocalDateTime); + Assert.Equal(ee.ZonedDateTime, aa.ZonedDateTime); + Assert.Equal(ee.Instant, aa.Instant); + Assert.Equal(ee.LocalDate, aa.LocalDate); + Assert.Equal(ee.LocalDate2, aa.LocalDate2); + Assert.Equal(ee.LocalTime, aa.LocalTime); + Assert.Equal(ee.OffsetTime, aa.OffsetTime); + Assert.Equal(ee.Period, aa.Period); + Assert.Equal(ee.Duration, aa.Duration); + Assert.Equal(ee.DateInterval, aa.DateInterval); + // Assert.Equal(ee.DateRange, aa.DateRange); + Assert.Equal(ee.Long, aa.Long); + Assert.Equal(ee.TimeZoneId, aa.TimeZoneId); + } + } + } + }.ToDictionary(e => e.Key, e => (object)e.Value); + } + + private class NodaTimeData : ISetSource + { + private IReadOnlyList NodaTimeTypes { get; } = CreateNodaTimeTypes(); + + public IQueryable Set() + where TEntity : class + { + if (typeof(TEntity) == typeof(NodaTimeTypes)) + { + return (IQueryable)NodaTimeTypes.AsQueryable(); + } + + throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)); + } + + public static IReadOnlyList CreateNodaTimeTypes() + { + var localDateTime = new LocalDateTime(2018, 4, 20, 10, 31, 33, 666); + var zonedDateTime = localDateTime.InUtc(); + var instant = zonedDateTime.ToInstant(); + var duration = Duration.FromMilliseconds(20) + .Plus(Duration.FromSeconds(8)) + .Plus(Duration.FromMinutes(4)) + .Plus(Duration.FromHours(9)) + .Plus(Duration.FromDays(27)); + + return new List + { + new() + { + Id = 1, + LocalDateTime = localDateTime, + ZonedDateTime = zonedDateTime, + Instant = instant, + LocalDate = localDateTime.Date, + LocalDate2 = localDateTime.Date + Period.FromDays(1), + LocalTime = localDateTime.TimeOfDay, + OffsetTime = new OffsetTime(new LocalTime(10, 31, 33, 666), Offset.Zero), + Period = _defaultPeriod, + Duration = duration, + DateInterval = new DateInterval(localDateTime.Date, localDateTime.Date.PlusDays(4)), // inclusive + LocalDateRange = new NpgsqlRange(localDateTime.Date, localDateTime.Date.PlusDays(5)), // exclusive + Interval = new Interval(instant, instant + Duration.FromDays(5)), + InstantRange = new NpgsqlRange(instant, true, instant + Duration.FromDays(5), false), + Long = 1, + TimeZoneId = "Europe/Berlin" + } + }; + } + } + + #endregion Support +} diff --git a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlFixture.cs index dbe69d633..05f61b936 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlFixture.cs @@ -4,15 +4,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; public class SpatialQueryNpgsqlFixture : SpatialQueryRelationalFixture { -#pragma warning disable CS0618 // GlobalTypeMapper is obsolete - public SpatialQueryNpgsqlFixture() - { - NpgsqlConnection.GlobalTypeMapper.UseNetTopologySuite(); - } -#pragma warning restore CS0618 - + // We instruct the test store to pass a connection string to UseNpgsql() instead of a DbConnection - that's required to allow + // EF's UseNodaTime() to function properly and instantiate an NpgsqlDataSource internally. protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; + => new NpgsqlTestStoreFactory(useConnectionString: true); protected override IServiceCollection AddServices(IServiceCollection serviceCollection) => base.AddServices(serviceCollection).AddEntityFrameworkNpgsqlNetTopologySuite(); diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index 4721eba93..2feac4876 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -869,7 +869,7 @@ protected override string StoreName // don't depend on the database's time zone, and also that operations which shouldn't take TimeZone into account indeed // don't. protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.WithConnectionStringOptions("-c TimeZone=Europe/Berlin"); + => new NpgsqlTestStoreFactory(connectionStringOptions: "-c TimeZone=Europe/Berlin"); public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlFixture.cs index cf6c900e3..71a311908 100644 --- a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlFixture.cs @@ -5,15 +5,10 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class SpatialNpgsqlFixture : SpatialFixtureBase { -#pragma warning disable CS0618 // GlobalTypeMapper is obsolete - public SpatialNpgsqlFixture() - { - NpgsqlConnection.GlobalTypeMapper.UseNetTopologySuite(); - } -#pragma warning restore CS0618 - + // We instruct the test store to pass a connection string to UseNpgsql() instead of a DbConnection - that's required to allow + // EF's UseNetTopologySuite() to function properly and instantiate an NpgsqlDataSource internally. protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; + => new NpgsqlTestStoreFactory(useConnectionString: true); protected override IServiceCollection AddServices(IServiceCollection serviceCollection) => base.AddServices(serviceCollection) diff --git a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs index 8f610757f..32e4e50e7 100644 --- a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs @@ -7,4 +7,9 @@ public class SpatialNpgsqlTest(SpatialNpgsqlFixture fixture) : SpatialTestBase facade.UseTransaction(transaction.GetDbTransaction()); + + // This test requires DbConnection to be used with the test store, but SpatialNpgsqlFixture must set useConnectionString to true + // in order to properly set up the NetTopologySuite internally with the data source. + public override void Mutation_of_tracked_values_does_not_mutate_values_in_store() + => Assert.Throws(() => base.Mutation_of_tracked_values_does_not_mutate_values_in_store()); } diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlNorthwindTestStoreFactory.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlNorthwindTestStoreFactory.cs index fb23e9446..8feb4a2b4 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlNorthwindTestStoreFactory.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlNorthwindTestStoreFactory.cs @@ -21,8 +21,9 @@ protected NpgsqlNorthwindTestStoreFactory() public override TestStore GetOrCreate(string storeName) => NpgsqlTestStore.GetOrCreate( - Name, "Northwind.sql", - TestEnvironment.PostgresVersion >= new Version(12, 0) - ? @"CREATE COLLATION IF NOT EXISTS ""some-case-insensitive-collation"" (LOCALE = 'en-u-ks-primary', PROVIDER = icu, DETERMINISTIC = False);" + Name, + scriptPath: "Northwind.sql", + additionalSql: TestEnvironment.PostgresVersion >= new Version(12, 0) + ? """CREATE COLLATION IF NOT EXISTS "some-case-insensitive-collation" (LOCALE = 'en-u-ks-primary', PROVIDER = icu, DETERMINISTIC = False);""" : null); } diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs index c6acdf2f5..7ed735a7f 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs @@ -1,6 +1,8 @@ using System.Data; using System.Data.Common; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; @@ -10,6 +12,7 @@ public class NpgsqlTestStore : RelationalTestStore { private readonly string? _scriptPath; private readonly string? _additionalSql; + private readonly string? _connectionString; private const string Northwind = "Northwind"; @@ -29,8 +32,9 @@ public static NpgsqlTestStore GetOrCreate( string name, string? scriptPath = null, string? additionalSql = null, - string? connectionStringOptions = null) - => new(name, scriptPath, additionalSql, connectionStringOptions); + string? connectionStringOptions = null, + bool useConnectionString = false) + => new(name, scriptPath, additionalSql, connectionStringOptions, useConnectionString: useConnectionString); public static NpgsqlTestStore Create(string name, string? connectionStringOptions = null) => new(name, connectionStringOptions: connectionStringOptions, shared: false); @@ -39,14 +43,20 @@ public static NpgsqlTestStore CreateInitialized(string name) => new NpgsqlTestStore(name, shared: false) .InitializeNpgsql(null, (Func?)null, null); - private NpgsqlTestStore( + public NpgsqlTestStore( string name, string? scriptPath = null, string? additionalSql = null, string? connectionStringOptions = null, - bool shared = true) + bool shared = true, + bool useConnectionString = false) : base(name, shared, CreateConnection(name, connectionStringOptions)) { + if (useConnectionString) + { + _connectionString = CreateConnectionString(name, connectionStringOptions); + } + Name = name; if (scriptPath is not null) @@ -104,23 +114,19 @@ protected override void Initialize(Func createContext, Action builder.UseNpgsql( - Connection, b => b.ApplyConfiguration() - .CommandTimeout(CommandTimeout) - // The tests are written with the assumption that NULLs are sorted first (SQL Server and .NET behavior), but PostgreSQL - // sorts NULLs last by default. This configures the provider to emit NULLS FIRST. - .ReverseNullOrdering()); - - private static string GetScratchDbName() { - string name; - do - { - name = "Scratch_" + Guid.NewGuid(); - } - while (DatabaseExists(name)); - - return name; + Action npgsqlOptionsBuilder = b => b.ApplyConfiguration() + .CommandTimeout(CommandTimeout) + // The tests are written with the assumption that NULLs are sorted first (SQL Server and .NET behavior), but PostgreSQL + // sorts NULLs last by default. This configures the provider to emit NULLS FIRST. + .ReverseNullOrdering(); + + // The default mode in the EF tests is to use a DbConnection, but in Npgsql we have certain test suites which require that + // we use a connection string instead, because an NpgsqlDataSource is required internally (e.g. enums or plugins + // are used). + return _connectionString is null + ? builder.UseNpgsql(Connection, npgsqlOptionsBuilder) + : builder.UseNpgsql(_connectionString, npgsqlOptionsBuilder); } private bool CreateDatabase(Action? clean) diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStoreFactory.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStoreFactory.cs index 95c16a849..06de22f29 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStoreFactory.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStoreFactory.cs @@ -1,26 +1,21 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; -public class NpgsqlTestStoreFactory : RelationalTestStoreFactory -{ - private readonly string _connectionStringOptions; +#nullable enable +public class NpgsqlTestStoreFactory( + string? scriptPath = null, + string? additionalSql = null, + string? connectionStringOptions = null, + bool useConnectionString = false) : RelationalTestStoreFactory +{ public static NpgsqlTestStoreFactory Instance { get; } = new(); - public static NpgsqlTestStoreFactory WithConnectionStringOptions(string connectionStringOptions) - => new(connectionStringOptions); - - protected NpgsqlTestStoreFactory(string connectionStringOptions = null) - { - _connectionStringOptions = connectionStringOptions; - } - public override TestStore Create(string storeName) - => NpgsqlTestStore.Create(storeName, _connectionStringOptions); + => new NpgsqlTestStore(storeName, scriptPath, additionalSql, connectionStringOptions, shared: false, useConnectionString); public override TestStore GetOrCreate(string storeName) - => NpgsqlTestStore.GetOrCreate(storeName, connectionStringOptions: _connectionStringOptions); + => new NpgsqlTestStore(storeName, scriptPath, additionalSql, connectionStringOptions, shared: true, useConnectionString); public override IServiceCollection AddProviderServices(IServiceCollection serviceCollection) => serviceCollection.AddEntityFrameworkNpgsql(); - // .AddEntityFrameworkNpgsqlNetTopologySuite(); } diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs index 1fab81f8c..ec9815e04 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.NodaTime.FunctionalTests/NodaTimeQueryNpgsqlTest.cs @@ -1,6 +1,5 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; -using Xunit.Sdk; namespace Npgsql.EntityFrameworkCore.PostgreSQL; @@ -1904,18 +1903,13 @@ public class NodaTimeQueryNpgsqlFixture : SharedStoreFixtureBase "NodaTimeTest"; -#pragma warning disable CS0618 // GlobalTypeMapper is obsolete - public NodaTimeQueryNpgsqlFixture() - { - NpgsqlConnection.GlobalTypeMapper.UseNodaTime(); - } -#pragma warning restore CS0618 - // Set the PostgreSQL TimeZone parameter to something local, to ensure that operations which take TimeZone into account // don't depend on the database's time zone, and also that operations which shouldn't take TimeZone into account indeed // don't. + // We also instruct the test store to pass a connection string to UseNpgsql() instead of a DbConnection - that's required to allow + // EF's UseNodaTime() to function properly and instantiate an NpgsqlDataSource internally. protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.WithConnectionStringOptions("-c TimeZone=Europe/Berlin"); + => new NpgsqlTestStoreFactory(connectionStringOptions: "-c TimeZone=Europe/Berlin", useConnectionString: true); public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.Tests/NpgsqlDbContextOptionsExtensionsTest.cs b/test/EFCore.PG.Tests/NpgsqlDbContextOptionsExtensionsTest.cs index 516229e48..e42956b57 100644 --- a/test/EFCore.PG.Tests/NpgsqlDbContextOptionsExtensionsTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlDbContextOptionsExtensionsTest.cs @@ -83,7 +83,7 @@ public void Can_add_extension_with_connection_using_generic_options() [ConditionalTheory] [InlineData(false)] [InlineData(true)] - public void Service_collection_extension_method_can_configure_sqlserver_options(bool nullConnectionString) + public void Service_collection_extension_method_can_configure_npgsql_options(bool nullConnectionString) { var serviceCollection = new ServiceCollection(); serviceCollection.AddNpgsql( diff --git a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs index 7f6421338..e5ce3d183 100644 --- a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Diagnostics.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; @@ -14,7 +15,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class NpgsqlRelationalConnectionTest { [Fact] - public void Creates_Npgsql_Server_connection_string() + public void Creates_NpgsqlConnection() { using var connection = CreateConnection(); @@ -51,7 +52,7 @@ public void Uses_DbDataSource_from_DbContextOptions() Assert.Equal("Host=FakeHost", connection2.ConnectionString); } - [Fact(Skip = "Passes in isolation, but fails when the entire test suite is run because of #2891")] + [Fact] public void Uses_DbDataSource_from_application_service_provider() { var serviceCollection = new ServiceCollection(); @@ -101,6 +102,83 @@ public void DbDataSource_from_application_service_provider_does_not_used_if_conn Assert.Equal("Host=FakeHost2", connection1.ConnectionString); } + [Fact] + public void Multiple_connection_strings_with_plugin() + { + var context1 = new ConnectionStringSwitchingContext("Host=FakeHost1", withNetTopologySuite: true); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConnectionStringSwitchingContext("Host=FakeHost1", withNetTopologySuite: true); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost1", connection2.ConnectionString); + Assert.Same(connection1.DbDataSource, connection2.DbDataSource); + + var context3 = new ConnectionStringSwitchingContext("Host=FakeHost2", withNetTopologySuite: true); + var connection3 = (NpgsqlRelationalConnection)context3.GetService(); + Assert.Equal("Host=FakeHost2", connection3.ConnectionString); + Assert.NotSame(connection1.DbDataSource, connection3.DbDataSource); + } + + [Fact] + public void Multiple_connection_strings_with_enum() + { + var context1 = new ConnectionStringSwitchingContext("Host=FakeHost1", withEnum: true); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConnectionStringSwitchingContext("Host=FakeHost1", withEnum: true); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost1", connection2.ConnectionString); + Assert.Same(connection1.DbDataSource, connection2.DbDataSource); + + var context3 = new ConnectionStringSwitchingContext("Host=FakeHost2", withEnum: true); + var connection3 = (NpgsqlRelationalConnection)context3.GetService(); + Assert.Equal("Host=FakeHost2", connection3.ConnectionString); + Assert.NotSame(connection1.DbDataSource, connection3.DbDataSource); + } + + [Fact] + public void Multiple_connection_strings_without_data_source_features() + { + var context1 = new ConnectionStringSwitchingContext("Host=FakeHost1"); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); + Assert.Null(connection1.DbDataSource); + + var context2 = new ConnectionStringSwitchingContext("Host=FakeHost1"); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost1", connection2.ConnectionString); + Assert.Null(connection2.DbDataSource); + + var context3 = new ConnectionStringSwitchingContext("Host=FakeHost2"); + var connection3 = (NpgsqlRelationalConnection)context3.GetService(); + Assert.Equal("Host=FakeHost2", connection3.ConnectionString); + Assert.Null(connection3.DbDataSource); + } + + private class ConnectionStringSwitchingContext(string connectionString, bool withNetTopologySuite = false, bool withEnum = false) + : DbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql(connectionString, b => + { + if (withNetTopologySuite) + { + b.UseNetTopologySuite(); + } + + if (withEnum) + { + b.MapEnum("mood"); + } + }); + + private enum Mood { Happy, Sad } + } + [Fact] public void Can_create_master_connection_with_connection_string() { @@ -193,8 +271,7 @@ public static NpgsqlRelationalConnection CreateConnection(DbContextOptions optio extension.Validate(options); } - var singletonOptions = new NpgsqlSingletonOptions(); - singletonOptions.Initialize(options); + var dbContextOptions = CreateOptions(); return new NpgsqlRelationalConnection( new RelationalConnectionDependencies( @@ -211,7 +288,7 @@ public static NpgsqlRelationalConnection CreateConnection(DbContextOptions optio new DiagnosticListener("FakeDiagnosticListener"), new NpgsqlLoggingDefinitions(), new NullDbContextLogger(), - CreateOptions()), + dbContextOptions), new NamedConnectionStringResolver(options), new RelationalTransactionFactory( new RelationalTransactionFactoryDependencies( @@ -226,7 +303,8 @@ public static NpgsqlRelationalConnection CreateConnection(DbContextOptions optio new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new NpgsqlSingletonOptions()), new ExceptionDetector()))), - singletonOptions); + new NpgsqlDataSourceManager([]), + dbContextOptions); } private const string ConnectionString = "Fake Connection String"; diff --git a/test/EFCore.PG.NodaTime.FunctionalTests/NpgsqlNodaTimeTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlNodaTimeTypeMappingTest.cs similarity index 99% rename from test/EFCore.PG.NodaTime.FunctionalTests/NpgsqlNodaTimeTypeMappingTest.cs rename to test/EFCore.PG.Tests/Storage/NpgsqlNodaTimeTypeMappingTest.cs index f2cb829af..4fe8cc00a 100644 --- a/test/EFCore.PG.NodaTime.FunctionalTests/NpgsqlNodaTimeTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlNodaTimeTypeMappingTest.cs @@ -1,6 +1,7 @@ using System.Text; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Storage.Json; +using NodaTime; using NodaTime.Calendars; using NodaTime.Text; using NodaTime.TimeZones; @@ -8,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; -namespace Npgsql.EntityFrameworkCore.PostgreSQL; +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage; public class NpgsqlNodaTimeTypeMappingTest { diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs index c16e5d2d2..c735a165c 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -312,7 +312,7 @@ private NpgsqlTypeMappingSource CreateTypeMappingSource(Version postgresVersion new RelationalTypeMappingSourceDependencies( new IRelationalTypeMappingSourcePlugin[] { - new NpgsqlNetTopologySuiteTypeMappingSourcePlugin(new NpgsqlNetTopologySuiteOptions()), + new NpgsqlNetTopologySuiteTypeMappingSourcePlugin(new NpgsqlNetTopologySuiteSingletonOptions()), new DummyTypeMappingSourcePlugin() }), new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs index 77f45d8bf..c2d90bd2a 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs @@ -489,7 +489,11 @@ public void ValueComparer_hstore_as_ImmutableDictionary() [Fact] public void GenerateSqlLiteral_returns_enum_literal() { - var mapping = new NpgsqlEnumTypeMapping("dummy_enum", typeof(DummyEnum), new NpgsqlSnakeCaseNameTranslator()); + var mapping = new NpgsqlEnumTypeMapping("dummy_enum", "dummy_enum", typeof(DummyEnum), new Dictionary + { + [DummyEnum.Happy] = "happy", + [DummyEnum.Sad] = "sad" + }); Assert.Equal("'sad'::dummy_enum", mapping.GenerateSqlLiteral(DummyEnum.Sad)); } @@ -497,7 +501,11 @@ public void GenerateSqlLiteral_returns_enum_literal() [Fact] public void GenerateSqlLiteral_returns_enum_uppercase_literal() { - var mapping = new NpgsqlEnumTypeMapping(@"""DummyEnum""", typeof(DummyEnum), new NpgsqlSnakeCaseNameTranslator()); + var mapping = new NpgsqlEnumTypeMapping(@"""DummyEnum""", "DummyEnum", typeof(DummyEnum), new Dictionary + { + [DummyEnum.Happy] = "happy", + [DummyEnum.Sad] = "sad" + }); Assert.Equal(@"'sad'::""DummyEnum""", mapping.GenerateSqlLiteral(DummyEnum.Sad)); } From 3b71c73c2092bfbdd4b0d397a731cc3bdb0b2112 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 8 Jun 2024 23:32:24 +0200 Subject: [PATCH 042/107] Add missing check for predicate in primitive collection simplifications (#3196) Fixes #3195 --- .../NpgsqlQueryableMethodTranslatingExpressionVisitor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index 0c4f030cb..aa93bc326 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -551,6 +551,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Source1: { Tables: [PgUnnestExpression { Array: var array1 }], + Predicate: null, GroupBy: [], Having: null, IsDistinct: false, @@ -560,6 +561,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Source2: { Tables: [PgUnnestExpression { Array: var array2 }], + Predicate: null, GroupBy: [], Having: null, IsDistinct: false, @@ -676,6 +678,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT && source.QueryExpression is SelectExpression { Tables: [PgUnnestExpression { Array: var array }], + Predicate: null, GroupBy: [], Having: null, IsDistinct: false, @@ -715,6 +718,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s if (source1.QueryExpression is SelectExpression { Tables: [PgUnnestExpression { Array: var array1 } unnestExpression1], + Predicate: null, GroupBy: [], Having: null, IsDistinct: false, @@ -725,6 +729,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s && source2.QueryExpression is SelectExpression { Tables: [PgUnnestExpression { Array: var array2 }], + Predicate: null, GroupBy: [], Having: null, IsDistinct: false, @@ -788,6 +793,7 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " && source.QueryExpression is SelectExpression { Tables: [PgUnnestExpression { Array: var array }], + Predicate: null, GroupBy: [], Having: null, IsDistinct: false, @@ -918,6 +924,7 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " if (source.QueryExpression is SelectExpression { Tables: [PgUnnestExpression { Array: var array } unnestExpression], + Predicate: null, GroupBy: [], Having: null, IsDistinct: false, @@ -980,6 +987,7 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " if (source.QueryExpression is SelectExpression { Tables: [PgUnnestExpression { Array: var array } unnestExpression], + Predicate: null, GroupBy: [], Having: null, IsDistinct: false, From 933bc8df4461442bf39c08cda14818b153dbb2a1 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 24 Jun 2024 00:22:47 +0200 Subject: [PATCH 043/107] Add ordering by ordinality column for primitive collections (#3209) Fixes #3207 --- .../NpgsqlShapedQueryExpressionExtensions.cs | 158 ++++++++++ ...yableMethodTranslatingExpressionVisitor.cs | 279 ++++-------------- .../Internal/NpgsqlUnnestPostprocessor.cs | 23 +- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 8 +- 4 files changed, 238 insertions(+), 230 deletions(-) create mode 100644 src/EFCore.PG/Extensions/Internal/NpgsqlShapedQueryExpressionExtensions.cs diff --git a/src/EFCore.PG/Extensions/Internal/NpgsqlShapedQueryExpressionExtensions.cs b/src/EFCore.PG/Extensions/Internal/NpgsqlShapedQueryExpressionExtensions.cs new file mode 100644 index 000000000..e234ac48c --- /dev/null +++ b/src/EFCore.PG/Extensions/Internal/NpgsqlShapedQueryExpressionExtensions.cs @@ -0,0 +1,158 @@ +using System.Diagnostics.CodeAnalysis; +using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Extensions.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public static class NpgsqlShapedQueryExpressionExtensions +{ + /// + /// If the given wraps an array-returning expression without any additional clauses (e.g. filter, + /// ordering...), returns that expression. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool TryExtractArray( + this ShapedQueryExpression source, + [NotNullWhen(true)] out SqlExpression? array, + bool ignoreOrderings = false, + bool ignorePredicate = false) + => TryExtractArray(source, out array, out _, ignoreOrderings, ignorePredicate); + + /// + /// If the given wraps an array-returning expression without any additional clauses (e.g. filter, + /// ordering...), returns that expression. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool TryExtractArray( + this ShapedQueryExpression source, + [NotNullWhen(true)] out SqlExpression? array, + [NotNullWhen(true)] out ColumnExpression? projectedColumn, + bool ignoreOrderings = false, + bool ignorePredicate = false) + { + if (source.QueryExpression is SelectExpression + { + Tables: [PgUnnestExpression { Array: var a } unnest], + GroupBy: [], + Having: null, + IsDistinct: false, + Limit: null, + Offset: null + } select + && (ignorePredicate || select.Predicate is null) + // We can only apply the indexing if the JSON array is ordered by its natural ordered, i.e. by the "ordinality" column that + // we created in TranslatePrimitiveCollection. For example, if another ordering has been applied (e.g. by the array elements + // themselves), we can no longer simply index into the original array. + && (ignoreOrderings + || select.Orderings is [] + || (select.Orderings is [{ Expression: ColumnExpression { Name: "ordinality", TableAlias: var orderingTableAlias } }] + && orderingTableAlias == unnest.Alias)) + && IsPostgresArray(a) + && TryGetProjectedColumn(source, out var column)) + { + array = a; + projectedColumn = column; + return true; + } + + array = null; + projectedColumn = null; + return false; + } + + /// + /// If the given wraps a without any additional clauses (e.g. filter, + /// ordering...), converts that to a and returns that. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool TryConvertValuesToArray( + this ShapedQueryExpression source, + [NotNullWhen(true)] out SqlExpression? array, + bool ignoreOrderings = false, + bool ignorePredicate = false) + { + if (source.QueryExpression is SelectExpression + { + Tables: [ValuesExpression { ColumnNames: ["_ord", "Value"], RowValues.Count: > 0 } valuesExpression], + GroupBy: [], + Having: null, + IsDistinct: false, + Limit: null, + Offset: null + } select + && (ignorePredicate || select.Predicate is null) + && (ignoreOrderings || select.Orderings is [])) + { + var elements = new SqlExpression[valuesExpression.RowValues.Count]; + + for (var i = 0; i < elements.Length; i++) + { + // Skip the first column (_ord) and copy the second (Value) + elements[i] = valuesExpression.RowValues[i].Values[1]; + } + + array = new PgNewArrayExpression(elements, valuesExpression.RowValues[0].Values[1].Type.MakeArrayType(), typeMapping: null); + return true; + } + + array = null; + return false; + } + + /// + /// Checks whether the given expression maps to a PostgreSQL array, as opposed to a multirange type. + /// + private static bool IsPostgresArray(SqlExpression expression) + => expression switch + { + { TypeMapping: NpgsqlArrayTypeMapping } => true, + { TypeMapping: NpgsqlMultirangeTypeMapping } => false, + { Type: var type } when type.IsMultirange() => false, + _ => true + }; + + private static bool TryGetProjectedColumn( + ShapedQueryExpression shapedQueryExpression, + [NotNullWhen(true)] out ColumnExpression? projectedColumn) + { + var shaperExpression = shapedQueryExpression.ShaperExpression; + if (shaperExpression is UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression + && unaryExpression.Operand.Type.IsNullableType() + && unaryExpression.Operand.Type.UnwrapNullableType() == unaryExpression.Type) + { + shaperExpression = unaryExpression.Operand; + } + + if (shaperExpression is ProjectionBindingExpression projectionBindingExpression + && shapedQueryExpression.QueryExpression is SelectExpression selectExpression + && selectExpression.GetProjection(projectionBindingExpression) is ColumnExpression c) + { + projectedColumn = c; + return true; + } + + projectedColumn = null; + return false; + } +} diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index aa93bc326..2224b09a2 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Npgsql.EntityFrameworkCore.PostgreSQL.Extensions.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; @@ -137,9 +138,11 @@ [new TableValuedFunctionExpression(tableAlias, "ST_Dump", new[] { sqlExpression // (f above); since the table alias may get uniquified by EF, this would break queries. // TODO: When we have metadata to determine if the element is nullable, pass that here to SelectExpression - // Note also that with PostgreSQL unnest, the output ordering is guaranteed to be the same as the input array, so we don't need - // to add ordering like in most other providers (https://www.postgresql.org/docs/current/functions-array.html) - // We also don't need to apply any casts or typing, since PG arrays are fully typed (unlike e.g. a JSON string). + + // Note also that with PostgreSQL unnest, the output ordering is guaranteed to be the same as the input array. However, we still + // need to add an explicit ordering on the ordinality column, since once the unnest is joined into a select, its "natural" + // orderings is lost and an explicit ordering is needed again (see #3207). + var (ordinalityColumn, ordinalityComparer) = GenerateOrdinalityIdentifier(tableAlias); selectExpression = new SelectExpression( [new PgUnnestExpression(tableAlias, sqlExpression, "value")], new ColumnExpression( @@ -148,8 +151,10 @@ [new PgUnnestExpression(tableAlias, sqlExpression, "value")], elementClrType.UnwrapNullableType(), elementTypeMapping, isElementNullable), - identifier: [GenerateOrdinalityIdentifier(tableAlias)], + identifier: [(ordinalityColumn, ordinalityComparer)], _queryCompilationContext.SqlAliasManager); + + selectExpression.AppendOrdering(new OrderingExpression(ordinalityColumn, ascending: true)); } #pragma warning restore EF1001 @@ -274,17 +279,9 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT /// protected override ShapedQueryExpression? TranslateAll(ShapedQueryExpression source, LambdaExpression predicate) { - if (source.QueryExpression is SelectExpression - { - Tables: [var sourceTable], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Limit: null, - Offset: null - } - && TryGetArray(sourceTable, out var array) + if ((source.TryExtractArray(out var array, ignoreOrderings: true) + || source.TryConvertValuesToArray(out array, ignoreOrderings: true)) + && source.QueryExpression is SelectExpression { Tables: [{ Alias: var tableAlias }] } && TranslateLambdaExpression(source, predicate) is { } translatedPredicate) { switch (translatedPredicate) @@ -297,7 +294,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Pattern: ColumnExpression pattern, EscapeChar: SqlConstantExpression { Value: "" } } - when pattern.TableAlias == sourceTable.Alias: + when pattern.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, @@ -312,7 +309,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Pattern: ColumnExpression pattern, EscapeChar: SqlConstantExpression { Value: "" } } - when pattern.TableAlias == sourceTable.Alias: + when pattern.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, @@ -326,7 +323,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Item: ColumnExpression sourceColumn, Array: var otherArray } - when sourceColumn.TableAlias == sourceTable.Alias: + when sourceColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.ContainedBy(array, otherArray)); } @@ -339,7 +336,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Left: var otherArray, Right: PgNewArrayExpression { Expressions: [ColumnExpression sourceColumn] } } - when sourceColumn.TableAlias == sourceTable.Alias: + when sourceColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.ContainedBy(array, otherArray)); } @@ -357,17 +354,9 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT /// protected override ShapedQueryExpression? TranslateAny(ShapedQueryExpression source, LambdaExpression? predicate) { - if (source.QueryExpression is SelectExpression - { - Tables: [var sourceTable], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Limit: null, - Offset: null - } - && TryGetArray(sourceTable, out var array)) + if ((source.TryExtractArray(out var array, ignoreOrderings: true) + || source.TryConvertValuesToArray(out array, ignoreOrderings: true)) + && source.QueryExpression is SelectExpression { Tables: [{ Alias: var tableAlias }] }) { // Pattern match: x.Array.Any() // Translation: cardinality(x.array) > 0 instead of EXISTS (SELECT 1 FROM FROM unnest(x.Array)) @@ -400,7 +389,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Pattern: ColumnExpression pattern, EscapeChar: SqlConstantExpression { Value: "" } } - when pattern.TableAlias == sourceTable.Alias: + when pattern.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, _sqlExpressionFactory.Any(match, array, PgAnyOperatorType.Like)); @@ -414,7 +403,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Pattern: ColumnExpression pattern, EscapeChar: SqlConstantExpression { Value: "" } } - when pattern.TableAlias == sourceTable.Alias: + when pattern.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, _sqlExpressionFactory.Any(match, array, PgAnyOperatorType.ILike)); @@ -428,7 +417,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Item: ColumnExpression sourceColumn, Array: var otherArray } - when sourceColumn.TableAlias == sourceTable.Alias: + when sourceColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.Overlaps(array, otherArray)); } @@ -442,7 +431,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Left: var otherArray, Right: PgNewArrayExpression { Expressions: [ColumnExpression sourceColumn] } } - when sourceColumn.TableAlias == sourceTable.Alias: + when sourceColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery(source, _sqlExpressionFactory.Overlaps(array, otherArray)); } @@ -457,7 +446,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Left: var ltree, Right: SqlUnaryExpression { OperatorType: ExpressionType.Convert, Operand: ColumnExpression lqueryColumn } } - when lqueryColumn.TableAlias == sourceTable.Alias: + when lqueryColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, @@ -480,7 +469,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT // Contains/ContainedBy can happen for non-LTree types too, so check that Right: { TypeMapping: NpgsqlLTreeTypeMapping } ltree } - when ltreeColumn.TableAlias == sourceTable.Alias: + when ltreeColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, @@ -502,7 +491,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Left: ColumnExpression ltreeColumn, Right: var lquery } - when ltreeColumn.TableAlias == sourceTable.Alias: + when ltreeColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, @@ -523,7 +512,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT Left: ColumnExpression ltreeColumn, Right: var lqueries } - when ltreeColumn.TableAlias == sourceTable.Alias: + when ltreeColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, @@ -593,16 +582,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT { // Note that most other simplifications convert ValuesExpression to unnest over array constructor, but we avoid doing that // here for Contains, since the relational translation for ValuesExpression is better. - if (source.QueryExpression is SelectExpression - { - Tables: [PgUnnestExpression { Array: var array }], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Limit: null, - Offset: null - } + if (source.TryExtractArray(out var array, ignoreOrderings: true) && TranslateExpression(item, applyDefaultTypeMapping: false) is SqlExpression translatedItem) { (translatedItem, array) = _sqlExpressionFactory.ApplyTypeMappingsOnItemAndArray(translatedItem, array); @@ -674,17 +654,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT protected override ShapedQueryExpression? TranslateCount(ShapedQueryExpression source, LambdaExpression? predicate) { // Simplify x.Array.Count() => cardinality(x.Array) instead of SELECT COUNT(*) FROM unnest(x.Array) - if (predicate is null - && source.QueryExpression is SelectExpression - { - Tables: [PgUnnestExpression { Array: var array }], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Limit: null, - Offset: null - }) + if (predicate is null && source.TryExtractArray(out var array, ignoreOrderings: true)) { var translation = _sqlExpressionFactory.Function( "cardinality", @@ -715,30 +685,8 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s { // Simplify x.Array.Concat(y.Array) => x.Array || y.Array instead of: // SELECT u.value FROM unnest(x.Array) UNION ALL SELECT u.value FROM unnest(y.Array) - if (source1.QueryExpression is SelectExpression - { - Tables: [PgUnnestExpression { Array: var array1 } unnestExpression1], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Limit: null, - Offset: null, - Orderings: [] - } - && source2.QueryExpression is SelectExpression - { - Tables: [PgUnnestExpression { Array: var array2 }], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Limit: null, - Offset: null, - Orderings: [] - } - && TryGetProjectedColumn(source1, out var projectedColumn1) - && TryGetProjectedColumn(source2, out var projectedColumn2)) + if (source1.TryExtractArray(out var array1, out var projectedColumn1) + && source2.TryExtractArray(out var array2, out var projectedColumn2)) { Check.DebugAssert(projectedColumn1.Type == projectedColumn2.Type, "projectedColumn1.Type == projectedColumn2.Type"); Check.DebugAssert( @@ -749,7 +697,7 @@ protected override ShapedQueryExpression TranslateConcat(ShapedQueryExpression s var inferredTypeMapping = projectedColumn1.TypeMapping ?? projectedColumn2.TypeMapping; #pragma warning disable EF1001 // SelectExpression constructors are currently internal - var tableAlias = unnestExpression1.Alias; + var tableAlias = ((SelectExpression)source1.QueryExpression).Tables.Single().Alias!; var selectExpression = new SelectExpression( [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), "value")], new ColumnExpression("value", tableAlias, projectedColumn1.Type, inferredTypeMapping, projectedColumn1.IsNullable || projectedColumn2.IsNullable), @@ -790,19 +738,7 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " // Simplify x.Array[1] => x.Array[1] (using the PG array subscript operator) instead of a subquery with LIMIT/OFFSET // Note that we have unnest over multiranges, not just arrays - but multiranges don't support subscripting/slicing. if (!returnDefault - && source.QueryExpression is SelectExpression - { - Tables: [PgUnnestExpression { Array: var array }], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Orderings: [], - Limit: null, - Offset: null - } - && IsPostgresArray(array) - && TryGetProjectedColumn(source, out var projectedColumn) + && source.TryExtractArray(out var array, out var projectedColumn) && TranslateExpression(index) is { } translatedIndex) { // Note that PostgreSQL arrays are 1-based, so adjust the index. @@ -834,18 +770,9 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " // Some LTree translations (see LTreeQueryTest) // Note that preprocessing normalizes FirstOrDefault(predicate) to Where(predicate).FirstOrDefault(), so the source's // select expression should already contain our predicate. - if (source.QueryExpression is SelectExpression - { - Tables: [var sourceTable], - Predicate: var translatedPredicate, - GroupBy: [], - Having: null, - IsDistinct: false, - Limit: null, - Offset: null, - Orderings: [] - } - && TryGetArray(sourceTable, out var array) + if ((source.TryExtractArray(out var array, ignorePredicate: true) + || source.TryConvertValuesToArray(out array, ignorePredicate: true)) + && source.QueryExpression is SelectExpression { Tables: [{ Alias: var tableAlias }], Predicate: var translatedPredicate } && translatedPredicate is null ^ predicate is null) { if (translatedPredicate is null) @@ -870,7 +797,7 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " // Contains/ContainedBy can happen for non-LTree types too, so check that Right: { TypeMapping: NpgsqlLTreeTypeMapping } ltree } - when ltreeColumn.TableAlias == sourceTable.Alias: + when ltreeColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, @@ -894,7 +821,7 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " Left: ColumnExpression ltreeColumn, Right: var lquery } - when ltreeColumn.TableAlias == sourceTable.Alias: + when ltreeColumn.TableAlias == tableAlias: { return BuildSimplifiedShapedQuery( source, @@ -921,23 +848,11 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " { // Translate Skip over array to the PostgreSQL slice operator (array.Skip(2) -> array[3,]) // Note that we have unnest over multiranges, not just arrays - but multiranges don't support subscripting/slicing. - if (source.QueryExpression is SelectExpression - { - Tables: [PgUnnestExpression { Array: var array } unnestExpression], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Orderings: [], - Limit: null, - Offset: null - } - && IsPostgresArray(array) - && TryGetProjectedColumn(source, out var projectedColumn) + if (source.TryExtractArray(out var array, out var projectedColumn) && TranslateExpression(count) is { } translatedCount) { #pragma warning disable EF1001 // SelectExpression constructors are currently internal - var tableAlias = unnestExpression.Alias; + var tableAlias = ((SelectExpression)source.QueryExpression).Tables[0].Alias!; var selectExpression = new SelectExpression( [ new PgUnnestExpression( @@ -984,26 +899,9 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " { // Translate Take over array to the PostgreSQL slice operator (array.Take(2) -> array[,2]) // Note that we have unnest over multiranges, not just arrays - but multiranges don't support subscripting/slicing. - if (source.QueryExpression is SelectExpression - { - Tables: [PgUnnestExpression { Array: var array } unnestExpression], - Predicate: null, - GroupBy: [], - Having: null, - IsDistinct: false, - Orderings: [], - Limit: null, - Offset: null - } - && IsPostgresArray(array) - && TryGetProjectedColumn(source, out var projectedColumn)) + if (source.TryExtractArray(out var array, out var projectedColumn) + && TranslateExpression(count) is { } translatedCount) { - var translatedCount = TranslateExpression(count); - if (translatedCount == null) - { - return base.TranslateTake(source, count); - } - PgArraySliceExpression sliceExpression; // If Skip has been called before, an array slice expression is already there; try to integrate this Take into it. @@ -1043,10 +941,11 @@ [new PgUnnestExpression(tableAlias, _sqlExpressionFactory.Add(array1, array2), " } #pragma warning disable EF1001 // SelectExpression constructors are currently internal + var tableAlias = ((SelectExpression)source.QueryExpression).Tables[0].Alias!; var selectExpression = new SelectExpression( - [new PgUnnestExpression(unnestExpression.Alias, sliceExpression, "value")], - new ColumnExpression("value", unnestExpression.Alias, projectedColumn.Type, projectedColumn.TypeMapping, projectedColumn.IsNullable), - [GenerateOrdinalityIdentifier(unnestExpression.Alias)], + [new PgUnnestExpression(tableAlias, sliceExpression, "value")], + new ColumnExpression("value", tableAlias, projectedColumn.Type, projectedColumn.TypeMapping, projectedColumn.IsNullable), + [GenerateOrdinalityIdentifier(tableAlias)], _queryCompilationContext.SqlAliasManager); #pragma warning restore EF1001 // Internal EF Core API usage. @@ -1069,6 +968,19 @@ [new PgUnnestExpression(unnestExpression.Alias, sliceExpression, "value")], return base.TranslateTake(source, count); } + /// + /// 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 IsNaturallyOrdered(SelectExpression selectExpression) + => selectExpression is { Tables: [PgUnnestExpression unnest, ..] } + && (selectExpression.Orderings is [] + || selectExpression.Orderings is + [{ Expression: ColumnExpression { Name: "ordinality", TableAlias: var orderingTableAlias } }] + && orderingTableAlias == unnest.Alias); + /// /// 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 @@ -1178,42 +1090,6 @@ protected override bool IsOrdered(SelectExpression selectExpression) || selectExpression.Tables is [PgTableValuedFunctionExpression { Name: "unnest" or "jsonb_to_recordset" or "json_to_recordset" }]; - /// - /// Checks whether the given expression maps to a PostgreSQL array, as opposed to a multirange type. - /// - private static bool IsPostgresArray(SqlExpression expression) - => expression switch - { - { TypeMapping: NpgsqlArrayTypeMapping } => true, - { TypeMapping: NpgsqlMultirangeTypeMapping } => false, - { Type: var type } when type.IsMultirange() => false, - _ => true - }; - - private bool TryGetProjectedColumn( - ShapedQueryExpression shapedQueryExpression, - [NotNullWhen(true)] out ColumnExpression? projectedColumn) - { - var shaperExpression = shapedQueryExpression.ShaperExpression; - if (shaperExpression is UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression - && unaryExpression.Operand.Type.IsNullableType() - && unaryExpression.Operand.Type.UnwrapNullableType() == unaryExpression.Type) - { - shaperExpression = unaryExpression.Operand; - } - - if (shaperExpression is ProjectionBindingExpression projectionBindingExpression - && shapedQueryExpression.QueryExpression is SelectExpression selectExpression - && selectExpression.GetProjection(projectionBindingExpression) is ColumnExpression c) - { - projectedColumn = c; - return true; - } - - projectedColumn = null; - return false; - } - private (ColumnExpression, ValueComparer) GenerateOrdinalityIdentifier(string tableAlias) { _ordinalityTypeMapping ??= _typeMappingSource.FindMapping("int")!; @@ -1238,43 +1114,6 @@ private ShapedQueryExpression BuildSimplifiedShapedQuery(ShapedQueryExpression s new ProjectionBindingExpression(translation, new ProjectionMember(), typeof(bool?)), typeof(bool))); #pragma warning restore EF1001 - /// - /// Extracts the out of . - /// If a is given, converts its literal values into a . - /// - private bool TryGetArray(TableExpressionBase tableExpression, [NotNullWhen(true)] out SqlExpression? array) - { - switch (tableExpression) - { - case PgUnnestExpression unnest: - array = unnest.Array; - return true; - - // TODO: We currently don't have information type information on empty ValuesExpression, so we can't transform that into an - // array. - case ValuesExpression { ColumnNames: ["_ord", "Value"], RowValues.Count: > 0 } valuesExpression: - { - // The source table was a constant collection, so translated by default to ValuesExpression. Convert it to an unnest over - // an array constructor. - var elements = new SqlExpression[valuesExpression.RowValues.Count]; - - for (var i = 0; i < elements.Length; i++) - { - // Skip the first column (_ord) and copy the second (Value) - elements[i] = valuesExpression.RowValues[i].Values[1]; - } - - array = new PgNewArrayExpression( - elements, valuesExpression.RowValues[0].Values[1].Type.MakeArrayType(), typeMapping: null); - return true; - } - - default: - array = null; - return false; - } - } - private sealed class OuterReferenceFindingExpressionVisitor(TableExpression mainTable) : ExpressionVisitor { private bool _containsReference; diff --git a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs index 1fc98797b..4b1a5d38d 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs @@ -33,19 +33,21 @@ public class NpgsqlUnnestPostprocessor : ExpressionVisitor { TableExpressionBase[]? newTables = null; + var orderings = selectExpression.Orderings; + for (var i = 0; i < selectExpression.Tables.Count; i++) { var table = selectExpression.Tables[i]; var unwrappedTable = table.UnwrapJoin(); // Find any unnest table which does not have any references to its ordinality column in the projection or orderings - // (this is where they may appear when a column is an identifier). + // (this is where they may appear); if found, remove the ordinality column from the unnest call. + // Note that if the ordinality column is the first ordering, we can still remove it, since unnest already returns + // ordered results. if (unwrappedTable is PgUnnestExpression unnest - && !selectExpression.Orderings.Select(o => o.Expression) + && !selectExpression.Orderings.Skip(1).Select(o => o.Expression) .Concat(selectExpression.Projection.Select(p => p.Expression)) - .Any( - p => p is ColumnExpression { Name: "ordinality" } ordinalityColumn - && ordinalityColumn.TableAlias == unwrappedTable.Alias)) + .Any(IsOrdinalityColumn)) { if (newTables is null) { @@ -65,7 +67,16 @@ public class NpgsqlUnnestPostprocessor : ExpressionVisitor PgUnnestExpression => newUnnest, _ => throw new UnreachableException() }; + + if (orderings.Count > 0 && IsOrdinalityColumn(orderings[0].Expression)) + { + orderings = orderings.Skip(1).ToList(); + } } + + bool IsOrdinalityColumn(SqlExpression expression) + => expression is ColumnExpression { Name: "ordinality" } ordinalityColumn + && ordinalityColumn.TableAlias == unwrappedTable.Alias; } return base.Visit( @@ -77,7 +88,7 @@ newTables is null selectExpression.Predicate, selectExpression.GroupBy, selectExpression.Having, - selectExpression.Orderings, + orderings, selectExpression.Limit, selectExpression.Offset)); } diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 3f73b78cf..7d34b3711 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -1254,7 +1254,7 @@ LEFT JOIN LATERAL ( FROM unnest(p."DateTimes") WITH ORDINALITY AS d(value) WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 OR d.value AT TIME ZONE 'UTC' IS NULL ) AS d0 ON TRUE -ORDER BY p."Id" NULLS FIRST +ORDER BY p."Id" NULLS FIRST, d0.ordinality NULLS FIRST """); } @@ -1339,7 +1339,7 @@ ORDER BY p."Id" NULLS FIRST LIMIT 1 ) AS p0 LEFT JOIN LATERAL unnest(p0."Ints") WITH ORDINALITY AS i(value) ON TRUE -ORDER BY p0."Id" NULLS FIRST +ORDER BY p0."Id" NULLS FIRST, i.ordinality NULLS FIRST """); } @@ -1361,7 +1361,7 @@ LEFT JOIN LATERAL ( FROM unnest(p."NullableInts") WITH ORDINALITY AS n0(value) WHERE n0.value IS NULL ) AS n2 ON TRUE -ORDER BY p."Id" NULLS FIRST, n1.ordinality NULLS FIRST +ORDER BY p."Id" NULLS FIRST, n1.ordinality NULLS FIRST, n2.ordinality NULLS FIRST """); } @@ -1403,7 +1403,7 @@ LEFT JOIN LATERAL ( FROM unnest(p."DateTimes") WITH ORDINALITY AS d0(value) WHERE d0.value > TIMESTAMPTZ '2000-01-01T00:00:00Z' ) AS d2 ON TRUE -ORDER BY p."Id" NULLS FIRST, i.ordinality NULLS FIRST, i0.value DESC NULLS LAST, i0.ordinality NULLS FIRST, d1.ordinality NULLS FIRST +ORDER BY p."Id" NULLS FIRST, i.ordinality NULLS FIRST, i0.value DESC NULLS LAST, i0.ordinality NULLS FIRST, d1.ordinality NULLS FIRST, d2.ordinality NULLS FIRST """); } From dba77fd16bf29d825caf5a5c6f78365b43393326 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 28 Jun 2024 15:01:28 +0200 Subject: [PATCH 044/107] Correct non-NodaTime date/time type mappings when NodaTime is configured (#3214) Fixes #3213 --- .../Internal/NpgsqlDateTimeMemberTranslator.cs | 4 ++-- .../Internal/NpgsqlDateTimeMethodTranslator.cs | 6 +++--- src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs index e9d54f133..73c2b578f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs @@ -27,8 +27,8 @@ public class NpgsqlDateTimeMemberTranslator : IMemberTranslator public NpgsqlDateTimeMemberTranslator(IRelationalTypeMappingSource typeMappingSource, NpgsqlSqlExpressionFactory sqlExpressionFactory) { _typeMappingSource = typeMappingSource; - _timestampMapping = typeMappingSource.FindMapping("timestamp without time zone")!; - _timestampTzMapping = typeMappingSource.FindMapping("timestamp with time zone")!; + _timestampMapping = typeMappingSource.FindMapping(typeof(DateTime), "timestamp without time zone")!; + _timestampTzMapping = typeMappingSource.FindMapping(typeof(DateTime), "timestamp with time zone")!; _sqlExpressionFactory = sqlExpressionFactory; } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs index e64192022..101ae9b19 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs @@ -113,9 +113,9 @@ public NpgsqlDateTimeMethodTranslator( { _typeMappingSource = typeMappingSource; _sqlExpressionFactory = sqlExpressionFactory; - _timestampMapping = typeMappingSource.FindMapping("timestamp without time zone")!; - _timestampTzMapping = typeMappingSource.FindMapping("timestamp with time zone")!; - _intervalMapping = typeMappingSource.FindMapping("interval")!; + _timestampMapping = typeMappingSource.FindMapping(typeof(DateTime), "timestamp without time zone")!; + _timestampTzMapping = typeMappingSource.FindMapping(typeof(DateTime), "timestamp with time zone")!; + _intervalMapping = typeMappingSource.FindMapping(typeof(TimeSpan), "interval")!; _textMapping = typeMappingSource.FindMapping("text")!; } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index cff84bdb2..5129fdb7c 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -11,7 +11,6 @@ using System.Text.Json; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; -using Npgsql.Internal; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; From b91ef352f65cfd66d36241134c19cdc07a5045ab Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Sun, 1 Sep 2024 16:00:38 +0800 Subject: [PATCH 045/107] Add UUID version 7 as the default guid generator (#3249) Closes #2909 --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- global.json | 2 +- .../Internal/NpgsqlUuid7ValueGenerator.cs | 107 ++++++++++++++++++ .../Internal/NpgsqlValueGeneratorSelector.cs | 2 +- .../NorthwindFunctionsQueryNpgsqlTest.cs | 44 +++---- .../NpgsqlValueGeneratorSelectorTest.cs | 19 +++- 7 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 src/EFCore.PG/ValueGeneration/Internal/NpgsqlUuid7ValueGenerator.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cabcf202d..8c6d4bc99 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: pull_request: env: - dotnet_sdk_version: '9.0.100-preview.3.24204.13' + dotnet_sdk_version: '9.0.100-preview.7.24407.12' postgis_version: 3 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e4a10226f..9f6bdb73b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ on: - cron: '30 22 * * 6' env: - dotnet_sdk_version: '9.0.100-preview.3.24204.13' + dotnet_sdk_version: '9.0.100-preview.7.24407.12' jobs: analyze: diff --git a/global.json b/global.json index 50d6cea52..a143424dc 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100-preview.3.24204.13", + "version": "9.0.100-preview.7.24407.12", "rollForward": "latestMajor", "allowPrerelease": true } diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlUuid7ValueGenerator.cs b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlUuid7ValueGenerator.cs new file mode 100644 index 000000000..16fee666f --- /dev/null +++ b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlUuid7ValueGenerator.cs @@ -0,0 +1,107 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.ValueGeneration.Internal; + +/// +/// This API supports the Entity Framework Core infrastructure and is not intended to be used +/// directly from your code. This API may change or be removed in future releases. +/// +public class NpgsqlUuid7ValueGenerator : ValueGenerator +{ + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override bool GeneratesTemporaryValues => false; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override Guid Next(EntityEntry entry) => BorrowedFromNet9.CreateVersion7(timestamp: DateTimeOffset.UtcNow); + + // Code borrowed from .NET 9 should be removed as soon as the target framework includes such code + #region Borrowed from .NET 9 + +#pragma warning disable IDE0007 // Use implicit type -- Avoid changes to code borrowed from BCL + + // https://github.com/dotnet/runtime/blob/f402418aaed508c1d77e41b942e3978675183bfc/src/libraries/System.Private.CoreLib/src/System/Guid.cs + internal static class BorrowedFromNet9 + { + private const byte Variant10xxMask = 0xC0; + private const byte Variant10xxValue = 0x80; + + private const ushort VersionMask = 0xF000; + private const ushort Version7Value = 0x7000; + + /// Creates a new according to RFC 9562, following the Version 7 format. + /// A new according to RFC 9562, following the Version 7 format. + /// + /// This uses to determine the Unix Epoch timestamp source. + /// This seeds the rand_a and rand_b sub-fields with random data. + /// + public static Guid CreateVersion7() => CreateVersion7(DateTimeOffset.UtcNow); + + /// Creates a new according to RFC 9562, following the Version 7 format. + /// The date time offset used to determine the Unix Epoch timestamp. + /// A new according to RFC 9562, following the Version 7 format. + /// represents an offset prior to . + /// + /// This seeds the rand_a and rand_b sub-fields with random data. + /// + public static Guid CreateVersion7(DateTimeOffset timestamp) + { + // NewGuid uses CoCreateGuid on Windows and Interop.GetCryptographicallySecureRandomBytes on Unix to get + // cryptographically-secure random bytes. We could use Interop.BCrypt.BCryptGenRandom to generate the random + // bytes on Windows, as is done in RandomNumberGenerator, but that's measurably slower than using CoCreateGuid. + // And while CoCreateGuid only generates 122 bits of randomness, the other 6 bits being for the version / variant + // fields, this method also needs those bits to be non-random, so we can just use NewGuid for efficiency. + var result = Guid.NewGuid(); + + // 2^48 is roughly 8925.5 years, which from the Unix Epoch means we won't + // overflow until around July of 10,895. So there isn't any need to handle + // it given that DateTimeOffset.MaxValue is December 31, 9999. However, we + // can't represent timestamps prior to the Unix Epoch since UUIDv7 explicitly + // stores a 48-bit unsigned value, so we do need to throw if one is passed in. + + var unix_ts_ms = timestamp.ToUnixTimeMilliseconds(); + ArgumentOutOfRangeException.ThrowIfNegative(unix_ts_ms, nameof(timestamp)); + + ref var resultClone = ref Unsafe.As(ref result); // Deviation from BLC: Reinterpret Guid as our own type so that we can manipulate its private fields + + Unsafe.AsRef(in resultClone._a) = (int)(unix_ts_ms >> 16); + Unsafe.AsRef(in resultClone._b) = (short)unix_ts_ms; + + Unsafe.AsRef(in resultClone._c) = (short)(resultClone._c & ~VersionMask | Version7Value); + Unsafe.AsRef(in resultClone._d) = (byte)(resultClone._d & ~Variant10xxMask | Variant10xxValue); + + return result; + } + } + + /// + /// Used to manipulate the private fields of a like its internal methods do, by treating a as a . + /// + [StructLayout(LayoutKind.Sequential)] + internal readonly struct GuidDoppleganger + { +#pragma warning disable IDE1006 // Naming Styles -- Avoid further changes to code borrowed from BCL when working with the current type + internal readonly int _a; // Do not rename (binary serialization) + internal readonly short _b; // Do not rename (binary serialization) + internal readonly short _c; // Do not rename (binary serialization) + internal readonly byte _d; // Do not rename (binary serialization) + internal readonly byte _e; // Do not rename (binary serialization) + internal readonly byte _f; // Do not rename (binary serialization) + internal readonly byte _g; // Do not rename (binary serialization) + internal readonly byte _h; // Do not rename (binary serialization) + internal readonly byte _i; // Do not rename (binary serialization) + internal readonly byte _j; // Do not rename (binary serialization) + internal readonly byte _k; // Do not rename (binary serialization) +#pragma warning restore IDE1006 // Naming Styles + } + +#pragma warning restore IDE0007 // Use implicit type + + #endregion +} diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs index 4b44c178e..2f5665be8 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs @@ -104,6 +104,6 @@ public override bool TrySelect(IProperty property, ITypeBase typeBase, out Value => property.ClrType.UnwrapNullableType() == typeof(Guid) ? property.ValueGenerated == ValueGenerated.Never || property.GetDefaultValueSql() is not null ? new TemporaryGuidValueGenerator() - : new GuidValueGenerator() + : new NpgsqlUuid7ValueGenerator() : base.FindForType(property, typeBase, clrType); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs index f74474e82..2950d6723 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs @@ -45,28 +45,28 @@ public override Task Where_mathf_round2(bool async) public override Task Convert_ToString(bool async) => AssertTranslationFailed(() => base.Convert_ToString(async)); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task String_Join_non_aggregate(bool async) - { - var param = "param"; - string nullParam = null; - - await AssertQuery( - async, - ss => ss.Set().Where( - c => string.Join("|", c.CustomerID, c.CompanyName, param, nullParam, "constant", null) - == "ALFKI|Alfreds Futterkiste|param||constant|")); - - AssertSql( - """ -@__param_0='param' - -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 concat_ws('|', c."CustomerID", c."CompanyName", COALESCE(@__param_0, ''), COALESCE(NULL, ''), 'constant', '') = 'ALFKI|Alfreds Futterkiste|param||constant|' -"""); - } +// [ConditionalTheory] +// [MemberData(nameof(IsAsyncData))] +// public virtual async Task String_Join_non_aggregate(bool async) +// { +// var param = "param"; +// string nullParam = null; +// +// await AssertQuery( +// async, +// ss => ss.Set().Where( +// c => string.Join("|", c.CustomerID, c.CompanyName, param, nullParam, "constant", null) +// == "ALFKI|Alfreds Futterkiste|param||constant|")); +// +// AssertSql( +// """ +// @__param_0='param' +// +// 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 concat_ws('|', c."CustomerID", c."CompanyName", COALESCE(@__param_0, ''), COALESCE(NULL, ''), 'constant', '') = 'ALFKI|Alfreds Futterkiste|param||constant|' +// """); +// } #region Substring diff --git a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs index 1b69e2dbe..0d95b24e8 100644 --- a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs @@ -21,7 +21,7 @@ public void Returns_built_in_generators_for_types_setup_for_value_generation() AssertGenerator("NullableByte"); AssertGenerator("Decimal"); AssertGenerator("String"); - AssertGenerator("Guid"); + AssertGenerator("Guid"); AssertGenerator("Binary"); } @@ -128,7 +128,7 @@ public void Returns_sequence_value_generators_when_configured_for_model() AssertGenerator>("NullableLong", setSequences: true); AssertGenerator>("NullableShort", setSequences: true); AssertGenerator("String", setSequences: true); - AssertGenerator("Guid", setSequences: true); + AssertGenerator("Guid", setSequences: true); AssertGenerator("Binary", setSequences: true); } @@ -210,4 +210,19 @@ public override int Next(EntityEntry entry) public override bool GeneratesTemporaryValues => false; } + + [Fact] + public void NpgsqlUuid7ValueGenerator_creates_uuidv7() + { + var dtoNow = DateTimeOffset.UtcNow; + var net9Internal = Guid.CreateVersion7(dtoNow); + var custom = NpgsqlUuid7ValueGenerator.BorrowedFromNet9.CreateVersion7(dtoNow); + var bytenet9 = net9Internal.ToByteArray().AsSpan(0, 6); + var bytecustom = custom.ToByteArray().AsSpan(0, 6); + Assert.Equal(bytenet9, bytecustom); + Assert.Equal(7, net9Internal.Version); + Assert.Equal(net9Internal.Version, custom.Version); + Assert.InRange(net9Internal.Variant, 8, 0xB); + Assert.InRange(custom.Variant, 8, 0xB); + } } From 36295e9f6fd6f5267c76a3ca95e3ae14c090086b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 1 Sep 2024 18:58:53 +0200 Subject: [PATCH 046/107] Rename NpgsqlUuid7ValueGenerator to NpgsqlSequentialGuidValueGenerator and make it public (#3255) Follow-up to #3249 --- .../Internal/NpgsqlValueGeneratorSelector.cs | 2 +- ...eGenerator.cs => NpgsqlSequentialGuidValueGenerator.cs} | 4 ++-- test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) rename src/EFCore.PG/ValueGeneration/{Internal/NpgsqlUuid7ValueGenerator.cs => NpgsqlSequentialGuidValueGenerator.cs} (97%) diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs index 2f5665be8..a9274f617 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs @@ -104,6 +104,6 @@ public override bool TrySelect(IProperty property, ITypeBase typeBase, out Value => property.ClrType.UnwrapNullableType() == typeof(Guid) ? property.ValueGenerated == ValueGenerated.Never || property.GetDefaultValueSql() is not null ? new TemporaryGuidValueGenerator() - : new NpgsqlUuid7ValueGenerator() + : new NpgsqlSequentialGuidValueGenerator() : base.FindForType(property, typeBase, clrType); } diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlUuid7ValueGenerator.cs b/src/EFCore.PG/ValueGeneration/NpgsqlSequentialGuidValueGenerator.cs similarity index 97% rename from src/EFCore.PG/ValueGeneration/Internal/NpgsqlUuid7ValueGenerator.cs rename to src/EFCore.PG/ValueGeneration/NpgsqlSequentialGuidValueGenerator.cs index 16fee666f..69d90f4f1 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlUuid7ValueGenerator.cs +++ b/src/EFCore.PG/ValueGeneration/NpgsqlSequentialGuidValueGenerator.cs @@ -1,13 +1,13 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace Npgsql.EntityFrameworkCore.PostgreSQL.ValueGeneration.Internal; +namespace Npgsql.EntityFrameworkCore.PostgreSQL.ValueGeneration; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// -public class NpgsqlUuid7ValueGenerator : ValueGenerator +public class NpgsqlSequentialGuidValueGenerator : ValueGenerator { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs index 0d95b24e8..57b0d1b9a 100644 --- a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore.ValueGeneration.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; +using Npgsql.EntityFrameworkCore.PostgreSQL.ValueGeneration; using Npgsql.EntityFrameworkCore.PostgreSQL.ValueGeneration.Internal; namespace Npgsql.EntityFrameworkCore.PostgreSQL; @@ -21,7 +22,7 @@ public void Returns_built_in_generators_for_types_setup_for_value_generation() AssertGenerator("NullableByte"); AssertGenerator("Decimal"); AssertGenerator("String"); - AssertGenerator("Guid"); + AssertGenerator("Guid"); AssertGenerator("Binary"); } @@ -128,7 +129,7 @@ public void Returns_sequence_value_generators_when_configured_for_model() AssertGenerator>("NullableLong", setSequences: true); AssertGenerator>("NullableShort", setSequences: true); AssertGenerator("String", setSequences: true); - AssertGenerator("Guid", setSequences: true); + AssertGenerator("Guid", setSequences: true); AssertGenerator("Binary", setSequences: true); } @@ -216,7 +217,7 @@ public void NpgsqlUuid7ValueGenerator_creates_uuidv7() { var dtoNow = DateTimeOffset.UtcNow; var net9Internal = Guid.CreateVersion7(dtoNow); - var custom = NpgsqlUuid7ValueGenerator.BorrowedFromNet9.CreateVersion7(dtoNow); + var custom = NpgsqlSequentialGuidValueGenerator.BorrowedFromNet9.CreateVersion7(dtoNow); var bytenet9 = net9Internal.ToByteArray().AsSpan(0, 6); var bytecustom = custom.ToByteArray().AsSpan(0, 6); Assert.Equal(bytenet9, bytecustom); From f3fb1a67e9a57fbc5d19d7da5de1cfa3f847391c Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 1 Sep 2024 20:45:31 +0200 Subject: [PATCH 047/107] Sync to EF Core 9.0.0-preview.7.24405.3 (#3258) --- Directory.Build.props | 2 +- Directory.Packages.props | 17 +- ...lNetTopologySuiteMemberTranslatorPlugin.cs | 2 +- ...TopologySuiteMethodCallTranslatorPlugin.cs | 2 +- .../NpgsqlJsonGeometryWktReaderWriter.cs | 5 + .../NpgsqlNodaTimeMemberTranslatorPlugin.cs | 2 +- .../Storage/Internal/DateMapping.cs | 5 + .../Internal/DurationIntervalMapping.cs | 5 + .../Storage/Internal/PeriodIntervalMapping.cs | 5 + .../Storage/Internal/TimeMapping.cs | 5 + .../Storage/Internal/TimeTzMapping.cs | 5 + .../Internal/TimestampLocalDateTimeMapping.cs | 5 + .../Internal/TimestampTzInstantMapping.cs | 5 + .../TimestampTzOffsetDateTimeMapping.cs | 5 + .../TimestampTzZonedDateTimeMapping.cs | 5 + src/EFCore.PG/EFCore.PG.csproj | 2 +- .../Internal/NpgsqlHistoryRepository.cs | 104 ++- .../NpgsqlMigrationsSqlGenerator.cs | 21 +- .../Internal/NpgsqlConvertTranslator.cs | 3 +- .../NpgsqlFullTextSearchMethodTranslator.cs | 8 +- .../Internal/NpgsqlJsonPocoTranslator.cs | 4 +- .../Internal/NpgsqlMathTranslator.cs | 4 +- .../NpgsqlMiscAggregateMethodTranslator.cs | 4 +- .../Internal/NpgsqlNetworkTranslator.cs | 2 +- .../NpgsqlObjectToStringTranslator.cs | 41 +- .../Internal/NpgsqlRangeTranslator.cs | 2 +- .../Internal/NpgsqlStringMemberTranslator.cs | 4 +- .../Internal/NpgsqlStringMethodTranslator.cs | 2 +- .../NpgsqlTimeSpanMemberTranslator.cs | 4 +- .../Internal/PgArraySliceExpression.cs | 4 +- .../Internal/PgFunctionExpression.cs | 1 - .../Expressions/Internal/PgILikeExpression.cs | 2 +- ...NpgsqlDeleteConvertingExpressionVisitor.cs | 4 +- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 12 +- ...ResolutionCompensatingExpressionVisitor.cs | 3 +- .../Internal/NpgsqlSqlNullabilityProcessor.cs | 241 +++--- .../NpgsqlSqlTranslatingExpressionVisitor.cs | 50 +- .../NpgsqlTypeMappingPostprocessor.cs | 4 +- .../Internal/NpgsqlUnnestPostprocessor.cs | 10 +- .../Query/NpgsqlSqlExpressionFactory.cs | 30 +- .../Internal/NpgsqlDatabaseModelFactory.cs | 2 - .../Internal/Json/JsonBitArrayReaderWriter.cs | 5 + .../Internal/Json/JsonMacaddrReaderWriter.cs | 5 + .../Mapping/NpgsqlArrayTypeMapping.cs | 17 +- .../Mapping/NpgsqlBigIntegerTypeMapping.cs | 5 + .../Internal/Mapping/NpgsqlCidrTypeMapping.cs | 5 + .../Mapping/NpgsqlDateOnlyTypeMapping.cs | 5 + .../Mapping/NpgsqlDateTimeDateTypeMapping.cs | 5 + .../Internal/Mapping/NpgsqlEnumTypeMapping.cs | 13 + .../Internal/Mapping/NpgsqlInetTypeMapping.cs | 10 + .../Mapping/NpgsqlIntervalTypeMapping.cs | 5 + .../Mapping/NpgsqlLTreeTypeMapping.cs | 5 + .../Mapping/NpgsqlPgLsnTypeMapping.cs | 5 + .../Mapping/NpgsqlTimeTzTypeMapping.cs | 5 + .../Mapping/NpgsqlTimestampTypeMapping.cs | 5 + .../Mapping/NpgsqlTimestampTzTypeMapping.cs | 12 + .../Internal/NpgsqlTypeMappingSource.cs | 12 +- .../EFCore.PG.FunctionalTests/BatchingTest.cs | 50 +- .../BuiltInDataTypesNpgsqlTest.cs | 127 ++-- .../ComplexTypeBulkUpdatesNpgsqlTest.cs | 12 +- .../NonSharedModelBulkUpdatesNpgsqlTest.cs | 20 +- .../NorthwindBulkUpdatesNpgsqlFixture.cs | 2 +- .../NorthwindBulkUpdatesNpgsqlTest.cs | 10 +- ...FiltersInheritanceBulkUpdatesNpgsqlTest.cs | 6 +- .../TPHInheritanceBulkUpdatesNpgsqlTest.cs | 14 +- .../ComputedColumnTest.cs | 14 +- .../ConferencePlannerNpgsqlTest.cs | 4 +- .../ConnectionSpecificationTest.cs | 96 +-- .../ConvertToProviderTypesNpgsqlTest.cs | 85 ++- .../CustomConvertersNpgsqlTest.cs | 15 +- .../DataAnnotationNpgsqlTest.cs | 18 +- .../EFCore.PG.FunctionalTests.csproj | 3 + .../ExistingConnectionTest.cs | 2 +- .../JsonTypesNpgsqlTest.cs | 492 ++++++------ .../MigrationsInfrastructureNpgsqlTest.cs | 19 + .../Migrations/MigrationsNpgsqlTest.cs | 105 +-- .../NpgsqlComplianceTest.cs | 7 +- .../NpgsqlDatabaseCreatorTest.cs | 24 +- .../NpgsqlValueGenerationScenariosTest.cs | 52 +- .../Query/AdHocComplexTypeQueryNpgsqlTest.cs | 32 + .../Query/AdHocJsonQueryNpgsqlTest.cs | 50 +- .../AdHocMiscellaneousQueryNpgsqlTest.cs | 4 +- .../Query/ArrayArrayQueryTest.cs | 4 +- .../Query/ArrayListQueryTest.cs | 4 +- .../Query/ArrayQueryFixture.cs | 4 +- .../Query/BigIntegerQueryTest.cs | 8 +- .../Query/CitextQueryTest.cs | 8 +- .../Query/CompatibilityQueryNpgsqlTest.cs | 9 +- ...lexNavigationsSharedTypeQueryNpgsqlTest.cs | 8 - .../Query/ComplexTypeQueryNpgsqlTest.cs | 132 +++- .../Query/EnumQueryTest.cs | 8 +- .../Query/FromSqlQueryNpgsqlTest.cs | 9 +- .../Query/FunkyDataQueryNpgsqlTest.cs | 4 +- .../Query/FuzzyStringMatchQueryNpgsqlTest.cs | 8 +- .../Query/GearsOfWarQueryNpgsqlFixture.cs | 6 +- .../Query/GearsOfWarQueryNpgsqlTest.cs | 60 +- .../Query/JsonDomQueryTest.cs | 9 +- .../Query/JsonPocoQueryTest.cs | 9 +- .../Query/JsonQueryNpgsqlTest.cs | 716 ++++++++++++++++-- .../Query/JsonStringQueryTest.cs | 9 +- .../Query/LTreeQueryTest.cs | 8 +- .../Query/LegacyTimestampQueryTest.cs | 12 +- .../Query/MathQueryTest.cs | 367 --------- .../Query/MultirangeQueryNpgsqlTest.cs | 8 +- .../Query/NetworkQueryNpgsqlTest.cs | 8 +- .../Query/NodaTimeQueryNpgsqlTest.cs | 8 +- ...aredPrimitiveCollectionsQueryNpgsqlTest.cs | 23 +- .../NorthwindDbFunctionsQueryNpgsqlTest.cs | 6 +- .../NorthwindFunctionsQueryNpgsqlTest.cs | 37 +- .../Query/NorthwindGroupByQueryNpgsqlTest.cs | 21 +- .../NorthwindMiscellaneousQueryNpgsqlTest.cs | 14 +- .../Query/NorthwindWhereQueryNpgsqlTest.cs | 32 +- .../Query/NullSemanticsQueryNpgsqlFixture.cs | 9 - .../Query/NullSemanticsQueryNpgsqlTest.cs | 26 +- .../Query/OperatorsQueryNpgsqlTest.cs | 41 +- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 447 ++++++++++- .../Query/QueryBugTest.cs | 16 +- .../Query/RangeQueryNpgsqlTest.cs | 8 +- .../Query/SharedTypeQueryNpgsqlTest.cs | 5 - .../Query/SqlQueryNpgsqlTest.cs | 7 + .../Query/TPCGearsOfWarQueryNpgsqlFixture.cs | 10 +- .../Query/TPCGearsOfWarQueryNpgsqlTest.cs | 6 +- .../Query/TPTGearsOfWarQueryNpgsqlFixture.cs | 10 +- .../Query/TPTGearsOfWarQueryNpgsqlTest.cs | 6 +- .../Query/TimestampQueryTest.cs | 8 +- .../Query/TrigramsQueryNpgsqlTest.cs | 8 +- .../Query/UdfDbFunctionNpgsqlTests.cs | 8 +- .../SequenceEndToEndTest.cs | 14 +- .../SpatialNpgsqlTest.cs | 3 +- .../StoreGeneratedFixupNpgsqlTest.cs | 8 +- .../TestModels/Array/ArrayQueryContext.cs | 8 +- .../TestUtilities/NpgsqlTestStore.cs | 226 +++--- .../Update/JsonUpdateNpgsqlTest.cs | 264 ++++--- .../ValueConvertersEndToEndNpgsqlTest.cs | 2 +- test/EFCore.PG.Tests/EFCore.PG.Tests.csproj | 4 + .../Migrations/NpgsqlHistoryRepositoryTest.cs | 1 - 136 files changed, 3005 insertions(+), 1724 deletions(-) create mode 100644 test/EFCore.PG.FunctionalTests/Query/AdHocComplexTypeQueryNpgsqlTest.cs delete mode 100644 test/EFCore.PG.FunctionalTests/Query/MathQueryTest.cs delete mode 100644 test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlFixture.cs diff --git a/Directory.Build.props b/Directory.Build.props index 59efc0cbe..64a9c63aa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0-preview.4 + 9.0.0-preview.7 latest true latest diff --git a/Directory.Packages.props b/Directory.Packages.props index 11a01c9ad..719cf3228 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - 9.0.0-preview.3.24172.4 - 9.0.0-preview.3.24172.9 + [9.0.0-preview.7.24405.3] + 9.0.0-preview.7.24405.7 8.0.3 @@ -21,14 +21,15 @@ + + + - - - - - - + + + + diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs index 1ab9fd45b..0bbeb6e0c 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs @@ -172,7 +172,7 @@ public NpgsqlGeometryMemberTranslator( _ => null }; - SqlFunctionExpression Function(string name, SqlExpression[] arguments, Type returnType, RelationalTypeMapping? typeMapping = null) + SqlExpression Function(string name, SqlExpression[] arguments, Type returnType, RelationalTypeMapping? typeMapping = null) => _sqlExpressionFactory.Function( name, arguments, nullable: true, argumentsPropagateNullability: TrueArrays[arguments.Length], diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs index 25e0b1f34..0b5afb739 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs @@ -220,7 +220,7 @@ public NpgsqlGeometryMethodTranslator( _ => null }; - SqlFunctionExpression Function(string name, SqlExpression[] arguments, Type returnType, RelationalTypeMapping? typeMapping = null) + SqlExpression Function(string name, SqlExpression[] arguments, Type returnType, RelationalTypeMapping? typeMapping = null) => _sqlExpressionFactory.Function( name, arguments, nullable: true, argumentsPropagateNullability: TrueArrays[arguments.Length], diff --git a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs index 266f14da2..3b5920e27 100644 --- a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs +++ b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs @@ -10,6 +10,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; /// public sealed class NpgsqlJsonGeometryWktReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonGeometryWktReaderWriter).GetProperty(nameof(Instance))!; + private static readonly WKTReader WktReader = new(); /// @@ -28,4 +30,7 @@ public override Geometry FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, Geometry value) => writer.WriteStringValue(value.ToText()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index f08aa47f6..e96117583 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -175,7 +175,7 @@ public NpgsqlNodaTimeMemberTranslator( _ => null, }; - SqlBinaryExpression TranslateDurationTotalMember(SqlExpression instance, double divisor) + SqlExpression TranslateDurationTotalMember(SqlExpression instance, double divisor) => _sqlExpressionFactory.Divide(GetDatePartExpressionDouble(instance, "epoch"), _sqlExpressionFactory.Constant(divisor)); } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs index 16cca6b5e..eea1ef0a5 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs @@ -116,6 +116,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonLocalDateReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLocalDateReaderWriter).GetProperty(nameof(Instance))!; + public static JsonLocalDateReaderWriter Instance { get; } = new(); public override LocalDate FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -138,5 +140,8 @@ public override LocalDate FromJsonTyped(ref Utf8JsonReaderManager manager, objec public override void ToJsonTyped(Utf8JsonWriter writer, LocalDate value) => writer.WriteStringValue(FormatLocalDate(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs index 27a2c2038..bb44e93bb 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs @@ -137,6 +137,8 @@ void Compose(Expression toAdd) private sealed class JsonDurationReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonDurationReaderWriter).GetProperty(nameof(Instance))!; + public static JsonDurationReaderWriter Instance { get; } = new(); public override Duration FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -144,5 +146,8 @@ public override Duration FromJsonTyped(ref Utf8JsonReaderManager manager, object public override void ToJsonTyped(Utf8JsonWriter writer, Duration value) => writer.WriteStringValue(NpgsqlIntervalTypeMapping.FormatTimeSpanAsInterval(value.ToTimeSpan())); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs index b46009434..bd001d58a 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs @@ -163,6 +163,8 @@ void Compose(Expression toAdd) private sealed class JsonPeriodReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonPeriodReaderWriter).GetProperty(nameof(Instance))!; + public static JsonPeriodReaderWriter Instance { get; } = new(); public override Period FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -170,5 +172,8 @@ public override Period FromJsonTyped(ref Utf8JsonReaderManager manager, object? public override void ToJsonTyped(Utf8JsonWriter writer, Period value) => writer.WriteStringValue(PeriodPattern.NormalizingIso.Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs index a9d25f72b..64c8f4c76 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs @@ -119,6 +119,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonLocalTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLocalTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonLocalTimeReaderWriter Instance { get; } = new(); public override LocalTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -126,5 +128,8 @@ public override LocalTime FromJsonTyped(ref Utf8JsonReaderManager manager, objec public override void ToJsonTyped(Utf8JsonWriter writer, LocalTime value) => writer.WriteStringValue(LocalTimePattern.ExtendedIso.Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs index 07ea8ac3a..4cf64a6d8 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs @@ -150,6 +150,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonOffsetTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonOffsetTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonOffsetTimeReaderWriter Instance { get; } = new(); public override OffsetTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -157,5 +159,8 @@ public override OffsetTime FromJsonTyped(ref Utf8JsonReaderManager manager, obje public override void ToJsonTyped(Utf8JsonWriter writer, OffsetTime value) => writer.WriteStringValue(Pattern.Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs index 6c85d56da..d9d069e0a 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs @@ -143,6 +143,8 @@ internal static Expression GenerateCodeLiteral(LocalDateTime dateTime) private sealed class JsonLocalDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLocalDateTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonLocalDateTimeReaderWriter Instance { get; } = new(); public override LocalDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -165,5 +167,8 @@ public override LocalDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, o public override void ToJsonTyped(Utf8JsonWriter writer, LocalDateTime value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs index ddeac0787..52da44391 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs @@ -125,6 +125,8 @@ private static readonly MethodInfo _fromUnixTimeTicks private sealed class JsonInstantReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonInstantReaderWriter).GetProperty(nameof(Instance))!; + public static JsonInstantReaderWriter Instance { get; } = new(); public override Instant FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -147,5 +149,8 @@ public override Instant FromJsonTyped(ref Utf8JsonReaderManager manager, object? public override void ToJsonTyped(Utf8JsonWriter writer, Instant value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs index 31d8d25fd..3fba21afe 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs @@ -123,6 +123,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonOffsetDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonOffsetDateTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonOffsetDateTimeReaderWriter Instance { get; } = new(); public override OffsetDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -130,5 +132,8 @@ public override OffsetDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, public override void ToJsonTyped(Utf8JsonWriter writer, OffsetDateTime value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs index d6e637156..03456b358 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs @@ -129,6 +129,8 @@ public override Expression GenerateCodeLiteral(object value) private sealed class JsonZonedDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonZonedDateTimeReaderWriter).GetProperty(nameof(Instance))!; + public static JsonZonedDateTimeReaderWriter Instance { get; } = new(); public override ZonedDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) @@ -136,5 +138,8 @@ public override ZonedDateTime FromJsonTyped(ref Utf8JsonReaderManager manager, o public override void ToJsonTyped(Utf8JsonWriter writer, ZonedDateTime value) => writer.WriteStringValue(Pattern.Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/EFCore.PG.csproj b/src/EFCore.PG/EFCore.PG.csproj index 030379ded..c344ed2e2 100644 --- a/src/EFCore.PG/EFCore.PG.csproj +++ b/src/EFCore.PG/EFCore.PG.csproj @@ -9,7 +9,7 @@ PostgreSQL/Npgsql provider for Entity Framework Core. npgsql;postgresql;postgres;Entity Framework Core;entity-framework-core;ef;efcore;orm;sql README.md - EF1003 + EF9100 diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index 9cf6a2ed0..beaf86d7c 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -19,6 +19,81 @@ public NpgsqlHistoryRepository(HistoryRepositoryDependencies dependencies) { } + // TODO: We override Exists() as a workaround for https://github.com/dotnet/efcore/issues/34569; this should be fixed on the EF side + // before EF 9.0 is released + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool Exists() + => Dependencies.DatabaseCreator.Exists() + && InterpretExistsResult( + Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalar( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + null, + Dependencies.CurrentContext.Context, + Dependencies.CommandLogger, CommandSource.Migrations))); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override async Task ExistsAsync(CancellationToken cancellationToken = default) + => await Dependencies.DatabaseCreator.ExistsAsync(cancellationToken).ConfigureAwait(false) + && InterpretExistsResult( + await Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalarAsync( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + null, + Dependencies.CurrentContext.Context, + Dependencies.CommandLogger, CommandSource.Migrations), + cancellationToken).ConfigureAwait(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. + /// + public override IDisposable GetDatabaseLock(TimeSpan timeout) + { + // TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a + // transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. + + // Dependencies.RawSqlCommandBuilder + // .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") + // .ExecuteNonQuery(CreateRelationalCommandParameters()); + + return new DummyDisposable(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override Task GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default) + { + // TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a + // transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. + + // await Dependencies.RawSqlCommandBuilder + // .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") + // .ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken) + // .ConfigureAwait(false); + + return Task.FromResult(new DummyDisposable()); + } + /// /// 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 @@ -83,10 +158,11 @@ public override string GetBeginIfNotExistsScript(string migrationId) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override string GetBeginIfExistsScript(string migrationId) - => $@" + => $""" DO $EF$ BEGIN - IF EXISTS(SELECT 1 FROM {SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} WHERE ""{MigrationIdColumnName}"" = '{migrationId}') THEN"; + IF EXISTS(SELECT 1 FROM {SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} WHERE "{MigrationIdColumnName}" = '{migrationId}') THEN +"""; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -95,6 +171,26 @@ public override string GetBeginIfExistsScript(string migrationId) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override string GetEndIfScript() - => @" END IF; -END $EF$;"; + => """ + END IF; +END $EF$; +"""; + + private RelationalCommandParameterObject CreateRelationalCommandParameters() + => new( + Dependencies.Connection, + null, + null, + Dependencies.CurrentContext.Context, + Dependencies.CommandLogger, CommandSource.Migrations); + + private sealed class DummyDisposable : IDisposable, IAsyncDisposable + { + public void Dispose() + { + } + + public ValueTask DisposeAsync() + => default; + } } diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index f2f1e6405..0d0969390 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -827,25 +827,6 @@ protected override void SequenceOptions( } builder.Append(operation.IsCyclic ? " CYCLE" : " NO CYCLE"); - - if (!operation.IsCached) - { - // The base implementation appends NO CACHE, which isn't supported by PG - builder - .Append(" CACHE 1"); - } - else if (operation.CacheSize != null) - { - builder - .Append(" CACHE ") - .Append(intTypeMapping.GenerateSqlLiteral(operation.CacheSize.Value)); - } - else if (forAlter) - { - // The base implementation just appends CACHE, which isn't supported by PG - builder - .Append(" CACHE 1"); - } } /// @@ -964,7 +945,7 @@ protected override void Generate( } /// - protected override void IndexOptions(CreateIndexOperation operation, IModel? model, MigrationCommandListBuilder builder) + protected override void IndexOptions(MigrationOperation operation, IModel? model, MigrationCommandListBuilder builder) { if (_postgresVersion.AtLeast(11) && operation[NpgsqlAnnotationNames.IndexInclude] is string[] { Length: > 0 } includeColumns) { diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs index 5ad41bcc8..84c27eb54 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlConvertTranslator.cs @@ -27,7 +27,8 @@ public class NpgsqlConvertTranslator : IMethodCallTranslator typeof(int), typeof(long), typeof(short), - typeof(string) + typeof(string), + typeof(object) ]; private static readonly List SupportedMethods diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs index d3e1cc758..7dc27b21c 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs @@ -148,7 +148,7 @@ public NpgsqlFullTextSearchMethodTranslator( "setweight", newArgs, nullable: true, - argumentsPropagateNullability: TrueArrays[2], + argumentsPropagateNullability: TrueArrays[newArgs.Count], method.ReturnType); } @@ -321,7 +321,7 @@ arguments[1] is SqlConstantExpression constant arguments[2] }, nullable: true, - argumentsPropagateNullability: TrueArrays[arguments.Count], + argumentsPropagateNullability: TrueArrays[2], method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model)); @@ -338,7 +338,7 @@ arguments[1] is SqlConstantExpression constant arguments[2] }, nullable: true, - argumentsPropagateNullability: TrueArrays[arguments.Count], + argumentsPropagateNullability: TrueArrays[2], method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model)); @@ -347,7 +347,7 @@ SqlExpression NonConfigAccepting(string functionName) functionName, new[] { arguments[1] }, nullable: true, - argumentsPropagateNullability: TrueArrays[arguments.Count], + argumentsPropagateNullability: TrueArrays[1], method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model)); } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs index 0b4fe3015..3a8118599 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs @@ -134,7 +134,7 @@ PgJsonTraversalExpression prevPathTraversal mapping.IsJsonb ? "jsonb_array_length" : "json_array_length", new[] { expression }, nullable: true, - argumentsPropagateNullability: TrueArrays[2], + argumentsPropagateNullability: TrueArrays[1], typeof(int)); case PgJsonTraversalExpression traversal: @@ -152,7 +152,7 @@ PgJsonTraversalExpression prevPathTraversal jsonMapping.IsJsonb ? "jsonb_array_length" : "json_array_length", new[] { newTraversal }, nullable: true, - argumentsPropagateNullability: TrueArrays[2], + argumentsPropagateNullability: TrueArrays[1], typeof(int)); default: diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs index 380463f6f..9568ba113 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs @@ -192,7 +192,7 @@ public NpgsqlMathTranslator( "trunc", new[] { argument }, nullable: true, - argumentsPropagateNullability: new[] { true, false, false }, + argumentsPropagateNullability: TrueArrays[1], argument.Type == typeof(float) ? typeof(double) : argument.Type); if (argument.Type == typeof(float)) @@ -213,7 +213,7 @@ public NpgsqlMathTranslator( "round", new[] { argument }, nullable: true, - argumentsPropagateNullability: new[] { true, true }, + argumentsPropagateNullability: TrueArrays[1], argument.Type == typeof(float) ? typeof(double) : argument.Type); if (argument.Type == typeof(float)) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs index 488994c93..1b972d54f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs @@ -78,7 +78,9 @@ public NpgsqlMiscAggregateMethodTranslator( }, source, nullable: true, - argumentsPropagateNullability: new[] { false, true }, + // string_agg can return nulls regardless of the nullability of its arguments, since if there's an aggregate predicate + // (string_agg(...) WHERE ...), it could cause there to be no elements, in which case string_agg returns null. + argumentsPropagateNullability: FalseArrays[2], typeof(string), _typeMappingSource.FindMapping("text")), // Note that string_agg returns text even if its inputs are varchar(x) _sqlExpressionFactory.Constant(string.Empty, typeof(string))); diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs index cb181134f..3f607894f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs @@ -248,7 +248,7 @@ public NpgsqlNetworkTranslator( _ => null }; - private SqlFunctionExpression NullPropagatingFunction( + private SqlExpression NullPropagatingFunction( string name, SqlExpression[] arguments, Type returnType, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs index 377939774..eddefd322 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs @@ -67,31 +67,40 @@ public NpgsqlObjectToStringTranslator(IRelationalTypeMappingSource typeMappingSo if (instance.Type == typeof(bool)) { - return instance is ColumnExpression { IsNullable: true } - ? _sqlExpressionFactory.Case( + if (instance.Type == typeof(bool)) + { + if (instance is not ColumnExpression { IsNullable: false }) + { + return _sqlExpressionFactory.Case( + instance, + new[] + { + new CaseWhenClause( + _sqlExpressionFactory.Constant(false), + _sqlExpressionFactory.Constant(false.ToString())), + new CaseWhenClause( + _sqlExpressionFactory.Constant(true), + _sqlExpressionFactory.Constant(true.ToString())) + }, + _sqlExpressionFactory.Constant(string.Empty)); + } + + return _sqlExpressionFactory.Case( new[] { new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)), - _sqlExpressionFactory.Constant(false.ToString())), - new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)), + instance, _sqlExpressionFactory.Constant(true.ToString())) }, - _sqlExpressionFactory.Constant(null, typeof(string))) - : _sqlExpressionFactory.Case( - new[] - { - new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)), - _sqlExpressionFactory.Constant(false.ToString())) - }, - _sqlExpressionFactory.Constant(true.ToString())); + _sqlExpressionFactory.Constant(false.ToString())); + } } return _typeMapping.Contains(instance.Type) || instance.Type.UnwrapNullableType().IsEnum && instance.TypeMapping is NpgsqlEnumTypeMapping - ? _sqlExpressionFactory.Convert(instance, typeof(string), _textTypeMapping) + ? _sqlExpressionFactory.Coalesce( + _sqlExpressionFactory.Convert(instance, typeof(string), _textTypeMapping), + _sqlExpressionFactory.Constant(string.Empty)) : null; } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs index 4a8cae301..9e0bb9113 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs @@ -171,7 +171,7 @@ public NpgsqlRangeTranslator( _ => null }; - SqlFunctionExpression SingleArgBoolFunction(string name, SqlExpression argument) + SqlExpression SingleArgBoolFunction(string name, SqlExpression argument) => _sqlExpressionFactory.Function( name, new[] { argument }, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs index 90efa7cb4..d56c7a9d3 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs @@ -31,11 +31,11 @@ public NpgsqlStringMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) MemberInfo member, Type returnType, IDiagnosticsLogger logger) - => member.Name == nameof(string.Length) && instance?.Type == typeof(string) + => member.Name == nameof(string.Length) && member.DeclaringType == typeof(string) ? _sqlExpressionFactory.Convert( _sqlExpressionFactory.Function( "length", - new[] { instance }, + new[] { instance! }, nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(long)), diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs index 27a02bb17..dbd1c13be 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs @@ -16,7 +16,7 @@ public class NpgsqlStringMethodTranslator : IMethodCallTranslator { private readonly ISqlExpressionFactory _sqlExpressionFactory; private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly SqlConstantExpression _whitespace; + private readonly SqlExpression _whitespace; #region MethodInfo diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs index 6efe34dae..b4b59966b 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs @@ -74,14 +74,14 @@ SqlExpression Floor(SqlExpression value) typeof(double)), typeof(int)); - SqlFunctionExpression DatePart(string part, SqlExpression value) + SqlExpression DatePart(string part, SqlExpression value) => _sqlExpressionFactory.Function( "date_part", new[] { _sqlExpressionFactory.Constant(part), value }, nullable: true, argumentsPropagateNullability: FalseTrueArray, returnType); - SqlBinaryExpression TranslateDurationTotalMember(SqlExpression instance, double divisor) + SqlExpression TranslateDurationTotalMember(SqlExpression instance, double divisor) => _sqlExpressionFactory.Divide(DatePart("epoch", instance), _sqlExpressionFactory.Constant(divisor)); } } diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs index 9c6d56fbe..c8a749402 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgArraySliceExpression.cs @@ -80,8 +80,8 @@ public override Expression Quote() _quotingConstructor ??= typeof(PgArraySliceExpression).GetConstructor( [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(bool), typeof(Type), typeof(RelationalTypeMapping)])!, Array.Quote(), - RelationalExpressionQuotingUtilities.VisitOrNull(LowerBound), - RelationalExpressionQuotingUtilities.VisitOrNull(UpperBound), + RelationalExpressionQuotingUtilities.QuoteOrNull(LowerBound), + RelationalExpressionQuotingUtilities.QuoteOrNull(UpperBound), Constant(IsNullable), Constant(Type), RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs index 7e4601d3a..8b98e44e9 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgFunctionExpression.cs @@ -6,7 +6,6 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; /// Represents a SQL function call expression, supporting PostgreSQL's named parameter notation /// (e.g. make_interval(weeks => 2) and non-comma parameter separators (e.g. position(substring in string)). /// -[DebuggerDisplay("{" + nameof(ToString) + "()}")] public class PgFunctionExpression : SqlFunctionExpression, IEquatable { /// diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs index 2304618ec..9b816886d 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgILikeExpression.cs @@ -69,7 +69,7 @@ public override Expression Quote() [typeof(SqlExpression), typeof(SqlExpression), typeof(SqlExpression), typeof(RelationalTypeMapping)])!, Match.Quote(), Pattern.Quote(), - RelationalExpressionQuotingUtilities.VisitOrNull(EscapeChar), + RelationalExpressionQuotingUtilities.QuoteOrNull(EscapeChar), RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); /// diff --git a/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs index c2712d10b..bba009ead 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlDeleteConvertingExpressionVisitor.cs @@ -41,7 +41,7 @@ protected virtual Expression VisitDelete(DeleteExpression deleteExpression) { throw new InvalidOperationException( RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration( - nameof(RelationalQueryableExtensions.ExecuteDelete))); + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))); } var fromItems = new List(); @@ -79,7 +79,7 @@ protected virtual Expression VisitDelete(DeleteExpression deleteExpression) default: throw new InvalidOperationException( RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration( - nameof(RelationalQueryableExtensions.ExecuteDelete))); + nameof(EntityFrameworkQueryableExtensions.ExecuteDelete))); } } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 5eba3759a..e81ebcf0a 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.NetworkInformation; +using System.Text.Json; using System.Text.RegularExpressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; @@ -131,6 +132,12 @@ ExpressionType.Add when ExpressionType.And when e.Type == typeof(bool) => " AND ", ExpressionType.Or when e.Type == typeof(bool) => " OR ", + + // In most databases/languages, the caret (^) is the bitwise XOR operator. But in PostgreSQL the caret is the exponentiation + // operator, and hash (#) is used instead. + ExpressionType.ExclusiveOr when e.Type == typeof(bool) => " <> ", + ExpressionType.ExclusiveOr => " # ", + _ => base.GetOperator(e) }; @@ -406,7 +413,7 @@ void LiftPredicate(TableExpressionBase joinTable) } throw new InvalidOperationException( - RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(RelationalQueryableExtensions.ExecuteUpdate))); + RelationalStrings.ExecuteOperationWithUnsupportedOperatorInSqlGeneration(nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate))); } /// @@ -1122,7 +1129,8 @@ private void GenerateJsonPath(SqlExpression expression, bool returnsText, IReadO Sql.Append(returnsText ? " #>> " : " #> "); // Use simplified array literal syntax if all path components are constants for cleaner SQL - if (path.All(p => p is SqlConstantExpression)) + if (path.All(p => p is SqlConstantExpression { Value: var pathSegment } + && (pathSegment is not string s || s.All(char.IsAsciiLetterOrDigit)))) { Sql .Append("'{") diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs index 1b7e53079..9b89c039c 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs @@ -112,8 +112,7 @@ private Expression VisitSelect(SelectExpression selectExpression) _state = parentState == State.InNestedSetOperation ? State.AlreadyCompensated : parentState; return changed - ? selectExpression.Update( - projections, tables, predicate, groupBy, havingExpression, orderings, limit, offset) + ? selectExpression.Update(tables, predicate, groupBy, havingExpression, projections, orderings, offset, limit) : selectExpression; } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs index c309220f8..09644eb44 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs @@ -29,144 +29,170 @@ protected override SqlExpression VisitSqlBinary( bool allowOptimizedExpansion, out bool nullable) { - if (sqlBinaryExpression is not + return sqlBinaryExpression switch + { { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual, Left: PgRowValueExpression leftRowValue, Right: PgRowValueExpression rightRowValue - }) + } + => VisitRowValueComparison(sqlBinaryExpression.OperatorType, leftRowValue, rightRowValue, out nullable), + + _ => base.VisitSqlBinary(sqlBinaryExpression, allowOptimizedExpansion, out nullable) + }; + + SqlExpression VisitRowValueComparison( + ExpressionType operatorType, + PgRowValueExpression leftRowValue, + PgRowValueExpression rightRowValue, + out bool nullable) { - return base.VisitSqlBinary(sqlBinaryExpression, allowOptimizedExpansion, out nullable); - } + // Equality checks between row values require some special null semantics compensation. + // Row value equality/inequality works the same as regular equals/non-equals; this means that it's fine as long as we're + // comparing non-nullable values (no need to compensate), but for nullable values, we need to compensate. We go over the value + // pairs, and extract out pairs that require compensation to an expanded, non-value-tuple expression (regular equality null + // semantics). Note that PostgreSQL does have DISTINCT FROM/NOT DISTINCT FROM which would have been perfect here, but those + // still don't use indexes. + // Note that we don't do compensation for comparisons (e.g. greater than) since these are expressed via EF.Functions, which + // correspond directly to SQL constructs. + // The PG docs around this are in https://www.postgresql.org/docs/current/functions-comparisons.html#ROW-WISE-COMPARISON + + Check.DebugAssert(leftRowValue.Values.Count == rightRowValue.Values.Count, "left.Values.Count == right.Values.Count"); + var count = leftRowValue.Values.Count; + + SqlExpression? expandedExpression = null; + List? visitedLeftValues = null; + List? visitedRightValues = null; - // Equality checks between row values require some special null semantics compensation. - // Row value equality/inequality works the same as regular equals/non-equals; this means that it's fine as long as we're comparing - // non-nullable values (no need to compensate), but for nullable values, we need to compensate. We go over the value pairs, and - // extract out pairs that require compensation to an expanded, non-value-tuple expression (regular equality null semantics). - // Note that PostgreSQL does have DISTINCT FROM/NOT DISTINCT FROM which would have been perfect here, but those still don't use - // indexes. - // Note that we don't do compensation for comparisons (e.g. greater than) since these are expressed via EF.Functions, which - // correspond directly to SQL constructs. - // The PG docs around this are in https://www.postgresql.org/docs/current/functions-comparisons.html#ROW-WISE-COMPARISON + for (var i = 0; i < count; i++) + { + // Visit the left value, populating visitedLeftValues only if we haven't yet switched to an expanded expression, and only if + // the visitation actually changed something + var leftValue = leftRowValue.Values[i]; + var rightValue = rightRowValue.Values[i]; + var visitedLeftValue = Visit(leftRowValue.Values[i], out var leftNullable); + var visitedRightValue = Visit(rightRowValue.Values[i], out var rightNullable); + + // If both sides are non-nullable, no null expansion is required; the same is true if we're doing equality in optimized + // mode, and only one side is nullable (WHERE (NonNullable1, NonNullable2) = (NonNullable3, Nullable4)). + if (!leftNullable && !rightNullable + || allowOptimizedExpansion && operatorType is ExpressionType.Equal && (!leftNullable || !rightNullable)) + { + // The comparison for this value pair doesn't require expansion and can remain in the row value (so continue below). + // But if the visitation above changed a value, construct a list to hold the visited values. + if (visitedLeftValue != leftValue && visitedLeftValues is null) + { + visitedLeftValues = SliceToList(leftRowValue.Values, count, i); + } - Check.DebugAssert(leftRowValue.Values.Count == rightRowValue.Values.Count, "left.Values.Count == right.Values.Count"); - var count = leftRowValue.Values.Count; + visitedLeftValues?.Add(visitedLeftValue); - var operatorType = sqlBinaryExpression.OperatorType; + if (visitedRightValue != rightValue && visitedRightValues is null) + { + visitedRightValues = SliceToList(rightRowValue.Values, count, i); + } - SqlExpression? expandedExpression = null; - List? visitedLeftValues = null; - List? visitedRightValues = null; + visitedRightValues?.Add(visitedRightValue); - for (var i = 0; i < count; i++) - { - // Visit the left value, populating visitedLeftValues only if we haven't yet switched to an expanded expression, and only if - // the visitation actually changed something, and - var leftValue = leftRowValue.Values[i]; - var rightValue = rightRowValue.Values[i]; - var visitedRightValue = Visit(rightRowValue.Values[i], out var rightNullable); - var visitedLeftValue = Visit(leftRowValue.Values[i], out var leftNullable); - - if (!leftNullable && !rightNullable - || allowOptimizedExpansion && (!leftNullable || !rightNullable)) - { - // The comparison for this value pair doesn't require expansion and can remain in the row value (so continue below). - // But if the visitation above changed a value, construct a list to hold the visited values. - if (visitedLeftValue != leftValue && visitedLeftValues is null) - { - visitedLeftValues = SliceToList(leftRowValue.Values, count, i); + continue; } - if (visitedLeftValues is not null) - { - visitedLeftValues.Add(visitedLeftValue); - } + // If we're here, the value pair requires null semantics compensation. We build a binary expression around the pair and + // visit that (that adds the compensation). We then chain all such expressions together with AND. + var valueBinaryExpression = Visit( + _sqlExpressionFactory.MakeBinary( + operatorType, visitedLeftValue, visitedRightValue, typeMapping: null, existingExpr: sqlBinaryExpression)!, + allowOptimizedExpansion, + out _); - if (visitedRightValue != rightValue && visitedRightValues is null) + if (expandedExpression is null) { + // visitedLeft/RightValues will contain all pairs that can remain in the row value (since they don't require + // compensation) + visitedLeftValues = SliceToList(leftRowValue.Values, count, i); visitedRightValues = SliceToList(rightRowValue.Values, count, i); - } - if (visitedRightValues is not null) + expandedExpression = valueBinaryExpression; + } + else { - visitedRightValues.Add(visitedRightValue); + expandedExpression = operatorType switch + { + ExpressionType.Equal => _sqlExpressionFactory.AndAlso(expandedExpression, valueBinaryExpression), + ExpressionType.NotEqual => _sqlExpressionFactory.OrElse(expandedExpression, valueBinaryExpression), + _ => throw new UnreachableException() + }; } - - continue; } - // If we're here, the value pair requires null semantics compensation. We build a binary expression around the pair and visit - // that (that adds the compensation). We then chain all such expressions together with AND. - var valueBinaryExpression = Visit( - _sqlExpressionFactory.MakeBinary(operatorType, visitedLeftValue, visitedRightValue, null)!, allowOptimizedExpansion, out _); + // TODO: This is wrong, need to properly calculate this: #3250 + nullable = false; if (expandedExpression is null) { - // visitedLeft/RightValues will contain all pairs that can remain in the row value (since they don't require compensation) - visitedLeftValues = SliceToList(leftRowValue.Values, count, i); - visitedRightValues = SliceToList(rightRowValue.Values, count, i); - - expandedExpression = valueBinaryExpression; + // No pairs required compensation, so they all stay in the row value. + // Either return the original binary expression (if no value visitation changed anything), or construct a new one over the + // visited values. + return visitedLeftValues is null && visitedRightValues is null + ? sqlBinaryExpression + : _sqlExpressionFactory.MakeBinary( + operatorType, + visitedLeftValues is null + ? leftRowValue + : new PgRowValueExpression(visitedLeftValues, leftRowValue.Type, leftRowValue.TypeMapping), + visitedRightValues is null + ? rightRowValue + : new PgRowValueExpression(visitedRightValues, leftRowValue.Type, leftRowValue.TypeMapping), + typeMapping: null, + existingExpr: sqlBinaryExpression)!; } - else + + Check.DebugAssert(visitedLeftValues is not null, "visitedLeftValues is not null"); + Check.DebugAssert(visitedRightValues is not null, "visitedRightValues is not null"); + + // All pairs required compensation - none are left in the row value comparison. Just return the expanded expression. + if (visitedLeftValues.Count is 0) { - expandedExpression = _sqlExpressionFactory.AndAlso(expandedExpression, valueBinaryExpression); + return expandedExpression; } - } - - nullable = false; - if (expandedExpression is null) - { - // No pairs required compensation, so they all stay in the row value. - // Either return the original binary expression (if no value visitation changed anything), or construct a new one over the - // visited values. - return visitedLeftValues is null && visitedRightValues is null - ? sqlBinaryExpression + // Some pairs required compensation; we're going to return a combined expression of the unexpanded pairs (which didn't require + // compensation) and of the expanded expression. + // If there's only one unexpanded pair left, that's a special case that doesn't require row values (just a regular pair + // comparison). Otherwise create the new row comparison expression for the unexpanded pairs. + var unexpandedExpression = visitedLeftValues.Count is 1 + ? _sqlExpressionFactory.MakeBinary(operatorType, visitedLeftValues[0], visitedRightValues[0], typeMapping: null)! : _sqlExpressionFactory.MakeBinary( operatorType, - visitedLeftValues is null - ? leftRowValue - : new PgRowValueExpression(visitedLeftValues, leftRowValue.Type, leftRowValue.TypeMapping), - visitedRightValues is null - ? rightRowValue - : new PgRowValueExpression(visitedRightValues, leftRowValue.Type, leftRowValue.TypeMapping), - null)!; - } - - Check.DebugAssert(visitedLeftValues is not null, "visitedLeftValues is not null"); - Check.DebugAssert(visitedRightValues is not null, "visitedRightValues is not null"); + new PgRowValueExpression(visitedLeftValues, leftRowValue.Type, leftRowValue.TypeMapping), + new PgRowValueExpression(visitedRightValues, rightRowValue.Type, rightRowValue.TypeMapping), + typeMapping: null)!; - // Some pairs required compensation. Combine the pairs which didn't (in visitedLeft/RightValues) with expandedExpression - // (which contains the logic for those that did). - return visitedLeftValues.Count switch - { - 0 => expandedExpression, - 1 => _sqlExpressionFactory.AndAlso( - _sqlExpressionFactory.MakeBinary(operatorType, visitedLeftValues[0], visitedRightValues[0], null)!, - expandedExpression), // Technically the CLR type and type mappings are incorrect, as we're truncating the row values. // But that shouldn't matter. - _ => _sqlExpressionFactory.AndAlso( - _sqlExpressionFactory.MakeBinary( - sqlBinaryExpression.OperatorType, - new PgRowValueExpression(visitedLeftValues, leftRowValue.Type, leftRowValue.TypeMapping), - new PgRowValueExpression(visitedRightValues, rightRowValue.Type, rightRowValue.TypeMapping), - null)!, - expandedExpression) - }; + return _sqlExpressionFactory.MakeBinary( + operatorType: operatorType switch + { + ExpressionType.Equal => ExpressionType.AndAlso, + ExpressionType.NotEqual => ExpressionType.OrElse, + _ => throw new UnreachableException() + }, + unexpandedExpression, + expandedExpression, + typeMapping: null)!; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static List SliceToList(IReadOnlyList source, int capacity, int count) + { + var list = new List(capacity); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static List SliceToList(IReadOnlyList source, int capacity, int count) - { - var list = new List(capacity); + for (var i = 0; i < count; i++) + { + list.Add(source[i]); + } - for (var i = 0; i < count; i++) - { - list.Add(source[i]); + return list; } - - return list; } } @@ -514,13 +540,10 @@ protected virtual SqlExpression VisitILike(PgILikeExpression iLikeExpression, bo result = _sqlExpressionFactory.AndAlso(result, GenerateNotNullCheck(escapeChar)); } + // TODO: This revisits the operand; ideally we'd call ProcessNullNotNull directly but that's private SqlExpression GenerateNotNullCheck(SqlExpression operand) - => OptimizeNonNullableNotExpression( - _sqlExpressionFactory.Not( - VisitSqlUnary( - _sqlExpressionFactory.IsNull(operand), - allowOptimizedExpansion: false, - out _))); + => _sqlExpressionFactory.Not( + Visit(_sqlExpressionFactory.IsNull(operand), allowOptimizedExpansion, out _)); } return result; diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 6234f7097..9c4029c40 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -346,11 +346,13 @@ protected override Expression VisitNew(NewExpression newExpression) rewrittenArguments.Add(_sqlExpressionFactory.Constant("UTC")); } - return kind == DateTimeKind.Utc - ? _sqlExpressionFactory.Function( - "make_timestamptz", rewrittenArguments, nullable: true, TrueArrays[8], typeof(DateTime), _timestampTzMapping) - : _sqlExpressionFactory.Function( - "make_timestamp", rewrittenArguments, nullable: true, TrueArrays[7], typeof(DateTime), _timestampMapping); + return _sqlExpressionFactory.Function( + kind == DateTimeKind.Utc ? "make_timestamptz" : "make_timestamp", + rewrittenArguments, + nullable: true, + TrueArrays[rewrittenArguments.Count], + typeof(DateTime), + kind == DateTimeKind.Utc ? _timestampTzMapping : _timestampMapping); } } @@ -576,6 +578,44 @@ private static string EscapeLikePattern(string pattern) #endregion StartsWith/EndsWith/Contains + #region GREATEST/LEAST + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override SqlExpression GenerateGreatest(IReadOnlyList expressions, Type resultType) + { + // Docs: https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-GREATEST-LEAST + var resultTypeMapping = ExpressionExtensions.InferTypeMapping(expressions); + + // If one or more arguments aren't NULL, then NULL arguments are ignored during comparison. + // If all arguments are NULL, then GREATEST returns NULL. + return _sqlExpressionFactory.Function( + "GREATEST", expressions, nullable: true, Enumerable.Repeat(false, expressions.Count), resultType, resultTypeMapping); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override SqlExpression GenerateLeast(IReadOnlyList expressions, Type resultType) + { + // Docs: https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-GREATEST-LEAST + var resultTypeMapping = ExpressionExtensions.InferTypeMapping(expressions); + + // If one or more arguments aren't NULL, then NULL arguments are ignored during comparison. + // If all arguments are NULL, then LEAST returns NULL. + return _sqlExpressionFactory.Function( + "LEAST", expressions, nullable: true, Enumerable.Repeat(false, expressions.Count), resultType, resultTypeMapping); + } + + #endregion GREATEST/LEAST + #region Copied from RelationalSqlTranslatingExpressionVisitor private static Expression TryRemoveImplicitConvert(Expression expression) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs index ba50c0577..62a6444ec 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlTypeMappingPostprocessor.cs @@ -10,6 +10,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; /// public class NpgsqlTypeMappingPostprocessor : RelationalTypeMappingPostprocessor { + private readonly IModel _model; private readonly IRelationalTypeMappingSource _typeMappingSource; private readonly ISqlExpressionFactory _sqlExpressionFactory; @@ -25,6 +26,7 @@ public NpgsqlTypeMappingPostprocessor( RelationalQueryCompilationContext queryCompilationContext) : base(dependencies, relationalDependencies, queryCompilationContext) { + _model = queryCompilationContext.Model; _typeMappingSource = relationalDependencies.TypeMappingSource; _sqlExpressionFactory = relationalDependencies.SqlExpressionFactory; } @@ -42,7 +44,7 @@ protected override Expression VisitExtension(Expression expression) case PgUnnestExpression unnestExpression when TryGetInferredTypeMapping(unnestExpression.Alias, unnestExpression.ColumnName, out var elementTypeMapping): { - var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, Model, elementTypeMapping); + var collectionTypeMapping = _typeMappingSource.FindMapping(unnestExpression.Array.Type, _model, elementTypeMapping); if (collectionTypeMapping is null) { diff --git a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs index 4b1a5d38d..b4a1d578c 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlUnnestPostprocessor.cs @@ -27,7 +27,9 @@ public class NpgsqlUnnestPostprocessor : ExpressionVisitor switch (expression) { case ShapedQueryExpression shapedQueryExpression: - return shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)); + return shapedQueryExpression + .UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression)) + .UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression)); case SelectExpression selectExpression: { @@ -83,14 +85,14 @@ bool IsOrdinalityColumn(SqlExpression expression) newTables is null ? selectExpression : selectExpression.Update( - selectExpression.Projection, newTables, selectExpression.Predicate, selectExpression.GroupBy, selectExpression.Having, + selectExpression.Projection, orderings, - selectExpression.Limit, - selectExpression.Offset)); + selectExpression.Offset, + selectExpression.Limit)); } default: diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 368ddc238..84fac6794 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -225,11 +225,12 @@ public virtual PgNewArrayExpression NewArray( => (PgNewArrayExpression)ApplyTypeMapping(new PgNewArrayExpression(expressions, type, typeMapping), typeMapping); /// - public override SqlBinaryExpression? MakeBinary( + public override SqlExpression? MakeBinary( ExpressionType operatorType, SqlExpression left, SqlExpression right, - RelationalTypeMapping? typeMapping) + RelationalTypeMapping? typeMapping, + SqlExpression? existingExpr = null) { switch (operatorType) { @@ -268,7 +269,7 @@ public virtual PgNewArrayExpression NewArray( } } - return base.MakeBinary(operatorType, left, right, typeMapping); + return base.MakeBinary(operatorType, left, right, typeMapping, existingExpr); } /// @@ -279,7 +280,7 @@ public virtual PgNewArrayExpression NewArray( /// The right operand of binary operation. /// A type mapping to be assigned to the created expression. /// A with the given arguments. - public virtual PgBinaryExpression MakePostgresBinary( + public virtual SqlExpression MakePostgresBinary( PgExpressionType operatorType, SqlExpression left, SqlExpression right, @@ -321,7 +322,7 @@ public virtual PgBinaryExpression MakePostgresBinary( /// /// Creates a new , for checking whether one value contains another. /// - public virtual PgBinaryExpression Contains(SqlExpression left, SqlExpression right) + public virtual SqlExpression Contains(SqlExpression left, SqlExpression right) { Check.NotNull(left, nameof(left)); Check.NotNull(right, nameof(right)); @@ -332,7 +333,7 @@ public virtual PgBinaryExpression Contains(SqlExpression left, SqlExpression rig /// /// Creates a new , for checking whether one value is contained by another. /// - public virtual PgBinaryExpression ContainedBy(SqlExpression left, SqlExpression right) + public virtual SqlExpression ContainedBy(SqlExpression left, SqlExpression right) { Check.NotNull(left, nameof(left)); Check.NotNull(right, nameof(right)); @@ -343,7 +344,7 @@ public virtual PgBinaryExpression ContainedBy(SqlExpression left, SqlExpression /// /// Creates a new , for checking whether one value overlaps with another. /// - public virtual PgBinaryExpression Overlaps(SqlExpression left, SqlExpression right) + public virtual SqlExpression Overlaps(SqlExpression left, SqlExpression right) { Check.NotNull(left, nameof(left)); Check.NotNull(right, nameof(right)); @@ -495,8 +496,19 @@ private SqlBinaryExpression ApplyTypeMappingOnSqlBinary(SqlBinaryExpression bina { var updatedElementBinaryExpression = MakeBinary(binary.OperatorType, leftValues[i], rightValues[i], typeMapping: null)!; - updatedLeftValues[i] = updatedElementBinaryExpression.Left; - updatedRightValues[i] = updatedElementBinaryExpression.Right; + if (updatedElementBinaryExpression is not SqlBinaryExpression + { + Left: var updatedLeft, + Right: var updatedRight, + OperatorType: var updatedOperatorType + } + || updatedOperatorType != binary.OperatorType) + { + throw new UnreachableException("MakeBinary modified binary expression type/operator when doing row value comparison"); + } + + updatedLeftValues[i] = updatedLeft; + updatedRightValues[i] = updatedRight; } // Note that we always return non-constant PostgresRowValueExpression operands, even if the original input was a diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 37ea2c68a..4be6a4d0c 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -1021,8 +1021,6 @@ WHERE NOT EXISTS (SELECT * FROM pg_depend AS dep WHERE dep.objid = cls.oid AND d MaxValue = seqInfo.MaxValue, IncrementBy = (int?)seqInfo.IncrementBy, IsCyclic = seqInfo.IsCyclic, - IsCached = seqInfo.CacheSize is not null, - CacheSize = seqInfo.CacheSize }; yield return sequence; diff --git a/src/EFCore.PG/Storage/Internal/Json/JsonBitArrayReaderWriter.cs b/src/EFCore.PG/Storage/Internal/Json/JsonBitArrayReaderWriter.cs index f74691d7e..eeb293210 100644 --- a/src/EFCore.PG/Storage/Internal/Json/JsonBitArrayReaderWriter.cs +++ b/src/EFCore.PG/Storage/Internal/Json/JsonBitArrayReaderWriter.cs @@ -12,6 +12,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Json; /// public sealed class JsonBitArrayReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonBitArrayReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -51,4 +53,7 @@ public override void ToJsonTyped(Utf8JsonWriter writer, BitArray value) s[i] = a[i] ? '1' : '0'; } })); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } diff --git a/src/EFCore.PG/Storage/Internal/Json/JsonMacaddrReaderWriter.cs b/src/EFCore.PG/Storage/Internal/Json/JsonMacaddrReaderWriter.cs index d2a9d4aed..ca1aa2bc6 100644 --- a/src/EFCore.PG/Storage/Internal/Json/JsonMacaddrReaderWriter.cs +++ b/src/EFCore.PG/Storage/Internal/Json/JsonMacaddrReaderWriter.cs @@ -12,6 +12,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Json; /// public sealed class JsonMacaddrReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonMacaddrReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -31,4 +33,7 @@ public override PhysicalAddress FromJsonTyped(ref Utf8JsonReaderManager manager, /// public override void ToJsonTyped(Utf8JsonWriter writer, PhysicalAddress value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index ef0e1aa0d..6b70853b3 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -133,10 +133,11 @@ private static RelationalTypeMappingParameters CreateParameters(string storeType ? null // TODO: Value comparer for multidimensional arrays : (ValueComparer?)Activator.CreateInstance( elementType.IsNullableValueType() - ? typeof(NullableValueTypeListComparer<>).MakeGenericType(elementType.UnwrapNullableType()) - : elementMapping.Comparer.Type.IsAssignableFrom(elementType) - ? typeof(ListComparer<>).MakeGenericType(elementType) - : typeof(ObjectListComparer<>).MakeGenericType(elementType), + ? typeof(ListOfNullableValueTypesComparer<,>) + .MakeGenericType(typeof(TConcreteCollection), elementType.UnwrapNullableType()) + : elementType.IsValueType + ? typeof(ListOfValueTypesComparer<,>).MakeGenericType(typeof(TConcreteCollection), elementType) + : typeof(ListOfReferenceTypesComparer<,>).MakeGenericType(typeof(TConcreteCollection), elementType), elementMapping.Comparer.ToNullableComparer(elementType)!); #pragma warning restore EF1001 @@ -155,9 +156,11 @@ private static RelationalTypeMappingParameters CreateParameters(string storeType ? null : (JsonValueReaderWriter?)Activator.CreateInstance( (elementType.IsNullableValueType() - ? typeof(JsonNullableStructCollectionReaderWriter<,,>) - : typeof(JsonCollectionReaderWriter<,,>)) - .MakeGenericType(typeof(TCollection), typeof(TConcreteCollection), elementType.UnwrapNullableType()), + ? typeof(JsonCollectionOfNullableStructsReaderWriter<,>) + : elementType.IsValueType + ? typeof(JsonCollectionOfStructsReaderWriter<,>) + : typeof(JsonCollectionOfReferencesReaderWriter<,>)) + .MakeGenericType(typeof(TConcreteCollection), elementType.UnwrapNullableType()), elementJsonReaderWriter); return new RelationalTypeMappingParameters( diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs index 11a18a0c2..4be7eb2f0 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlBigIntegerTypeMapping.cs @@ -72,6 +72,8 @@ protected override string ProcessStoreType(RelationalTypeMappingParameters param /// public sealed class JsonBigIntegerReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonBigIntegerReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -98,5 +100,8 @@ public override BigInteger FromJsonTyped(ref Utf8JsonReaderManager manager, obje /// public override void ToJsonTyped(Utf8JsonWriter writer, BigInteger value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs index a07b54d41..7a943d887 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs @@ -91,6 +91,8 @@ public override Expression GenerateCodeLiteral(object value) /// public sealed class JsonCidrReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonCidrReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -116,5 +118,8 @@ public override NpgsqlCidr FromJsonTyped(ref Utf8JsonReaderManager manager, obje /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlCidr value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs index 55d181bec..3b6be8c3b 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateOnlyTypeMapping.cs @@ -95,6 +95,8 @@ private static string Format(DateOnly date) /// public sealed class NpgsqlJsonDateOnlyReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonDateOnlyReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -135,5 +137,8 @@ public override DateOnly FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateOnly value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs index dba554f11..c966b483b 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlDateTimeDateTypeMapping.cs @@ -95,6 +95,8 @@ private static string Format(DateTime date) /// public sealed class NpgsqlJsonDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonDateTimeReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -135,5 +137,8 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs index faf8cc37b..108892bb3 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs @@ -150,6 +150,16 @@ protected override string GenerateNonNullSqlLiteral(object value) public sealed class JsonPgEnumReaderWriter : JsonValueReaderWriter where T : struct, Enum { + private static readonly PropertyInfo InstanceProperty = typeof(JsonPgEnumReaderWriter).GetProperty(nameof(Instance))!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static JsonPgEnumReaderWriter Instance { get; } = new(); + /// /// 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 @@ -167,5 +177,8 @@ public override T FromJsonTyped(ref Utf8JsonReaderManager manager, object? exist /// public override void ToJsonTyped(Utf8JsonWriter writer, T value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs index 90a6edb9d..ad99bd3c6 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlInetTypeMapping.cs @@ -93,6 +93,8 @@ public override Expression GenerateCodeLiteral(object value) /// public sealed class JsonIPAddressReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonIPAddressReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -118,6 +120,9 @@ public override IPAddress FromJsonTyped(ref Utf8JsonReaderManager manager, objec /// public override void ToJsonTyped(Utf8JsonWriter writer, IPAddress value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } /// @@ -128,6 +133,8 @@ public override void ToJsonTyped(Utf8JsonWriter writer, IPAddress value) /// public sealed class JsonNpgsqlInetReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonNpgsqlInetReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -153,5 +160,8 @@ public override NpgsqlInet FromJsonTyped(ref Utf8JsonReaderManager manager, obje /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlInet value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs index 6c4706a7f..c5846088d 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs @@ -163,6 +163,8 @@ public static TimeSpan ParseIntervalAsTimeSpan(ReadOnlySpan s) /// public sealed class NpgsqlJsonTimeSpanReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonTimeSpanReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -188,5 +190,8 @@ public override TimeSpan FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, TimeSpan value) => writer.WriteStringValue(FormatTimeSpanAsInterval(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs index a761cffa7..8f31c436f 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlLTreeTypeMapping.cs @@ -76,6 +76,8 @@ public override Expression GenerateCodeLiteral(object value) /// public sealed class JsonLTreeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLTreeReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -101,5 +103,8 @@ public override LTree FromJsonTyped(ref Utf8JsonReaderManager manager, object? e /// public override void ToJsonTyped(Utf8JsonWriter writer, LTree value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs index 7dcb7279e..f107520cb 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlPgLsnTypeMapping.cs @@ -91,6 +91,8 @@ public override Expression GenerateCodeLiteral(object value) /// public sealed class JsonLogSequenceNumberReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonLogSequenceNumberReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -120,5 +122,8 @@ public override NpgsqlLogSequenceNumber FromJsonTyped(ref Utf8JsonReaderManager /// public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlLogSequenceNumber value) => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs index fba3bf838..fad0f4143 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimeTzTypeMapping.cs @@ -85,6 +85,8 @@ protected override string GenerateEmbeddedNonNullSqlLiteral(object value) /// public sealed class JsonTimeTzReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(JsonTimeTzReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -114,5 +116,8 @@ public override DateTimeOffset FromJsonTyped(ref Utf8JsonReaderManager manager, /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTimeOffset value) => writer.WriteStringValue(value.ToString("HH:mm:ss.FFFFFFz")); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs index 0977641ba..c1e4ac41f 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs @@ -109,6 +109,8 @@ private static string FormatDateTime(DateTime dateTime) /// public sealed class NpgsqlJsonTimestampReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty = typeof(NpgsqlJsonTimestampReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -149,5 +151,8 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(FormatDateTime(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs index 8f3a09c20..0cba8cfe3 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs @@ -154,6 +154,9 @@ private static string Format(DateTimeOffset dateTimeOffset) /// public sealed class NpgsqlJsonTimestampTzDateTimeReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty + = typeof(NpgsqlJsonTimestampTzDateTimeReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -196,6 +199,9 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } /// @@ -206,6 +212,9 @@ public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) /// public sealed class NpgsqlJsonTimestampTzDateTimeOffsetReaderWriter : JsonValueReaderWriter { + private static readonly PropertyInfo InstanceProperty + = typeof(NpgsqlJsonTimestampTzDateTimeOffsetReaderWriter).GetProperty(nameof(Instance))!; + /// /// 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 @@ -246,5 +255,8 @@ public override DateTimeOffset FromJsonTyped(ref Utf8JsonReaderManager manager, /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTimeOffset value) => writer.WriteStringValue(Format(value)); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); } } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 5129fdb7c..d1b922231 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -441,11 +441,10 @@ public NpgsqlTypeMappingSource( } } - // TODO: the following is a workaround/hack for https://github.com/dotnet/efcore/issues/31505 if ((storeTypeName.EndsWith("[]", StringComparison.Ordinal) || storeTypeName is "int4multirange" or "int8multirange" or "nummultirange" or "datemultirange" or "tsmultirange" or "tstzmultirange") - && FindCollectionMapping(mappingInfo, mappingInfo.ClrType!, providerType: null, elementMapping: null) is + && FindCollectionMapping(mappingInfo, mappingInfo.ClrType, providerType: null, elementMapping: null) is RelationalTypeMapping collectionMapping) { return collectionMapping; @@ -460,7 +459,7 @@ public NpgsqlTypeMappingSource( if (ClrTypeMappings.TryGetValue(clrType, out var mapping)) { // Handle types with the size facet (string, bitarray) - if (mappingInfo.Size is > 0) + if (mappingInfo.Size > 0) { if (clrType == typeof(string)) { @@ -526,7 +525,9 @@ public NpgsqlTypeMappingSource( /// protected override RelationalTypeMapping? FindCollectionMapping( RelationalTypeMappingInfo info, - Type modelType, + // Note that modelType is nullable (in the relational base signature it isn't) because of array scaffolding, i.e. we call + // FindCollectionMapping from our own FindMapping, where the clrType is null when scaffolding. + Type? modelType, Type? providerType, CoreTypeMapping? elementMapping) { @@ -538,9 +539,6 @@ public NpgsqlTypeMappingSource( Type concreteCollectionType; Type? elementType = null; - // TODO: modelType can be null (contrary to nullable annotations) only because of https://github.com/dotnet/efcore/issues/31505, - // i.e. we call into here - // If there's a CLR type (i.e. not reverse-engineering), check that it's a compatible enumerable. if (modelType is not null) { // We do GetElementType for multidimensional arrays - these don't implement generic IEnumerable<> diff --git a/test/EFCore.PG.FunctionalTests/BatchingTest.cs b/test/EFCore.PG.FunctionalTests/BatchingTest.cs index a8cd3516f..f7c87c74d 100644 --- a/test/EFCore.PG.FunctionalTests/BatchingTest.cs +++ b/test/EFCore.PG.FunctionalTests/BatchingTest.cs @@ -20,11 +20,11 @@ public class BatchingTest(BatchingTest.BatchingTestFixture fixture) : IClassFixt [InlineData(false, true, false)] [InlineData(true, false, false)] [InlineData(false, false, false)] - public void Inserts_are_batched_correctly(bool clientPk, bool clientFk, bool clientOrder) + public async Task Inserts_are_batched_correctly(bool clientPk, bool clientFk, bool clientOrder) { var expectedBlogs = new List(); - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var owner1 = new Owner(); var owner2 = new Owner(); @@ -53,18 +53,18 @@ public void Inserts_are_batched_correctly(bool clientPk, bool clientFk, bool cli expectedBlogs.Add(blog); } - context.SaveChanges(); + await context.SaveChangesAsync(); }, context => AssertDatabaseState(context, clientOrder, expectedBlogs)); } [Fact] - public void Inserts_and_updates_are_batched_correctly() + public async Task Inserts_and_updates_are_batched_correctly() { var expectedBlogs = new List(); - ExecuteWithStrategyInTransaction( - context => + await ExecuteWithStrategyInTransactionAsync( + async context => { var owner1 = new Owner { Name = "0" }; var owner2 = new Owner { Name = "1" }; @@ -106,35 +106,35 @@ public void Inserts_and_updates_are_batched_correctly() context.Set().Add(blog3); expectedBlogs.Add(blog3); - context.SaveChanges(); + await context.SaveChangesAsync(); }, context => AssertDatabaseState(context, true, expectedBlogs)); } [Fact] - public void Inserts_when_database_type_is_different() - => ExecuteWithStrategyInTransaction( - context => + public Task Inserts_when_database_type_is_different() + => ExecuteWithStrategyInTransactionAsync( + async context => { var owner1 = new Owner { Id = "0", Name = "Zero" }; var owner2 = new Owner { Id = "A", Name = string.Join("", Enumerable.Repeat('A', 900)) }; context.Owners.Add(owner1); context.Owners.Add(owner2); - context.SaveChanges(); + await context.SaveChangesAsync(); }, - context => Assert.Equal(2, context.Owners.Count())); + async context => Assert.Equal(2, await context.Owners.CountAsync())); [ConditionalTheory] [InlineData(3)] [InlineData(4)] - public void Inserts_are_batched_only_when_necessary(int minBatchSize) + public Task Inserts_are_batched_only_when_necessary(int minBatchSize) { var expectedBlogs = new List(); - TestHelpers.ExecuteWithStrategyInTransaction( + return TestHelpers.ExecuteWithStrategyInTransactionAsync( () => (BloggingContext)Fixture.CreateContext(minBatchSize), UseTransaction, - context => + async context => { var owner = new Owner(); context.Owners.Add(owner); @@ -149,7 +149,7 @@ public void Inserts_are_batched_only_when_necessary(int minBatchSize) Fixture.TestSqlLoggerFactory.Clear(); - context.SaveChanges(); + await context.SaveChangesAsync(); Assert.Contains( minBatchSize == 3 @@ -163,13 +163,13 @@ public void Inserts_are_batched_only_when_necessary(int minBatchSize) }, context => AssertDatabaseState(context, false, expectedBlogs)); } - private void AssertDatabaseState(DbContext context, bool clientOrder, List expectedBlogs) + private async Task AssertDatabaseState(DbContext context, bool clientOrder, List expectedBlogs) { expectedBlogs = clientOrder ? expectedBlogs.OrderBy(b => b.Order).ToList() : expectedBlogs.OrderBy(b => b.Id).ToList(); var actualBlogs = clientOrder - ? context.Set().OrderBy(b => b.Order).ToList() + ? await context.Set().OrderBy(b => b.Order).ToListAsync() : expectedBlogs.OrderBy(b => b.Id).ToList(); Assert.Equal(expectedBlogs.Count, actualBlogs.Count); @@ -187,10 +187,10 @@ private void AssertDatabaseState(DbContext context, bool clientOrder, List private BloggingContext CreateContext() => (BloggingContext)Fixture.CreateContext(); - private void ExecuteWithStrategyInTransaction( - Action testOperation, - Action nestedTestOperation) - => TestHelpers.ExecuteWithStrategyInTransaction( + private Task ExecuteWithStrategyInTransactionAsync( + Func testOperation, + Func nestedTestOperation) + => TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, UseTransaction, testOperation, nestedTestOperation); protected void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) @@ -254,8 +254,8 @@ protected override ITestStoreFactory TestStoreFactory protected override bool ShouldLogCategory(string logCategory) => logCategory == DbLoggerCategory.Update.Name; - protected override void Seed(PoolableDbContext context) - => context.Database.EnsureCreatedResiliently(); + protected override Task SeedAsync(PoolableDbContext context) + => context.Database.EnsureCreatedResilientlyAsync(); public DbContext CreateContext(int minBatchSize) { diff --git a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs index 69125d02e..62ba84946 100644 --- a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs @@ -717,88 +717,102 @@ private static void AssertNullMappedNullableDataTypes(MappedNullableDataTypes en Assert.Null(entity.Mood); } - public override void Can_query_with_null_parameters_using_any_nullable_data_type() + public override async Task Can_query_with_null_parameters_using_any_nullable_data_type() { using (var context = CreateContext()) { context.Set().Add( new BuiltInNullableDataTypes { Id = 711 }); - Assert.Equal(1, context.SaveChanges()); + Assert.Equal(1, await context.SaveChangesAsync()); } using (var context = CreateContext()) { - var entity = context.Set().Where(e => e.Id == 711).ToList().Single(); + var entity = (await context.Set().Where(e => e.Id == 711).ToListAsync()).Single(); short? param1 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableInt16 == param1).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableInt16 == param1).ToListAsync()) + .Single()); Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && (long?)e.TestNullableInt16 == param1).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && (long?)e.TestNullableInt16 == param1) + .ToListAsync()) + .Single()); int? param2 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableInt32 == param2).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableInt32 == param2).ToListAsync()) + .Single()); long? param3 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableInt64 == param3).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableInt64 == param3).ToListAsync()) + .Single()); double? param4 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableDouble == param4).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableDouble == param4).ToListAsync()) + .Single()); decimal? param5 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableDecimal == param5).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableDecimal == param5).ToListAsync()) + .Single()); DateTime? param6 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableDateTime == param6).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableDateTime == param6).ToListAsync()) + .Single()); // We don't support DateTimeOffset TimeSpan? param8 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableTimeSpan == param8).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableTimeSpan == param8).ToListAsync()) + .Single()); float? param9 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableSingle == param9).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableSingle == param9).ToListAsync()) + .Single()); bool? param10 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableBoolean == param10).ToList().Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableBoolean == param10).ToListAsync()) + .Single()); // We don't support byte Enum64? param12 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.Enum64 == param12).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.Enum64 == param12).ToListAsync()).Single()); Enum32? param13 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.Enum32 == param13).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.Enum32 == param13).ToListAsync()).Single()); Enum16? param14 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.Enum16 == param14).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.Enum16 == param14).ToListAsync()).Single()); Enum8? param15 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.Enum8 == param15).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.Enum8 == param15).ToListAsync()).Single()); var entityType = context.Model.FindEntityType(typeof(BuiltInNullableDataTypes)); if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableUnsignedInt16)) is not null) @@ -806,8 +820,9 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type ushort? param16 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt16 == param16).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt16 == param16) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableUnsignedInt32)) is not null) @@ -815,8 +830,9 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type uint? param17 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt32 == param17).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt32 == param17) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableUnsignedInt64)) is not null) @@ -824,8 +840,9 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type ulong? param18 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt64 == param18).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableUnsignedInt64 == param18) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableCharacter)) is not null) @@ -833,8 +850,9 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type char? param19 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableCharacter == param19).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableCharacter == param19) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.TestNullableSignedByte)) is not null) @@ -842,57 +860,68 @@ public override void Can_query_with_null_parameters_using_any_nullable_data_type sbyte? param20 = null; Assert.Same( entity, - context.Set().Where(e => e.Id == 711 && e.TestNullableSignedByte == param20).ToList() - .Single()); + (await context.Set().Where(e => e.Id == 711 && e.TestNullableSignedByte == param20) + .ToListAsync()) + .Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.EnumU64)) is not null) { EnumU64? param21 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.EnumU64 == param21).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.EnumU64 == param21).ToListAsync()).Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.EnumU32)) is not null) { EnumU32? param22 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.EnumU32 == param22).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.EnumU32 == param22).ToListAsync()).Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.EnumU16)) is not null) { EnumU16? param23 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.EnumU16 == param23).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.EnumU16 == param23).ToListAsync()).Single()); } if (entityType.FindProperty(nameof(BuiltInNullableDataTypes.EnumS8)) is not null) { EnumS8? param24 = null; Assert.Same( - entity, context.Set().Where(e => e.Id == 711 && e.EnumS8 == param24).ToList().Single()); + entity, + (await context.Set().Where(e => e.Id == 711 && e.EnumS8 == param24).ToListAsync()).Single()); } } } [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_all_nullable_data_types_with_values_set_to_non_null() { } + public override Task Can_insert_and_read_back_all_nullable_data_types_with_values_set_to_non_null() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_non_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_non_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_object_backed_data_types() { } + public override Task Can_insert_and_read_back_object_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_query_using_any_data_type_nullable_shadow() { } + public override Task Can_query_using_any_data_type_nullable_shadow() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_query_using_any_data_type_shadow() { } + public override Task Can_query_using_any_data_type_shadow() + => Task.CompletedTask; [ConditionalFact] public void Sum_Conversions() @@ -998,25 +1027,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .HasColumnType("timestamp without time zone"); // We don't support DateTimeOffset with non-zero offset, so we need to override the seeding data - var builtInDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - builtInDataTypes[nameof(BuiltInDataTypes.TestDateTimeOffset)] + modelBuilder.Entity().Metadata.GetSeedData() + .Single(t => (int)t[nameof(BuiltInDataTypes.Id)] == 13)[nameof(BuiltInDataTypes.TestDateTimeOffset)] = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.Zero); - var builtInNullableDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - builtInNullableDataTypes[nameof(BuiltInNullableDataTypes.TestNullableDateTimeOffset)] + modelBuilder.Entity().Metadata.GetSeedData() + .Single(t => (int)t[nameof(BuiltInDataTypes.Id)] == 13)[nameof(BuiltInNullableDataTypes.TestNullableDateTimeOffset)] = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.Zero); - var objectBackedDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - objectBackedDataTypes[nameof(ObjectBackedDataTypes.DateTimeOffset)] - = new DateTimeOffset(new DateTime(), TimeSpan.Zero); + modelBuilder.Entity().Metadata.GetSeedData() + .Single()[nameof(ObjectBackedDataTypes.DateTimeOffset)] = new DateTimeOffset(new DateTime(), TimeSpan.Zero); - var nullableBackedDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - nullableBackedDataTypes[nameof(NullableBackedDataTypes.DateTimeOffset)] + modelBuilder.Entity().Metadata.GetSeedData() + .Single()[nameof(NullableBackedDataTypes.DateTimeOffset)] = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.Zero); - var nonNullableBackedDataTypes = modelBuilder.Entity().Metadata.GetSeedData().Single(); - nonNullableBackedDataTypes[nameof(NonNullableBackedDataTypes.DateTimeOffset)] - = new DateTimeOffset(new DateTime(), TimeSpan.Zero); + modelBuilder.Entity().Metadata.GetSeedData() + .Single()[nameof(NonNullableBackedDataTypes.DateTimeOffset)] = new DateTimeOffset(new DateTime(), TimeSpan.Zero); modelBuilder.Entity( b => diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs index abaa17130..7bf72a670 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/ComplexTypeBulkUpdatesNpgsqlTest.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Update; public class ComplexTypeBulkUpdatesNpgsqlTest( ComplexTypeBulkUpdatesNpgsqlTest.ComplexTypeBulkUpdatesNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : ComplexTypeBulkUpdatesTestBase(fixture, testOutputHelper) + : ComplexTypeBulkUpdatesRelationalTestBase(fixture, testOutputHelper) { public override async Task Delete_entity_type_with_complex_type(bool async) { @@ -19,9 +19,9 @@ DELETE FROM "Customer" AS c """); } - public override async Task Delete_complex_type_throws(bool async) + public override async Task Delete_complex_type(bool async) { - await base.Delete_complex_type_throws(async); + await base.Delete_complex_type(async); AssertSql(); } @@ -87,9 +87,9 @@ public override async Task Update_multiple_projected_complex_types_via_anonymous """); } - public override async Task Update_projected_complex_type_via_OrderBy_Skip_throws(bool async) + public override async Task Update_projected_complex_type_via_OrderBy_Skip(bool async) { - await base.Update_projected_complex_type_via_OrderBy_Skip_throws(async); + await base.Update_projected_complex_type_via_OrderBy_Skip(async); AssertExecuteUpdateSql(); } @@ -229,7 +229,7 @@ private void AssertSql(params string[] expected) protected void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); - public class ComplexTypeBulkUpdatesNpgsqlFixture : ComplexTypeBulkUpdatesFixtureBase + public class ComplexTypeBulkUpdatesNpgsqlFixture : ComplexTypeBulkUpdatesRelationalFixtureBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs index 2704ed853..af6d6cfa2 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs @@ -3,7 +3,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Update; -public class NonSharedModelBulkUpdatesNpgsqlTest : NonSharedModelBulkUpdatesTestBase +public class NonSharedModelBulkUpdatesNpgsqlTest : NonSharedModelBulkUpdatesRelationalTestBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; @@ -50,6 +50,14 @@ DELETE FROM "Owner" AS o """); } + // TODO: #3253 + public override async Task Replace_ColumnExpression_in_column_setter(bool async) + { + var exception = await Assert.ThrowsAsync(() => base.Replace_ColumnExpression_in_column_setter(async)); + + Assert.Equal("42712", exception.SqlState); + } + public override async Task Delete_aggregate_root_when_table_sharing_with_non_owned_throws(bool async) { await base.Delete_aggregate_root_when_table_sharing_with_non_owned_throws(async); @@ -116,7 +124,7 @@ public override async Task Update_owned_and_non_owned_properties_with_table_shar """ UPDATE "Owner" AS o SET "OwnedReference_Number" = length(o."Title")::int, - "Title" = o."OwnedReference_Number"::text + "Title" = COALESCE(o."OwnedReference_Number"::text, '') """); } @@ -132,10 +140,10 @@ public override async Task Update_main_table_in_entity_with_entity_splitting(boo tb.Property(b => b.Title); tb.Property(b => b.Rating); }), - seed: context => + seed: async context => { context.Set().Add(new Blog { Title = "SomeBlog" }); - context.SaveChanges(); + await context.SaveChangesAsync(); }); await AssertUpdate( @@ -201,10 +209,10 @@ SELECT COALESCE(sum(o0."Amount"), 0)::int public virtual async Task Update_with_primitive_collection_in_value_selector(bool async) { var contextFactory = await InitializeAsync( - seed: ctx => + seed: async ctx => { ctx.AddRange(new EntityWithPrimitiveCollection { Tags = ["tag1", "tag2"] }); - ctx.SaveChanges(); + await ctx.SaveChangesAsync(); }); await AssertUpdate( diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs index 4dfa8eea2..13ea83511 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlFixture.cs @@ -5,7 +5,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class NorthwindBulkUpdatesNpgsqlFixture : NorthwindBulkUpdatesFixture +public class NorthwindBulkUpdatesNpgsqlFixture : NorthwindBulkUpdatesRelationalFixture where TModelCustomizer : ITestModelCustomizer, new() { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs index 60c20d9d9..40d60f7f5 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesNpgsqlTest.cs @@ -6,7 +6,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; public class NorthwindBulkUpdatesNpgsqlTest( NorthwindBulkUpdatesNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : NorthwindBulkUpdatesTestBase>(fixture, testOutputHelper) + : NorthwindBulkUpdatesRelationalTestBase>(fixture, testOutputHelper) { public override async Task Delete_Where_TagWith(bool async) { @@ -1375,12 +1375,12 @@ public override async Task Update_Where_Join_set_property_from_joined_single_res AssertExecuteUpdateSql( """ UPDATE "Customers" AS c -SET "City" = date_part('year', ( +SET "City" = COALESCE(date_part('year', ( SELECT o."OrderDate" FROM "Orders" AS o WHERE c."CustomerID" = o."CustomerID" ORDER BY o."OrderDate" DESC NULLS LAST - LIMIT 1))::int::text + LIMIT 1))::int::text, '') WHERE c."CustomerID" LIKE 'F%' """); } @@ -1409,12 +1409,12 @@ public override async Task Update_Where_Join_set_property_from_joined_single_res AssertExecuteUpdateSql( """ UPDATE "Customers" AS c -SET "City" = date_part('year', ( +SET "City" = COALESCE(date_part('year', ( SELECT o."OrderDate" FROM "Orders" AS o WHERE c."CustomerID" = o."CustomerID" ORDER BY o."OrderDate" DESC NULLS LAST - LIMIT 1))::int::text + LIMIT 1))::int::text, '') WHERE c."CustomerID" LIKE 'F%' """); } diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs index fcf00633d..ecab1b210 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,16 +2,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPHFiltersInheritanceBulkUpdatesNpgsqlTest : FiltersInheritanceBulkUpdatesTestBase< +public class TPHFiltersInheritanceBulkUpdatesNpgsqlTest : FiltersInheritanceBulkUpdatesRelationalTestBase< TPHFiltersInheritanceBulkUpdatesNpgsqlFixture> { public TPHFiltersInheritanceBulkUpdatesNpgsqlTest( TPHFiltersInheritanceBulkUpdatesNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) + : base(fixture, testOutputHelper) { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } public override async Task Delete_where_hierarchy(bool async) diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlTest.cs index 3fafaa0a8..21bb652f8 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,17 +2,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPHInheritanceBulkUpdatesNpgsqlTest : TPHInheritanceBulkUpdatesTestBase +public class TPHInheritanceBulkUpdatesNpgsqlTest( + TPHInheritanceBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : TPHInheritanceBulkUpdatesTestBase(fixture, testOutputHelper) { - public TPHInheritanceBulkUpdatesNpgsqlTest( - TPHInheritanceBulkUpdatesNpgsqlFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture) - { - ClearLog(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - public override async Task Delete_where_hierarchy(bool async) { await base.Delete_where_hierarchy(async); diff --git a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs index 17340262f..2cc8f767c 100644 --- a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs +++ b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs @@ -3,7 +3,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; [MinimumPostgresVersion(12, 0)] -public class ComputedColumnTest : IDisposable +public class ComputedColumnTest : IAsyncLifetime { [ConditionalFact] public void Can_use_computed_columns() @@ -128,8 +128,14 @@ public void Can_use_computed_columns_with_nullable_enum() Assert.Equal(FlagEnum.AValue | FlagEnum.BValue, entity.CalculatedFlagEnum); } - protected NpgsqlTestStore TestStore { get; } = NpgsqlTestStore.CreateInitialized("ComputedColumnTest"); + protected NpgsqlTestStore TestStore { get; private set; } - public virtual void Dispose() - => TestStore.Dispose(); + public async Task InitializeAsync() + => TestStore = await NpgsqlTestStore.CreateInitializedAsync("ComputedColumnTest"); + + public Task DisposeAsync() + { + TestStore.Dispose(); + return Task.CompletedTask; + } } diff --git a/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs index c7619c547..fbfe6f2c2 100644 --- a/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConferencePlannerNpgsqlTest.cs @@ -45,7 +45,7 @@ protected override ITestStoreFactory TestStoreFactory // We don't support DateTimeOffset with non-zero offsets, so we unfortunately need to override the entire seeding method. // See https://github.com/dotnet/efcore/issues/26068 - protected override void Seed(ApplicationDbContext context) + protected override async Task SeedAsync(ApplicationDbContext context) { var attendees1 = new List { @@ -162,7 +162,7 @@ protected override void Seed(ApplicationDbContext context) } context.AddRange(tracks.Values); - context.SaveChanges(); + await context.SaveChangesAsync(); } } } diff --git a/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs b/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs index 152082d8b..2cac8e343 100644 --- a/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs @@ -7,25 +7,25 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class ConnectionSpecificationTest { [Fact] - public void Can_specify_connection_string_in_OnConfiguring() + public async Task Can_specify_connection_string_in_OnConfiguring() { var serviceProvider = new ServiceCollection() .AddDbContext() .BuildServiceProvider(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = serviceProvider.GetRequiredService(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = serviceProvider.GetRequiredService(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } [Fact] - public void Can_specify_connection_string_in_OnConfiguring_with_default_service_provider() + public async Task Can_specify_connection_string_in_OnConfiguring_with_default_service_provider() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = new StringInOnConfiguringContext(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = new StringInOnConfiguringContext(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } private class StringInOnConfiguringContext : NorthwindContextBase @@ -35,25 +35,25 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) } [Fact] - public void Can_specify_connection_in_OnConfiguring() + public async Task Can_specify_connection_in_OnConfiguring() { var serviceProvider = new ServiceCollection() .AddScoped(_ => new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)) .AddDbContext().BuildServiceProvider(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = serviceProvider.GetRequiredService(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = serviceProvider.GetRequiredService(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } [Fact] - public void Can_specify_connection_in_OnConfiguring_with_default_service_provider() + public async Task Can_specify_connection_in_OnConfiguring_with_default_service_provider() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = new ConnectionInOnConfiguringContext(new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = new ConnectionInOnConfiguringContext(new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } private class ConnectionInOnConfiguringContext(NpgsqlConnection connection) : NorthwindContextBase @@ -104,28 +104,28 @@ public void Throws_if_no_config_without_UseNpgsql() private class NoUseNpgsqlContext : NorthwindContextBase; [Fact] - public void Can_depend_on_DbContextOptions() + public async Task Can_depend_on_DbContextOptions() { var serviceProvider = new ServiceCollection() .AddScoped(_ => new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)) .AddDbContext() .BuildServiceProvider(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = serviceProvider.GetRequiredService(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = serviceProvider.GetRequiredService(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } [Fact] - public void Can_depend_on_DbContextOptions_with_default_service_provider() + public async Task Can_depend_on_DbContextOptions_with_default_service_provider() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = new OptionsContext( + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = new OptionsContext( new DbContextOptions(), new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString)); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } private class OptionsContext(DbContextOptions options, NpgsqlConnection connection) @@ -148,25 +148,25 @@ public override void Dispose() } [Fact] - public void Can_depend_on_non_generic_options_when_only_one_context() + public async Task Can_depend_on_non_generic_options_when_only_one_context() { var serviceProvider = new ServiceCollection() .AddDbContext() .BuildServiceProvider(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = serviceProvider.GetRequiredService(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = serviceProvider.GetRequiredService(); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } [Fact] - public void Can_depend_on_non_generic_options_when_only_one_context_with_default_service_provider() + public async Task Can_depend_on_non_generic_options_when_only_one_context_with_default_service_provider() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); - using var context = new NonGenericOptionsContext(new DbContextOptions()); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + await using var context = new NonGenericOptionsContext(new DbContextOptions()); - Assert.True(context.Customers.Any()); + Assert.True(await context.Customers.AnyAsync()); } private class NonGenericOptionsContext(DbContextOptions options) : NorthwindContextBase(options) @@ -222,55 +222,55 @@ private class Customer #region Added for Npgsql [Fact] - public void Can_create_admin_connection_with_data_source() + public async Task Can_create_admin_connection_with_data_source() { - using var dataSource = NpgsqlDataSource.Create(NpgsqlTestStore.NorthwindConnectionString); + await using var dataSource = NpgsqlDataSource.Create(NpgsqlTestStore.NorthwindConnectionString); - using var _ = NpgsqlTestStore.GetNorthwindStore(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql(dataSource, b => b.ApplyConfiguration()); - using var context = new GeneralOptionsContext(optionsBuilder.Options); + await using var context = new GeneralOptionsContext(optionsBuilder.Options); var relationalConnection = context.GetService(); - using var adminConnection = relationalConnection.CreateAdminConnection(); + await using var adminConnection = relationalConnection.CreateAdminConnection(); Assert.Equal("postgres", new NpgsqlConnectionStringBuilder(adminConnection.ConnectionString).Database); - adminConnection.Open(); + await adminConnection.OpenAsync(CancellationToken.None); } [Fact] - public void Can_create_admin_connection_with_connection_string() + public async Task Can_create_admin_connection_with_connection_string() { - using var _ = NpgsqlTestStore.GetNorthwindStore(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql(NpgsqlTestStore.NorthwindConnectionString, b => b.ApplyConfiguration()); - using var context = new GeneralOptionsContext(optionsBuilder.Options); + await using var context = new GeneralOptionsContext(optionsBuilder.Options); var relationalConnection = context.GetService(); - using var adminConnection = relationalConnection.CreateAdminConnection(); + await using var adminConnection = relationalConnection.CreateAdminConnection(); Assert.Equal("postgres", new NpgsqlConnectionStringBuilder(adminConnection.ConnectionString).Database); - adminConnection.Open(); + await adminConnection.OpenAsync(CancellationToken.None); } [Fact] - public void Can_create_admin_connection_with_connection() + public async Task Can_create_admin_connection_with_connection() { - using var connection = new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString); + await using var connection = new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString); connection.Open(); - using var _ = NpgsqlTestStore.GetNorthwindStore(); + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql(connection, b => b.ApplyConfiguration()); - using var context = new GeneralOptionsContext(optionsBuilder.Options); + await using var context = new GeneralOptionsContext(optionsBuilder.Options); var relationalConnection = context.GetService(); - using var adminConnection = relationalConnection.CreateAdminConnection(); + await using var adminConnection = relationalConnection.CreateAdminConnection(); Assert.Equal("postgres", new NpgsqlConnectionStringBuilder(adminConnection.ConnectionString).Database); diff --git a/test/EFCore.PG.FunctionalTests/ConvertToProviderTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConvertToProviderTypesNpgsqlTest.cs index 8c7a1d96b..50fd6c5ee 100644 --- a/test/EFCore.PG.FunctionalTests/ConvertToProviderTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConvertToProviderTypesNpgsqlTest.cs @@ -12,53 +12,56 @@ public ConvertToProviderTypesNpgsqlTest(ConvertToProviderTypesNpgsqlFixture fixt Fixture.TestSqlLoggerFactory.Clear(); } - [Fact] - public override void Can_insert_and_read_with_max_length_set() - { - const string shortString = "Sky"; - var shortBinary = new byte[] { 8, 8, 7, 8, 7 }; - - var longString = new string('X', Fixture.LongStringLength); - var longBinary = new byte[Fixture.LongStringLength]; - for (var i = 0; i < longBinary.Length; i++) - { - longBinary[i] = (byte)i; - } - - using (var context = CreateContext()) - { - context.Set().Add( - new MaxLengthDataTypes - { - Id = 79, - String3 = shortString, - ByteArray5 = shortBinary, - String9000 = longString, - ByteArray9000 = longBinary - }); - - Assert.Equal(1, context.SaveChanges()); - } - - using (var context = CreateContext()) - { - var dt = context.Set().Where(e => e.Id == 79).ToList().Single(); - - Assert.Equal(shortString, dt.String3); - Assert.Equal(shortBinary, dt.ByteArray5); - Assert.Equal(longString, dt.String9000); - Assert.Equal(longBinary, dt.ByteArray9000); - } - } + // [Fact] + // public override void Can_insert_and_read_with_max_length_set() + // { + // const string shortString = "Sky"; + // var shortBinary = new byte[] { 8, 8, 7, 8, 7 }; + // + // var longString = new string('X', Fixture.LongStringLength); + // var longBinary = new byte[Fixture.LongStringLength]; + // for (var i = 0; i < longBinary.Length; i++) + // { + // longBinary[i] = (byte)i; + // } + // + // using (var context = CreateContext()) + // { + // context.Set().Add( + // new MaxLengthDataTypes + // { + // Id = 79, + // String3 = shortString, + // ByteArray5 = shortBinary, + // String9000 = longString, + // ByteArray9000 = longBinary + // }); + // + // Assert.Equal(1, context.SaveChanges()); + // } + // + // using (var context = CreateContext()) + // { + // var dt = context.Set().Where(e => e.Id == 79).ToList().Single(); + // + // Assert.Equal(shortString, dt.String3); + // Assert.Equal(shortBinary, dt.ByteArray5); + // Assert.Equal(longString, dt.String9000); + // Assert.Equal(longBinary, dt.ByteArray9000); + // } + // } [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_non_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_non_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_object_backed_data_types() { } + public override Task Can_insert_and_read_back_object_backed_data_types() + => Task.CompletedTask; public class ConvertToProviderTypesNpgsqlFixture : ConvertToProviderTypesFixtureBase { diff --git a/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs index 10f0cc9d9..a08007fe7 100644 --- a/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/CustomConvertersNpgsqlTest.cs @@ -6,19 +6,24 @@ public class CustomConvertersNpgsqlTest(CustomConvertersNpgsqlTest.CustomConvert : CustomConvertersTestBase(fixture) { // Disabled: PostgreSQL is case-sensitive - public override void Can_insert_and_read_back_with_case_insensitive_string_key() { } + public override Task Can_insert_and_read_back_with_case_insensitive_string_key() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_non_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_non_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_nullable_backed_data_types() { } + public override Task Can_insert_and_read_back_nullable_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_object_backed_data_types() { } + public override Task Can_insert_and_read_back_object_backed_data_types() + => Task.CompletedTask; [ConditionalFact(Skip = "DateTimeOffset with non-zero offset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_query_using_any_data_type_nullable_shadow() { } + public override Task Can_query_using_any_data_type_nullable_shadow() + => Task.CompletedTask; public override void Value_conversion_on_enum_collection_contains() => Assert.Contains( diff --git a/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs index 634a16cf2..71b56bd3a 100644 --- a/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/DataAnnotationNpgsqlTest.cs @@ -11,20 +11,14 @@ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransact protected override TestHelpers TestHelpers => NpgsqlTestHelpers.Instance; - public override void StringLengthAttribute_throws_while_inserting_value_longer_than_max_length() - { - // Npgsql does not support length - } + public override Task StringLengthAttribute_throws_while_inserting_value_longer_than_max_length() + => Task.CompletedTask; // Npgsql does not support length - public override void TimestampAttribute_throws_if_value_in_database_changed() - { - // Npgsql does not support length - } + public override Task TimestampAttribute_throws_if_value_in_database_changed() + => Task.CompletedTask; // Npgsql does not support length - public override void MaxLengthAttribute_throws_while_inserting_value_longer_than_max_length() - { - // Npgsql does not support length - } + public override Task MaxLengthAttribute_throws_while_inserting_value_longer_than_max_length() + => Task.CompletedTask; // Npgsql does not support length public class DataAnnotationNpgsqlFixture : DataAnnotationRelationalFixtureBase { diff --git a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj index ce5bd43a8..084ec149f 100644 --- a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj +++ b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj @@ -19,6 +19,9 @@ + + + diff --git a/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs b/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs index 2ef271f86..f155b67ba 100644 --- a/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs +++ b/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs @@ -20,7 +20,7 @@ private static async Task Can_use_an_existing_closed_connection_test(bool openCo .AddEntityFrameworkNpgsql() .BuildServiceProvider(); - await using (var store = NpgsqlTestStore.GetNorthwindStore()) + await using (var store = await NpgsqlTestStore.GetNorthwindStoreAsync()) { store.CloseConnection(); diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index 593c41c26..d5307dbbd 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -11,165 +11,192 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; public class JsonTypesNpgsqlTest : JsonTypesRelationalTestBase { - public override void Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) - { - // Relational databases don't support unsigned numeric types, so ulong is value-converted to long - if (value == EnumU64.Max) - { - json = """{"Prop":-1}"""; - } - - base.Can_read_write_ulong_enum_JSON_values(value, json); - } + #region Nested collections (unsupported) - public override void Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json) - { - // Relational databases don't support unsigned numeric types, so ulong is value-converted to long - if (Equals(value, ulong.MaxValue)) - { - json = """{"Prop":-1}"""; - } - - base.Can_read_write_nullable_ulong_enum_JSON_values(value, json); - } + // The following tests are disabled because they use nested collections, which are not supported by EFCore.PG (arrays of arrays aren't + // supported). - public override void Can_read_write_collection_of_ulong_enum_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(EnumU64CollectionType.EnumU64), - [ - EnumU64.Min, - EnumU64.Max, - EnumU64.Default, - EnumU64.One, - (EnumU64)8 - ], - // Relational databases don't support unsigned numeric types, so ulong is value-converted to long - """{"Prop":[0,-1,0,1,8]}""", - mappedCollection: true); - - public override void Can_read_write_collection_of_nullable_ulong_enum_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(NullableEnumU64CollectionType.EnumU64), - [ - EnumU64.Min, - null, - EnumU64.Max, - EnumU64.Default, - EnumU64.One, - (EnumU64?)8 - ], - // Relational databases don't support unsigned numeric types, so ulong is value-converted to long - """{"Prop":[0,null,-1,0,1,8]}""", - mappedCollection: true); + public override Task Can_read_write_array_of_array_of_array_of_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_array_of_array_of_int_JSON_values()); + + public override Task Can_read_write_array_of_list_of_array_of_IPAddress_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_array_of_IPAddress_JSON_values()); + + public override Task Can_read_write_array_of_list_of_array_of_string_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_array_of_string_JSON_values()); + + public override Task Can_read_write_array_of_list_of_binary_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_binary_JSON_values(expected)); + + public override Task Can_read_write_array_of_list_of_GUID_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_GUID_JSON_values(expected)); + + public override Task Can_read_write_array_of_list_of_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_int_JSON_values()); + + public override Task Can_read_write_array_of_list_of_IPAddress_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_IPAddress_JSON_values()); + + public override Task Can_read_write_array_of_list_of_string_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_string_JSON_values()); + + public override Task Can_read_write_array_of_list_of_ulong_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_ulong_JSON_values()); + + public override Task Can_read_write_list_of_array_of_GUID_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_GUID_JSON_values(expected)); + + public override Task Can_read_write_list_of_array_of_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_int_JSON_values()); + + public override Task Can_read_write_list_of_array_of_IPAddress_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_IPAddress_JSON_values()); + + public override Task Can_read_write_list_of_array_of_list_of_array_of_binary_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_array_of_binary_JSON_values(expected)); + + public override Task Can_read_write_list_of_array_of_list_of_IPAddress_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_IPAddress_JSON_values()); + + public override Task Can_read_write_list_of_array_of_list_of_string_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_string_JSON_values()); + + public override Task Can_read_write_list_of_array_of_list_of_ulong_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_ulong_JSON_values()); + + public override Task Can_read_write_list_of_array_of_nullable_GUID_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_GUID_JSON_values(expected)); + + public override Task Can_read_write_list_of_array_of_nullable_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_int_JSON_values()); + + public override Task Can_read_write_list_of_array_of_nullable_ulong_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_ulong_JSON_values()); + + public override Task Can_read_write_list_of_array_of_string_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_string_JSON_values()); + + public override Task Can_read_write_list_of_array_of_ulong_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_ulong_JSON_values()); + + public override Task Can_read_write_list_of_list_of_list_of_int_JSON_values() + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_list_of_list_of_int_JSON_values()); + + #endregion Nested collections (unsupported) + + // IEnumerable property + public override Task Can_read_write_list_of_array_of_binary_JSON_values(string expected) + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_binary_JSON_values(expected)); + + // public override Task Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) + // { + // // Relational databases don't support unsigned numeric types, so ulong is value-converted to long + // if (value == EnumU64.Max) + // { + // json = """{"Prop":-1}"""; + // } + // + // return base.Can_read_write_ulong_enum_JSON_values(value, json); + // } + // + // public override void Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json) + // { + // // Relational databases don't support unsigned numeric types, so ulong is value-converted to long + // if (Equals(value, ulong.MaxValue)) + // { + // json = """{"Prop":-1}"""; + // } + // + // base.Can_read_write_nullable_ulong_enum_JSON_values(value, json); + // } + // + // public override void Can_read_write_collection_of_ulong_enum_JSON_values() + // => Can_read_and_write_JSON_value>( + // nameof(EnumU64CollectionType.EnumU64), + // [ + // EnumU64.Min, + // EnumU64.Max, + // EnumU64.Default, + // EnumU64.One, + // (EnumU64)8 + // ], + // // Relational databases don't support unsigned numeric types, so ulong is value-converted to long + // """{"Prop":[0,-1,0,1,8]}""", + // mappedCollection: true); + // + // public override void Can_read_write_collection_of_nullable_ulong_enum_JSON_values() + // => Can_read_and_write_JSON_value>( + // nameof(NullableEnumU64CollectionType.EnumU64), + // [ + // EnumU64.Min, + // null, + // EnumU64.Max, + // EnumU64.Default, + // EnumU64.One, + // (EnumU64?)8 + // ], + // // Relational databases don't support unsigned numeric types, so ulong is value-converted to long + // """{"Prop":[0,null,-1,0,1,8]}""", + // mappedCollection: true); #region TimeSpan - public override void Can_read_write_TimeSpan_JSON_values(string value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } - - [ConditionalTheory] - [InlineData("-10675199.02:48:05.477580", """{"Prop":"-10675199 02:48:05.47758"}""")] - [InlineData("10675199.02:48:05.477580", """{"Prop":"10675199 02:48:05.47758"}""")] - [InlineData("00:00:00", """{"Prop":"00:00:00"}""")] - [InlineData("12:23:23.801885", """{"Prop":"12:23:23.801885"}""")] - public virtual void Can_read_write_TimeSpan_JSON_values_npgsql(string value, string json) - => Can_read_and_write_JSON_value( - nameof(TimeSpanType.TimeSpan), - TimeSpan.Parse(value, CultureInfo.InvariantCulture), - json); - - public override void Can_read_write_nullable_TimeSpan_JSON_values(string? value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } - - [ConditionalTheory] - [InlineData("-10675199.02:48:05.47758", """{"Prop":"-10675199 02:48:05.47758"}""")] - [InlineData("10675199.02:48:05.47758", """{"Prop":"10675199 02:48:05.47758"}""")] - [InlineData("00:00:00", """{"Prop":"00:00:00"}""")] - [InlineData("12:23:23.801885", """{"Prop":"12:23:23.801885"}""")] - [InlineData(null, """{"Prop":null}""")] - public virtual void Can_read_write_nullable_TimeSpan_JSON_values_npgsql(string? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableTimeSpanType.TimeSpan), - value == null ? default(TimeSpan?) : TimeSpan.Parse(value), json); - - [ConditionalFact] - public override void Can_read_write_collection_of_TimeSpan_JSON_values() - { - Can_read_and_write_JSON_value>( - nameof(TimeSpanCollectionType.TimeSpan), - [ - new(1, 2, 3, 4), - new(0, 2, 3, 4, 5, 678) - // TimeSpan.MaxValue - ], - """{"Prop":["1 02:03:04","02:03:04.005678"]}""", - mappedCollection: true); - } - - [ConditionalFact] - public override void Can_read_write_collection_of_nullable_TimeSpan_JSON_values() - => Can_read_and_write_JSON_value>( - nameof(NullableTimeSpanCollectionType.TimeSpan), - [ - new(1, 2, 3, 4), - new(0, 2, 3, 4, 5, 678), - // TimeSpan.MaxValue - null - ], - """{"Prop":["1 02:03:04","02:03:04.005678",null]}""", - mappedCollection: true); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_TimeSpan_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_TimeSpan_JSON_values_npgsql + public override Task Can_read_write_TimeSpan_JSON_values(string value, string json) + => Task.CompletedTask; + + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_TimeSpan_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_nullable_TimeSpan_JSON_values_npgsql + public override Task Can_read_write_nullable_TimeSpan_JSON_values(string? value, string json) + => Task.CompletedTask; + + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_TimeSpan_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_collection_of_TimeSpan_JSON_values_npgsql + public override Task Can_read_write_collection_of_TimeSpan_JSON_values() + => Task.CompletedTask; + + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_TimeSpan_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_collection_of_nullable_TimeSpan_JSON_values_npgsql + public override Task Can_read_write_collection_of_nullable_TimeSpan_JSON_values() + => Task.CompletedTask; #endregion TimeSpan #region DateOnly - public override void Can_read_write_DateOnly_JSON_values(string value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_DateOnly_JSON_values_npgsql instead. + public override Task Can_read_write_DateOnly_JSON_values(string value, string json) + => Task.CompletedTask; [ConditionalTheory] [InlineData("1/1/0001", """{"Prop":"-infinity"}""")] [InlineData("12/31/9999", """{"Prop":"infinity"}""")] [InlineData("5/29/2023", """{"Prop":"2023-05-29"}""")] - public virtual void Can_read_write_DateOnly_JSON_values_npgsql(string value, string json) + public virtual Task Can_read_write_DateOnly_JSON_values_npgsql(string value, string json) => Can_read_and_write_JSON_value( nameof(DateOnlyType.DateOnly), DateOnly.Parse(value, CultureInfo.InvariantCulture), json); - public override void Can_read_write_nullable_DateOnly_JSON_values(string? value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_DateOnly_JSON_values_sqlite instead. - } - - [ConditionalTheory] - [InlineData("1/1/0001", """{"Prop":"-infinity"}""")] - [InlineData("12/31/9999", """{"Prop":"infinity"}""")] - [InlineData("5/29/2023", """{"Prop":"2023-05-29"}""")] - [InlineData(null, """{"Prop":null}""")] - public virtual void Can_read_write_nullable_DateOnly_JSON_values_npgsql(string? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableDateOnlyType.DateOnly), - value == null ? default(DateOnly?) : DateOnly.Parse(value, CultureInfo.InvariantCulture), json); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_DateOnly_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_nullable_DateOnly_JSON_values_npgsql + public override Task Can_read_write_nullable_DateOnly_JSON_values(string? value, string json) + => Task.CompletedTask; - [ConditionalFact] - public virtual void Can_read_write_DateOnly_JSON_values_infinity() - { - Can_read_and_write_JSON_value(nameof(DateOnlyType.DateOnly), DateOnly.MinValue, """{"Prop":"-infinity"}"""); - Can_read_and_write_JSON_value(nameof(DateOnlyType.DateOnly), DateOnly.MaxValue, """{"Prop":"infinity"}"""); - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_TimeSpan_JSON_values_npgsql instead. + public override Task Can_read_write_collection_of_DateOnly_JSON_values() + => Task.CompletedTask; [ConditionalFact] - public override void Can_read_write_collection_of_DateOnly_JSON_values() - => Can_read_and_write_JSON_value>( + public virtual Task Can_read_write_collection_of_DateOnly_JSON_values_npgsql() + => Can_read_and_write_JSON_value>( nameof(DateOnlyCollectionType.DateOnly), [ DateOnly.MinValue, @@ -179,8 +206,13 @@ public override void Can_read_write_collection_of_DateOnly_JSON_values() """{"Prop":["-infinity","2023-05-29","infinity"]}""", mappedCollection: true); + protected class NpgsqlDateOnlyCollectionType + { + public List DateOnly { get; set; } = null!; + } + [ConditionalFact] - public override void Can_read_write_collection_of_nullable_DateOnly_JSON_values() + public override Task Can_read_write_collection_of_nullable_DateOnly_JSON_values() => Can_read_and_write_JSON_value>( nameof(NullableDateOnlyCollectionType.DateOnly), [ @@ -196,136 +228,113 @@ public override void Can_read_write_collection_of_nullable_DateOnly_JSON_values( #region DateTime - public override void Can_read_write_DateTime_JSON_values(string value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_DateTime_JSON_values_npgsql instead. + public override Task Can_read_write_DateTime_JSON_values(string value, string json) + => Task.CompletedTask; [ConditionalTheory] - [InlineData("2023-05-29T10:52:47.206435Z", """{"Prop":"2023-05-29T10:52:47.206435Z"}""")] - public virtual void Can_read_write_DateTime_JSON_values_npgsql(string value, string json) - => Can_read_and_write_JSON_value( - nameof(DateTimeType.DateTime), - DateTime.Parse(value, CultureInfo.InvariantCulture).ToUniversalTime(), json); - - [ConditionalFact] - public virtual void Can_read_write_DateTime_JSON_values_npgsql_infinity() + [InlineData("0001-01-01T00:00:00.0000000", """{"Prop":"-infinity"}""")] + [InlineData("9999-12-31T23:59:59.9999999", """{"Prop":"infinity"}""")] + [InlineData("2023-05-29T10:52:47.2064350", """{"Prop":"2023-05-29T10:52:47.206435"}""")] + public virtual Task Can_read_write_DateTime_JSON_values_npgsql(string value, string json) => Can_read_and_write_JSON_value( + modelBuilder => modelBuilder + .Entity() + .HasNoKey() + .Property(nameof(DateTimeType.DateTime)) + .HasColumnType("timestamp without time zone"), + configureConventions: null, nameof(DateTimeType.DateTime), - DateTime.MaxValue, """{"Prop":"infinity"}"""); + DateTime.Parse(value, CultureInfo.InvariantCulture), json); - [ConditionalFact] - public virtual void Can_read_write_DateTime_JSON_values_npgsql_negative_infinity() - => Can_read_and_write_JSON_value( - nameof(DateTimeType.DateTime), - DateTime.MinValue, """{"Prop":"-infinity"}"""); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_DateTime_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_nullable_DateTime_JSON_values_npgsql + public override Task Can_read_write_nullable_DateTime_JSON_values(string? value, string json) + => Task.CompletedTask; - public override void Can_read_write_nullable_DateTime_JSON_values(string? value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_TimeSpan_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_TimeSpan_JSON_values_npgsql instead. + public override Task Can_read_write_collection_of_DateTime_JSON_values(string expected) + => Task.CompletedTask; [ConditionalTheory] - [InlineData("0001-01-01T00:00:00.0000000", """{"Prop":"-infinity"}""")] - [InlineData("9999-12-31T23:59:59.9999999", """{"Prop":"infinity"}""")] - [InlineData("2023-05-29T10:52:47.206435", """{"Prop":"2023-05-29T10:52:47.206435Z"}""")] - [InlineData(null, """{"Prop":null}""")] - public virtual void Can_read_write_nullable_DateTime_JSON_values_npgsql(string? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableDateTimeType.DateTime), - value == null - ? default(DateTime?) - : DateTime.SpecifyKind(DateTime.Parse(value, CultureInfo.InvariantCulture), DateTimeKind.Utc), json); - - [ConditionalFact] - public override void Can_read_write_collection_of_DateTime_JSON_values() - => Can_read_and_write_JSON_value>( + [InlineData("""{"Prop":["-infinity","2023-05-29T10:52:47","infinity"]}""")] + public virtual Task Can_read_write_collection_of_DateTime_JSON_values_npgsql(string expected) + => Can_read_and_write_JSON_value>( + modelBuilder => modelBuilder + .Entity() + .HasNoKey() + .PrimitiveCollection(nameof(DateTimeCollectionType.DateTime)) + .ElementType() + .HasStoreType("timestamp without time zone"), + configureConventions: null, nameof(DateTimeCollectionType.DateTime), [ DateTime.MinValue, - new(2023, 5, 29, 10, 52, 47, DateTimeKind.Utc), + new(2023, 5, 29, 10, 52, 47), DateTime.MaxValue ], - """{"Prop":["-infinity","2023-05-29T10:52:47Z","infinity"]}""", + expected, mappedCollection: true); - [ConditionalFact] - public override void Can_read_write_collection_of_nullable_DateTime_JSON_values() + protected class NpgsqlDateTimeCollectionType + { + public List DateTime { get; set; } = null!; + } + + public override Task Can_read_write_collection_of_nullable_DateTime_JSON_values(string expected) => Can_read_and_write_JSON_value>( + modelBuilder => modelBuilder + .Entity() + .HasNoKey() + .PrimitiveCollection(nameof(NullableDateTimeCollectionType.DateTime)) + .ElementType() + .HasStoreType("timestamp without time zone"), + configureConventions: null, nameof(NullableDateTimeCollectionType.DateTime), [ DateTime.MinValue, null, - new(2023, 5, 29, 10, 52, 47, DateTimeKind.Utc), + new(2023, 5, 29, 10, 52, 47), DateTime.MaxValue ], - """{"Prop":["-infinity",null,"2023-05-29T10:52:47Z","infinity"]}""", + """{"Prop":["-infinity",null,"2023-05-29T10:52:47","infinity"]}""", mappedCollection: true); - [ConditionalFact] - public virtual void Can_read_write_DateTime_timestamptz_JSON_values_infinity() - { - Can_read_and_write_JSON_value(nameof(DateTimeType.DateTime), DateTime.MinValue, """{"Prop":"-infinity"}"""); - Can_read_and_write_JSON_value(nameof(DateTimeType.DateTime), DateTime.MaxValue, """{"Prop":"infinity"}"""); - } - - [ConditionalFact] - public virtual void Can_read_write_DateTime_timestamp_JSON_values_infinity() - { - Can_read_and_write_JSON_property_value( - b => b.HasColumnType("timestamp without time zone"), - nameof(DateTimeType.DateTime), - DateTime.MinValue, - """{"Prop":"-infinity"}"""); - - Can_read_and_write_JSON_property_value( - b => b.HasColumnType("timestamp without time zone"), - nameof(DateTimeType.DateTime), - DateTime.MaxValue, - """{"Prop":"infinity"}"""); - } - #endregion DateTime #region DateTimeOffset - public override void Can_read_write_DateTimeOffset_JSON_values(string value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_DateTimeOffset_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_DateTimeOffset_JSON_values_npgsql instead. + public override Task Can_read_write_DateTimeOffset_JSON_values(string value, string json) + => Task.CompletedTask; [ConditionalTheory] - [InlineData("0001-01-01T00:00:00.000000-01:00", """{"Prop":"0001-01-01T00:00:00-01:00"}""")] - [InlineData("9999-12-31T23:59:59.999999+02:00", """{"Prop":"9999-12-31T23:59:59.999999\u002B02:00"}""")] - [InlineData("0001-01-01T00:00:00.000000-03:00", """{"Prop":"0001-01-01T00:00:00-03:00"}""")] - [InlineData("2023-05-29T11:11:15.567285+04:00", """{"Prop":"2023-05-29T11:11:15.567285\u002B04:00"}""")] - public virtual void Can_read_write_DateTimeOffset_JSON_values_npgsql(string value, string json) + [InlineData("0001-01-01T00:00:00.0000000-01:00", """{"Prop":"0001-01-01T00:00:00-01:00"}""")] + [InlineData("9999-12-31T23:59:59.9999990+02:00", """{"Prop":"9999-12-31T23:59:59.999999\u002B02:00"}""")] + [InlineData("0001-01-01T00:00:00.0000000-03:00", """{"Prop":"0001-01-01T00:00:00-03:00"}""")] + [InlineData("2023-05-29T11:11:15.5672850+04:00", """{"Prop":"2023-05-29T11:11:15.567285\u002B04:00"}""")] + public virtual Task Can_read_write_DateTimeOffset_JSON_values_npgsql(string value, string json) => Can_read_and_write_JSON_value( nameof(DateTimeOffsetType.DateTimeOffset), DateTimeOffset.Parse(value, CultureInfo.InvariantCulture), json); - public override void Can_read_write_nullable_DateTimeOffset_JSON_values(string? value, string json) - { - // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need - // to override. See Can_read_write_DateTimeOffset_JSON_values_sqlite instead. - } + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_nullable_DateTimeOffset_JSON_values_npgsql instead. + // TODO: Implement Can_read_write_nullable_DateTimeOffset_JSON_values_npgsql + public override Task Can_read_write_nullable_DateTimeOffset_JSON_values(string? value, string json) + => Task.CompletedTask; - [ConditionalTheory] - [InlineData("0001-01-01T00:00:00.000000-01:00", """{"Prop":"0001-01-01T00:00:00-01:00"}""")] - [InlineData("9999-12-31T23:59:59.999999+02:00", """{"Prop":"9999-12-31T23:59:59.999999\u002B02:00"}""")] - [InlineData("0001-01-01T00:00:00.000000-03:00", """{"Prop":"0001-01-01T00:00:00-03:00"}""")] - [InlineData("2023-05-29T11:11:15.567285+04:00", """{"Prop":"2023-05-29T11:11:15.567285\u002B04:00"}""")] - [InlineData(null, """{"Prop":null}""")] - public virtual void Can_read_write_nullable_DateTimeOffset_JSON_values_npgsql(string? value, string json) - => Can_read_and_write_JSON_value( - nameof(NullableDateTimeOffsetType.DateTimeOffset), - value == null ? default(DateTimeOffset?) : DateTimeOffset.Parse(value, CultureInfo.InvariantCulture), json); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_collection_of_DateTimeOffset_JSON_values_npgsql instead. + public override Task Can_read_write_collection_of_DateTimeOffset_JSON_values(string expected) + => Task.CompletedTask; [ConditionalFact] - public override void Can_read_write_collection_of_DateTimeOffset_JSON_values() + public virtual Task Can_read_write_collection_of_DateTimeOffset_JSON_values_npgsql() => Can_read_and_write_JSON_value>( nameof(DateTimeOffsetCollectionType.DateTimeOffset), [ @@ -338,8 +347,13 @@ public override void Can_read_write_collection_of_DateTimeOffset_JSON_values() """{"Prop":["-infinity","2023-05-29T10:52:47-02:00","2023-05-29T10:52:47\u002B00:00","2023-05-29T10:52:47\u002B02:00","infinity"]}""", mappedCollection: true); + // Cannot override since the base test contains [InlineData] attributes which still apply, and which contain data we need + // to override. See Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values_npgsql instead. + public override Task Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values(string expected) + => Task.CompletedTask; + [ConditionalFact] - public override void Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values() + public virtual Task Can_read_write_collection_of_nullable_DateTimeOffset_JSON_values_npgsql() => Can_read_and_write_JSON_value>( nameof(NullableDateTimeOffsetCollectionType.DateTimeOffset), [ @@ -367,7 +381,7 @@ public virtual void Can_read_write_timetz_JSON_values(string value, string json) [ConditionalTheory] [InlineData(Mood.Happy, """{"Prop":"Happy"}""")] [InlineData(Mood.Sad, """{"Prop":"Sad"}""")] - public virtual void Can_read_write_pg_enum_JSON_values(Mood value, string json) + public virtual Task Can_read_write_pg_enum_JSON_values(Mood value, string json) => Can_read_and_write_JSON_value( nameof(EnumType.Mood), value, @@ -387,7 +401,7 @@ public enum Mood [ConditionalTheory] [InlineData(new[] { 1, 2, 3 }, """{"Prop":[1,2,3]}""")] [InlineData(new int[0], """{"Prop":[]}""")] - public virtual void Can_read_write_array_JSON_values(int[] value, string json) + public virtual Task Can_read_write_array_JSON_values(int[] value, string json) => Can_read_and_write_JSON_value( nameof(ArrayType.Array), value, @@ -400,9 +414,9 @@ protected class ArrayType } [ConditionalFact] - public virtual void Cannot_read_write_multidimensional_array_JSON_values() + public virtual Task Cannot_read_write_multidimensional_array_JSON_values() // EF currently throws NRE when the type mapping has no JsonValueReaderWriter (this has been improved for 9.0) - => Assert.Throws( + => Assert.ThrowsAsync( () => Can_read_and_write_JSON_value( nameof(MultidimensionalArrayType.MultidimensionalArray), new[,] { { 1, 2 }, { 3, 4 } }, @@ -414,7 +428,7 @@ protected class MultidimensionalArrayType } [ConditionalFact] - public virtual void Can_read_write_BigInteger_JSON_values() + public virtual Task Can_read_write_BigInteger_JSON_values() => Can_read_and_write_JSON_value( nameof(BigIntegerType.BigInteger), new BigInteger(ulong.MaxValue), @@ -429,7 +443,7 @@ protected class BigIntegerType [InlineData(new[] { true, false, true }, """{"Prop":"101"}""")] [InlineData(new[] { true, false, true, true, false, true, false, true, false }, """{"Prop":"101101010"}""")] [InlineData(new bool[0], """{"Prop":""}""")] - public virtual void Can_read_write_BitArray_JSON_values(bool[] value, string json) + public virtual Task Can_read_write_BitArray_JSON_values(bool[] value, string json) => Can_read_and_write_JSON_value( nameof(BitArrayType.BitArray), new BitArray(value), @@ -443,7 +457,7 @@ protected class BitArrayType [ConditionalTheory] [InlineData(1000, """{"Prop":"0/3E8"}""")] [InlineData(0, """{"Prop":"0/0"}""")] - public virtual void Can_read_write_LogSequenceNumber_JSON_values(ulong value, string json) + public virtual Task Can_read_write_LogSequenceNumber_JSON_values(ulong value, string json) => Can_read_and_write_JSON_value( nameof(LogSequenceNumberType.LogSequenceNumber), new NpgsqlLogSequenceNumber(value), diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs index 18a8eadcc..996e3402b 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs @@ -7,6 +7,25 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations public class MigrationsInfrastructureNpgsqlTest(MigrationsInfrastructureNpgsqlTest.MigrationsInfrastructureNpgsqlFixture fixture) : MigrationsInfrastructureTestBase(fixture) { + // TODO: The following test the migration lock, which isn't yet implemented - waiting for EF-side fixes in rc.2 + #region Unskip for 9.0.0-rc.2 + + public override void Can_apply_one_migration_in_parallel() + { + } + + public override Task Can_apply_one_migration_in_parallel_async() + => Task.CompletedTask; + + public override void Can_apply_second_migration_in_parallel() + { + } + + public override Task Can_apply_second_migration_in_parallel_async() + => Task.CompletedTask; + + #endregion Unskip for 9.0.0-rc.2 + public override void Can_get_active_provider() { base.Can_get_active_provider(); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 2277793c9..1f11df6bf 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2575,42 +2575,10 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'dbo2') THEN """, // """ -CREATE SEQUENCE dbo2."TestSequence" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE CACHE 20; +CREATE SEQUENCE dbo2."TestSequence" START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE; """); } - public override async Task Create_sequence_nocache() - { - await base.Create_sequence_nocache(); - - AssertSql("""CREATE SEQUENCE "Alpha" START WITH 1 INCREMENT BY 1 NO CYCLE CACHE 1;"""); - } - - public override async Task Create_sequence_cache() - { - await base.Create_sequence_cache(); - - AssertSql("""CREATE SEQUENCE "Beta" START WITH 1 INCREMENT BY 1 NO CYCLE CACHE 20;"""); - } - - public override async Task Create_sequence_default_cache() - { - // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as - // disabling caching. - await Test( - builder => { }, - builder => builder.HasSequence("Gamma").UseCache(), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.Equal("Gamma", sequence.Name); - Assert.False(sequence.IsCached); - Assert.Null(sequence.CacheSize); - }); - - AssertSql("""CREATE SEQUENCE "Gamma" START WITH 1 INCREMENT BY 1 NO CYCLE;"""); - } - [Fact] public virtual async Task Create_sequence_smallint() { @@ -2634,7 +2602,7 @@ public override async Task Alter_sequence_all_settings() AssertSql( """ -ALTER SEQUENCE foo INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE CACHE 20; +ALTER SEQUENCE foo INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE; """, // """ @@ -2647,71 +2615,10 @@ public override async Task Alter_sequence_increment_by() { await base.Alter_sequence_increment_by(); - AssertSql("ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"); - } - - public override async Task Alter_sequence_default_cache_to_cache() - { - await base.Alter_sequence_default_cache_to_cache(); - - AssertSql("""ALTER SEQUENCE "Delta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); - } - - public override async Task Alter_sequence_default_cache_to_nocache() - { - await base.Alter_sequence_default_cache_to_nocache(); - - AssertSql("""ALTER SEQUENCE "Epsilon" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); - } - - public override async Task Alter_sequence_cache_to_nocache() - { - await base.Alter_sequence_cache_to_nocache(); - - AssertSql("""ALTER SEQUENCE "Zeta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); - } - - public override async Task Alter_sequence_cache_to_default_cache() - { - // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as - // disabling caching. - await Test( - builder => builder.HasSequence("Eta").UseCache(20), - builder => { }, - builder => builder.HasSequence("Eta").UseCache(), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.False(sequence.IsCached); - Assert.Null(sequence.CacheSize); - }); - - AssertSql("""ALTER SEQUENCE "Eta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); - } - - public override async Task Alter_sequence_nocache_to_cache() - { - await base.Alter_sequence_nocache_to_cache(); - - AssertSql("""ALTER SEQUENCE "Theta" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 20;"""); - } - - public override async Task Alter_sequence_nocache_to_default_cache() - { - // PG has no distinction between "no cached" and "CACHE 1" (which is the default), so setting to the default is the same as - // disabling caching. - await Test( - builder => builder.HasSequence("Iota").UseNoCache(), - builder => { }, - builder => builder.HasSequence("Iota").UseCache(), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.False(sequence.IsCached); - Assert.Null(sequence.CacheSize); - }); - - AssertSql("""ALTER SEQUENCE "Iota" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE CACHE 1;"""); + AssertSql( + """ +ALTER SEQUENCE foo INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE; +"""); } public override async Task Alter_sequence_restart_with() diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs index b51dea3bc..76e12c800 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs @@ -5,11 +5,16 @@ public class NpgsqlComplianceTest : RelationalComplianceTestBase protected override ICollection IgnoredTestBases { get; } = new HashSet { // Not implemented - typeof(CompiledModelTestBase), typeof(CompiledModelRelationalTestBase), // ##3087 + typeof(CompiledModelTestBase), typeof(CompiledModelRelationalTestBase), // #3087 typeof(FromSqlSprocQueryTestBase<>), typeof(UdfDbFunctionTestBase<>), typeof(UpdateSqlGeneratorTestBase), + // Precompiled query/NativeAOT (#3257) + typeof(AdHocPrecompiledQueryRelationalTestBase), + typeof(PrecompiledQueryRelationalTestBase), + typeof(PrecompiledSqlPregenerationQueryRelationalTestBase), + // Disabled typeof(GraphUpdatesTestBase<>), typeof(ProxyGraphUpdatesTestBase<>), diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs index 2c9d7e992..2c7bc8061 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs @@ -46,7 +46,7 @@ await context.Database.CreateExecutionStrategy().ExecuteAsync( [InlineData(false, true, true)] public async Task Returns_true_when_database_exists(bool async, bool ambientTransaction, bool useCanConnect) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("ExistingBlogging"); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("ExistingBlogging"); await using var context = new BloggingContext(testDatabase); var creator = GetDatabaseCreator(context); @@ -79,7 +79,7 @@ public class NpgsqlDatabaseCreatorEnsureDeletedTest : NpgsqlDatabaseCreatorTest [InlineData(false, true, false)] public async Task Deletes_database(bool async, bool open, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.CreateInitialized("EnsureDeleteBlogging"); + await using var testDatabase = await NpgsqlTestStore.CreateInitializedAsync("EnsureDeleteBlogging"); if (!open) { testDatabase.CloseConnection(); @@ -166,11 +166,11 @@ private static async Task Creates_physical_database_and_schema_test( await using var context = new BloggingContext(testDatabase); if (createDatabase) { - testDatabase.Initialize(null, (Func)null); + await testDatabase.InitializeAsync(null, (Func)null); } else { - testDatabase.DeleteDatabase(); + await testDatabase.DeleteDatabaseAsync(); } var creator = GetDatabaseCreator(context); @@ -232,7 +232,7 @@ private static async Task Creates_physical_database_and_schema_test( [InlineData(false)] public async Task Noop_when_database_exists_and_has_schema(bool async) { - await using var testDatabase = NpgsqlTestStore.CreateInitialized("InitializedBlogging"); + await using var testDatabase = await NpgsqlTestStore.CreateInitializedAsync("InitializedBlogging"); await using var context = new BloggingContext(testDatabase); context.Database.EnsureCreatedResiliently(); @@ -275,7 +275,7 @@ await databaseCreator.ExecutionStrategy.ExecuteAsync( [InlineData(false, true)] public async Task Returns_false_when_database_exists_but_has_no_tables(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("Empty"); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("Empty"); var creator = GetDatabaseCreator(testDatabase); await GetExecutionStrategy(testDatabase).ExecuteAsync( @@ -293,8 +293,8 @@ await GetExecutionStrategy(testDatabase).ExecuteAsync( [InlineData(false, false)] public async Task Returns_true_when_database_exists_and_has_any_tables(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.GetOrCreate("ExistingTables") - .InitializeNpgsql(null, t => new BloggingContext(t), null); + await using var testDatabase = await NpgsqlTestStore.GetOrCreate("ExistingTables") + .InitializeNpgsqlAsync(null, t => new BloggingContext(t), null); var creator = GetDatabaseCreator(testDatabase); await GetExecutionStrategy(testDatabase).ExecuteAsync( @@ -313,7 +313,7 @@ await GetExecutionStrategy(testDatabase).ExecuteAsync( [RequiresPostgis] public async Task Returns_false_when_database_exists_and_has_only_postgis_tables(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("Empty"); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("Empty"); testDatabase.ExecuteNonQuery("CREATE EXTENSION IF NOT EXISTS postgis"); var creator = GetDatabaseCreator(testDatabase); @@ -336,7 +336,7 @@ public class NpgsqlDatabaseCreatorDeleteTest : NpgsqlDatabaseCreatorTest [InlineData(false, false)] public static async Task Deletes_database(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.CreateInitialized("DeleteBlogging"); + await using var testDatabase = await NpgsqlTestStore.CreateInitializedAsync("DeleteBlogging"); testDatabase.CloseConnection(); var creator = GetDatabaseCreator(testDatabase); @@ -384,7 +384,7 @@ public class NpgsqlDatabaseCreatorCreateTablesTest : NpgsqlDatabaseCreatorTest [InlineData(false, false)] public async Task Creates_schema_in_existing_database_test(bool async, bool ambientTransaction) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("ExistingBlogging" + (async ? "Async" : "")); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("ExistingBlogging" + (async ? "Async" : "")); await using var context = new BloggingContext(testDatabase); var creator = GetDatabaseCreator(context); @@ -539,7 +539,7 @@ await testDatabase.QueryAsync( [InlineData(false)] public async Task Throws_if_database_already_exists(bool async) { - await using var testDatabase = NpgsqlTestStore.GetOrCreateInitialized("ExistingBlogging"); + await using var testDatabase = await NpgsqlTestStore.GetOrCreateInitializedAsync("ExistingBlogging"); var creator = GetDatabaseCreator(testDatabase); var ex = async diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs index 6f663d7d9..1cdb9aed7 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs @@ -8,9 +8,9 @@ public class NpgsqlValueGenerationScenariosTest private static readonly string DatabaseName = "NpgsqlValueGenerationScenariosTest"; [Fact] - public void Insert_with_sequence_id() + public async Task Insert_with_sequence_id() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextSequence(testStore.Name)) { @@ -33,9 +33,9 @@ public void Insert_with_sequence_id() public class BlogContextSequence(string databaseName) : ContextBase(databaseName); [Fact] - public void Insert_with_sequence_HiLo() + public async Task Insert_with_sequence_HiLo() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextHiLo(testStore.Name)) { @@ -66,9 +66,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_default_value_from_sequence() + public async Task Insert_with_default_value_from_sequence() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextDefaultValue(testStore.Name)) { @@ -144,9 +144,9 @@ public class BlogWithStringKey } [Fact] - public void Insert_with_key_default_value_from_sequence() + public async Task Insert_with_key_default_value_from_sequence() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextKeyColumnWithDefaultValue(testStore.Name)) { @@ -185,9 +185,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [ConditionalFact] - public void Insert_uint_to_Identity_column_using_value_converter() + public async Task Insert_uint_to_Identity_column_using_value_converter() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextUIntToIdentityUsingValueConverter(testStore.Name)) { context.Database.EnsureCreatedResiliently(); @@ -229,9 +229,9 @@ public class BlogWithUIntKey } [ConditionalFact] - public void Insert_string_to_Identity_column_using_value_converter() + public async Task Insert_string_to_Identity_column_using_value_converter() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextStringToIdentityUsingValueConverter(testStore.Name)) { context.Database.EnsureCreatedResiliently(); @@ -274,9 +274,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_explicit_non_default_keys() + public async Task Insert_with_explicit_non_default_keys() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNoKeyGeneration(testStore.Name)) { @@ -310,9 +310,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_explicit_with_default_keys() + public async Task Insert_with_explicit_with_default_keys() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNoKeyGenerationNullableKey(testStore.Name)) { @@ -346,9 +346,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_non_key_default_value() + public async Task Insert_with_non_key_default_value() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNonKeyDefaultValue(testStore.Name)) { @@ -399,9 +399,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_non_key_default_value_readonly() + public async Task Insert_with_non_key_default_value_readonly() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNonKeyReadOnlyDefaultValue(testStore.Name)) { @@ -453,9 +453,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_serial_non_id() + public async Task Insert_with_serial_non_id() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); int afterSave; @@ -491,9 +491,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } [Fact] - public void Insert_with_client_generated_GUID_key() + public async Task Insert_with_client_generated_GUID_key() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); Guid afterSave; using (var context = new BlogContext(testStore.Name)) @@ -516,9 +516,9 @@ public void Insert_with_client_generated_GUID_key() public class BlogContext(string databaseName) : ContextBase(databaseName); [Fact] - public void Insert_with_server_generated_GUID_key() + public async Task Insert_with_server_generated_GUID_key() { - using var testStore = NpgsqlTestStore.CreateInitialized(DatabaseName); + using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); Guid afterSave; using (var context = new BlogContextServerGuidKey(testStore.Name)) diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocComplexTypeQueryNpgsqlTest.cs new file mode 100644 index 000000000..cf86a2b2f --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocComplexTypeQueryNpgsqlTest.cs @@ -0,0 +1,32 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class AdHocComplexTypeQueryNpgsqlTest : AdHocComplexTypeQueryTestBase +{ + public override async Task Complex_type_equals_parameter_with_nested_types_with_property_of_same_name() + { + await base.Complex_type_equals_parameter_with_nested_types_with_property_of_same_name(); + + AssertSql( + """ +@__entity_equality_container_0_Id='1' (Nullable = true) +@__entity_equality_container_0_Containee1_Id='2' (Nullable = true) +@__entity_equality_container_0_Containee2_Id='3' (Nullable = true) + +SELECT e."Id", e."ComplexContainer_Id", e."ComplexContainer_Containee1_Id", e."ComplexContainer_Containee2_Id" +FROM "EntityType" AS e +WHERE e."ComplexContainer_Id" = @__entity_equality_container_0_Id AND e."ComplexContainer_Containee1_Id" = @__entity_equality_container_0_Containee1_Id AND e."ComplexContainer_Containee2_Id" = @__entity_equality_container_0_Containee2_Id +LIMIT 2 +"""); + } + + protected TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + + protected void AssertSql(params string[] expected) + => TestSqlLoggerFactory.AssertBaseline(expected); + + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; +} diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs index c218b8a5d..cec0ec78a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs @@ -7,7 +7,7 @@ public class AdHocJsonQueryNpgsqlTest : AdHocJsonQueryTestBase protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; - protected override void Seed29219(MyContext29219 ctx) + protected override async Task Seed29219(MyContext29219 ctx) { var entity1 = new MyEntity29219 { @@ -29,19 +29,19 @@ protected override void Seed29219(MyContext29219 ctx) }; ctx.Entities.AddRange(entity1, entity2); - ctx.SaveChanges(); + await ctx.SaveChangesAsync(); - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Id", "Reference", "Collection") VALUES(3, '{ "NonNullableScalar" : 30 }', '[{ "NonNullableScalar" : 10001 }]') """); } - protected override void Seed30028(MyContext30028 ctx) + protected override async Task Seed30028(MyContext30028 ctx) { // complete - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( @@ -50,7 +50,7 @@ protected override void Seed30028(MyContext30028 ctx) """); // missing collection - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( @@ -59,7 +59,7 @@ protected override void Seed30028(MyContext30028 ctx) """); // missing optional reference - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( @@ -68,7 +68,7 @@ protected override void Seed30028(MyContext30028 ctx) """); // missing required reference - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO "Entities" ("Id", "Json") VALUES( @@ -77,14 +77,14 @@ protected override void Seed30028(MyContext30028 ctx) """); } - protected override void Seed33046(Context33046 ctx) - => ctx.Database.ExecuteSql( + protected override async Task Seed33046(Context33046 ctx) + => await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Reviews" ("Rounds", "Id") VALUES('[{"RoundNumber":11,"SubRounds":[{"SubRoundNumber":111},{"SubRoundNumber":112}]}]', 1) """); - protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) + protected override async Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) { var entity1 = new MyEntityArrayOfPrimitives { @@ -127,11 +127,11 @@ protected override void SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) }; ctx.Entities.AddRange(entity1, entity2); - ctx.SaveChanges(); + await ctx.SaveChangesAsync(); } - protected override void SeedJunkInJson(MyContextJunkInJson ctx) - => ctx.Database.ExecuteSql( + protected override async Task SeedJunkInJson(MyContextJunkInJson ctx) + => await ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id") VALUES( @@ -142,16 +142,16 @@ protected override void SeedJunkInJson(MyContextJunkInJson ctx) 1) """); - protected override void SeedTrickyBuffering(MyContextTrickyBuffering ctx) - => ctx.Database.ExecuteSql( + protected override async Task SeedTrickyBuffering(MyContextTrickyBuffering ctx) + => await ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO "Entities" ("Reference", "Id") VALUES( '{"Name": "r1", "Number": 7, "JunkReference":{"Something": "SomeValue" }, "JunkCollection": [{"Foo": "junk value"}], "NestedReference": {"DoB": "2000-01-01T00:00:00Z"}, "NestedCollection": [{"DoB": "2000-02-01T00:00:00Z", "JunkReference": {"Something": "SomeValue"}}, {"DoB": "2000-02-02T00:00:00Z"}]}',1) """); - protected override void SeedShadowProperties(MyContextShadowProperties ctx) - => ctx.Database.ExecuteSql( + protected override async Task SeedShadowProperties(MyContextShadowProperties ctx) + => await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id", "Name") VALUES( @@ -163,9 +163,9 @@ protected override void SeedShadowProperties(MyContextShadowProperties ctx) 'e1') """); - protected override void SeedNotICollection(MyContextNotICollection ctx) + protected override async Task SeedNotICollection(MyContextNotICollection ctx) { - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Json", "Id") VALUES( @@ -173,7 +173,7 @@ protected override void SeedNotICollection(MyContextNotICollection ctx) 1) """); - ctx.Database.ExecuteSql( + await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Json", "Id") VALUES( @@ -186,12 +186,12 @@ protected override void SeedNotICollection(MyContextNotICollection ctx) public virtual async Task Json_predicate_on_bytea(bool async) { var contextFactory = await InitializeAsync( - seed: context => + seed: async context => { context.Entities.AddRange( new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 3] } }, new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Bytea = [1, 2, 4] } }); - context.SaveChanges(); + await context.SaveChangesAsync(); }); using (var context = contextFactory.CreateContext()) @@ -218,12 +218,12 @@ LIMIT 2 public virtual async Task Json_predicate_on_interval(bool async) { var contextFactory = await InitializeAsync( - seed: context => + seed: async context => { context.Entities.AddRange( new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Interval = new TimeSpan(1, 2, 3, 4, 123, 456) } }, new TypesContainerEntity { JsonEntity = new TypesJsonEntity { Interval = new TimeSpan(2, 2, 3, 4, 123, 456) } }); - context.SaveChanges(); + await context.SaveChangesAsync(); }); using (var context = contextFactory.CreateContext()) diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs index 6d44a9bdd..cf497d6a5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocMiscellaneousQueryNpgsqlTest.cs @@ -7,8 +7,8 @@ public class AdHocMiscellaneousQueryNpgsqlTest : AdHocMiscellaneousQueryRelation protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; - protected override void Seed2951(Context2951 context) - => context.Database.ExecuteSqlRaw( + protected override Task Seed2951(Context2951 context) + => context.Database.ExecuteSqlRawAsync( """ CREATE TABLE "ZeroKey" ("Id" int); INSERT INTO "ZeroKey" VALUES (NULL) diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index b89306d03..e00543860 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -835,7 +835,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE COALESCE(array_position(s."IntArray", 6) - 1, -1) = 1 +WHERE array_position(s."IntArray", 6) - 1 = 1 """); } @@ -849,7 +849,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE COALESCE(array_position(s."IntArray", 6, 2) - 1, -1) = 1 +WHERE array_position(s."IntArray", 6, 2) - 1 = 1 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs index 99e88ded6..361fb653b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs @@ -855,7 +855,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE COALESCE(array_position(s."IntList", 6) - 1, -1) = 1 +WHERE array_position(s."IntList", 6) - 1 = 1 """); } @@ -871,7 +871,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE COALESCE(array_position(s."IntList", 6, 2) - 1, -1) = 1 +WHERE array_position(s."IntList", 6, 2) - 1 = 1 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs index 2c470c46f..1809ce560 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayQueryFixture.cs @@ -16,8 +16,8 @@ public TestSqlLoggerFactory TestSqlLoggerFactory public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) => base.AddOptions(builder).ConfigureWarnings(wcb => wcb.Ignore(CoreEventId.CollectionWithoutComparer)); - protected override void Seed(ArrayQueryContext context) - => ArrayQueryContext.Seed(context); + protected override Task SeedAsync(ArrayQueryContext context) + => ArrayQueryContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs index 207ba6a23..72950440a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/BigIntegerQueryTest.cs @@ -132,10 +132,10 @@ public class BigIntegerQueryContext(DbContextOptions options) : PoolableDbContex { public DbSet Entities { get; set; } - public static void Seed(BigIntegerQueryContext context) + public static async Task SeedAsync(BigIntegerQueryContext context) { context.Entities.AddRange(BigIntegerData.CreateEntities()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -158,8 +158,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(BigIntegerQueryContext context) - => BigIntegerQueryContext.Seed(context); + protected override Task SeedAsync(BigIntegerQueryContext context) + => BigIntegerQueryContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs index 1ff63672d..d87ad0c9c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CitextQueryTest.cs @@ -298,12 +298,12 @@ public class CitextQueryContext(DbContextOptions options) : PoolableDbContext(op { public DbSet SomeEntities { get; set; } - public static void Seed(CitextQueryContext context) + public static async Task SeedAsync(CitextQueryContext context) { context.SomeEntities.AddRange( new SomeArrayEntity { Id = 1, CaseInsensitiveText = "SomeText" }, new SomeArrayEntity { Id = 2, CaseInsensitiveText = "AnotherText" }); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -326,7 +326,7 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(CitextQueryContext context) - => CitextQueryContext.Seed(context); + protected override Task SeedAsync(CitextQueryContext context) + => CitextQueryContext.SeedAsync(context); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs index 19f73cf39..ebf72bbb9 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs @@ -73,11 +73,10 @@ public virtual CompatibilityContext CreateRedshiftContext() return new CompatibilityContext(builder.Options); } - public virtual Task InitializeAsync() + public virtual async Task InitializeAsync() { _testStore = NpgsqlTestStoreFactory.Instance.GetOrCreate(StoreName); - _testStore.Initialize(null, CreateContext, c => CompatibilityContext.Seed((CompatibilityContext)c)); - return Task.CompletedTask; + await _testStore.InitializeAsync(null, CreateContext, c => CompatibilityContext.SeedAsync((CompatibilityContext)c)); } // Called after DisposeAsync @@ -99,12 +98,12 @@ public class CompatibilityContext(DbContextOptions options) : DbContext(options) { public DbSet TestEntities { get; set; } - public static void Seed(CompatibilityContext context) + public static async Task SeedAsync(CompatibilityContext context) { context.TestEntities.AddRange( new CompatibilityTestEntity { Id = 1, SomeInt = 8 }, new CompatibilityTestEntity { Id = 2, SomeInt = 10 }); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryNpgsqlTest.cs index 7626042e8..69e20028d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryNpgsqlTest.cs @@ -19,14 +19,6 @@ public ComplexNavigationsSharedTypeQueryNpgsqlTest( public override Task Subquery_with_Distinct_Skip_FirstOrDefault_without_OrderBy(bool async) => base.Subquery_with_Distinct_Skip_FirstOrDefault_without_OrderBy(async); - [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/pull/22532")] - public override Task Distinct_skip_without_orderby(bool async) - => base.Distinct_skip_without_orderby(async); - - [ConditionalTheory(Skip = "https://github.com/dotnet/efcore/pull/22532")] - public override Task Distinct_take_without_orderby(bool async) - => base.Distinct_take_without_orderby(async); - public override async Task Join_with_result_selector_returning_queryable_throws_validation_error(bool async) => await Assert.ThrowsAsync( () => base.Join_with_result_selector_returning_queryable_throws_validation_error(async)); diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs index 9ca70651d..d46b14a82 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs @@ -231,12 +231,12 @@ public override async Task Complex_type_equals_parameter(bool async) @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' @__entity_equality_address_0_Tags={ 'foo', 'bar' } (DbType = Object) @__entity_equality_address_0_ZipCode='38654' (Nullable = true) -@__entity_equality_address_0_Code='US' -@__entity_equality_address_0_FullName='United States' +@__entity_equality_address_0_Country_Code='US' +@__entity_equality_address_0_Country_FullName='United States' SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c -WHERE c."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c."ShippingAddress_AddressLine2" IS NULL AND c."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND c."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName +WHERE c."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c."ShippingAddress_AddressLine2" IS NULL AND c."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c."ShippingAddress_Country_Code" = @__entity_equality_address_0_Country_Code AND c."ShippingAddress_Country_FullName" = @__entity_equality_address_0_Country_FullName """); } @@ -263,15 +263,15 @@ public override async Task Contains_over_complex_type(bool async) @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' @__entity_equality_address_0_Tags={ 'foo', 'bar' } (DbType = Object) @__entity_equality_address_0_ZipCode='38654' (Nullable = true) -@__entity_equality_address_0_Code='US' -@__entity_equality_address_0_FullName='United States' +@__entity_equality_address_0_Country_Code='US' +@__entity_equality_address_0_Country_FullName='United States' SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" FROM "Customer" AS c WHERE EXISTS ( SELECT 1 FROM "Customer" AS c0 - WHERE c0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c0."ShippingAddress_AddressLine2" IS NULL AND c0."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND c0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName) + WHERE c0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND c0."ShippingAddress_AddressLine2" IS NULL AND c0."ShippingAddress_Tags" = @__entity_equality_address_0_Tags AND c0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND c0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Country_Code AND c0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_Country_FullName) """); } @@ -600,12 +600,12 @@ public override async Task Struct_complex_type_equals_parameter(bool async) """ @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' @__entity_equality_address_0_ZipCode='38654' (Nullable = true) -@__entity_equality_address_0_Code='US' -@__entity_equality_address_0_FullName='United States' +@__entity_equality_address_0_Country_Code='US' +@__entity_equality_address_0_Country_FullName='United States' SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName" FROM "ValuedCustomer" AS v -WHERE v."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND v."ShippingAddress_AddressLine2" IS NULL AND v."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND v."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND v."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName +WHERE v."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND v."ShippingAddress_AddressLine2" IS NULL AND v."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND v."ShippingAddress_Country_Code" = @__entity_equality_address_0_Country_Code AND v."ShippingAddress_Country_FullName" = @__entity_equality_address_0_Country_FullName """); } @@ -624,15 +624,15 @@ public override async Task Contains_over_struct_complex_type(bool async) """ @__entity_equality_address_0_AddressLine1='804 S. Lakeshore Road' @__entity_equality_address_0_ZipCode='38654' (Nullable = true) -@__entity_equality_address_0_Code='US' -@__entity_equality_address_0_FullName='United States' +@__entity_equality_address_0_Country_Code='US' +@__entity_equality_address_0_Country_FullName='United States' SELECT v."Id", v."Name", v."BillingAddress_AddressLine1", v."BillingAddress_AddressLine2", v."BillingAddress_ZipCode", v."BillingAddress_Country_Code", v."BillingAddress_Country_FullName", v."ShippingAddress_AddressLine1", v."ShippingAddress_AddressLine2", v."ShippingAddress_ZipCode", v."ShippingAddress_Country_Code", v."ShippingAddress_Country_FullName" FROM "ValuedCustomer" AS v WHERE EXISTS ( SELECT 1 FROM "ValuedCustomer" AS v0 - WHERE v0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND v0."ShippingAddress_AddressLine2" IS NULL AND v0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND v0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Code AND v0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_FullName) + WHERE v0."ShippingAddress_AddressLine1" = @__entity_equality_address_0_AddressLine1 AND v0."ShippingAddress_AddressLine2" IS NULL AND v0."ShippingAddress_ZipCode" = @__entity_equality_address_0_ZipCode AND v0."ShippingAddress_Country_Code" = @__entity_equality_address_0_Country_Code AND v0."ShippingAddress_Country_FullName" = @__entity_equality_address_0_Country_FullName) """); } @@ -1023,6 +1023,44 @@ public override async Task Same_complex_type_projected_twice_with_pushdown_as_pa AssertSql(""); } + #region GroupBy + + public override async Task GroupBy_over_property_in_nested_complex_type(bool async) + { + await base.GroupBy_over_property_in_nested_complex_type(async); + + AssertSql( + """ +SELECT c."ShippingAddress_Country_Code" AS "Code", count(*)::int AS "Count" +FROM "Customer" AS c +GROUP BY c."ShippingAddress_Country_Code" +"""); + } + + public override async Task GroupBy_over_complex_type(bool async) + { + await base.GroupBy_over_complex_type(async); + + AssertSql( + """ +SELECT c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", count(*)::int AS "Count" +FROM "Customer" AS c +GROUP BY c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +"""); + } + + public override async Task GroupBy_over_nested_complex_type(bool async) + { + await base.GroupBy_over_nested_complex_type(async); + + AssertSql( + """ +SELECT c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName", count(*)::int AS "Count" +FROM "Customer" AS c +GROUP BY c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" +"""); + } + public override async Task Entity_with_complex_type_with_group_by_and_first(bool async) { await base.Entity_with_complex_type_with_group_by_and_first(async); @@ -1046,6 +1084,76 @@ LEFT JOIN ( """); } + #endregion GroupBy + + public override async Task Projecting_property_of_complex_type_using_left_join_with_pushdown(bool async) + { + await base.Projecting_property_of_complex_type_using_left_join_with_pushdown(async); + + AssertSql( + """ +SELECT c1."BillingAddress_ZipCode" +FROM "CustomerGroup" AS c +LEFT JOIN ( + SELECT c0."Id", c0."BillingAddress_ZipCode" + FROM "Customer" AS c0 + WHERE c0."Id" > 5 +) AS c1 ON c."Id" = c1."Id" +"""); + } + + public override async Task Projecting_complex_from_optional_navigation_using_conditional(bool async) + { + await base.Projecting_complex_from_optional_navigation_using_conditional(async); + + AssertSql( + """ +@__p_0='20' + +SELECT s0."ShippingAddress_ZipCode" +FROM ( + SELECT DISTINCT s."ShippingAddress_AddressLine1", s."ShippingAddress_AddressLine2", s."ShippingAddress_Tags", s."ShippingAddress_ZipCode", s."ShippingAddress_Country_Code", s."ShippingAddress_Country_FullName" + FROM ( + SELECT c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" + FROM "CustomerGroup" AS c + LEFT JOIN "Customer" AS c0 ON c."OptionalCustomerId" = c0."Id" + ORDER BY c0."ShippingAddress_ZipCode" NULLS FIRST + LIMIT @__p_0 + ) AS s +) AS s0 +"""); } + + public override async Task Project_entity_with_complex_type_pushdown_and_then_left_join(bool async) + { + await base.Project_entity_with_complex_type_pushdown_and_then_left_join(async); + + AssertSql( +""" +@__p_0='20' +@__p_1='30' + +SELECT c3."BillingAddress_ZipCode" AS "Zip1", c4."ShippingAddress_ZipCode" AS "Zip2" +FROM ( + SELECT DISTINCT c0."Id", c0."Name", c0."BillingAddress_AddressLine1", c0."BillingAddress_AddressLine2", c0."BillingAddress_Tags", c0."BillingAddress_ZipCode", c0."BillingAddress_Country_Code", c0."BillingAddress_Country_FullName", c0."ShippingAddress_AddressLine1", c0."ShippingAddress_AddressLine2", c0."ShippingAddress_Tags", c0."ShippingAddress_ZipCode", c0."ShippingAddress_Country_Code", c0."ShippingAddress_Country_FullName" + FROM ( + SELECT c."Id", c."Name", c."BillingAddress_AddressLine1", c."BillingAddress_AddressLine2", c."BillingAddress_Tags", c."BillingAddress_ZipCode", c."BillingAddress_Country_Code", c."BillingAddress_Country_FullName", c."ShippingAddress_AddressLine1", c."ShippingAddress_AddressLine2", c."ShippingAddress_Tags", c."ShippingAddress_ZipCode", c."ShippingAddress_Country_Code", c."ShippingAddress_Country_FullName" + FROM "Customer" AS c + ORDER BY c."Id" NULLS FIRST + LIMIT @__p_0 + ) AS c0 +) AS c3 +LEFT JOIN ( + SELECT DISTINCT c2."Id", c2."Name", c2."BillingAddress_AddressLine1", c2."BillingAddress_AddressLine2", c2."BillingAddress_Tags", c2."BillingAddress_ZipCode", c2."BillingAddress_Country_Code", c2."BillingAddress_Country_FullName", c2."ShippingAddress_AddressLine1", c2."ShippingAddress_AddressLine2", c2."ShippingAddress_Tags", c2."ShippingAddress_ZipCode", c2."ShippingAddress_Country_Code", c2."ShippingAddress_Country_FullName" + FROM ( + SELECT c1."Id", c1."Name", c1."BillingAddress_AddressLine1", c1."BillingAddress_AddressLine2", c1."BillingAddress_Tags", c1."BillingAddress_ZipCode", c1."BillingAddress_Country_Code", c1."BillingAddress_Country_FullName", c1."ShippingAddress_AddressLine1", c1."ShippingAddress_AddressLine2", c1."ShippingAddress_Tags", c1."ShippingAddress_ZipCode", c1."ShippingAddress_Country_Code", c1."ShippingAddress_Country_FullName" + FROM "Customer" AS c1 + ORDER BY c1."Id" DESC NULLS LAST + LIMIT @__p_1 + ) AS c2 +) AS c4 ON c3."Id" = c4."Id" +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index f73103648..a3b899507 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -226,10 +226,10 @@ public class EnumContext(DbContextOptions options) : PoolableDbContext(options) protected override void OnModelCreating(ModelBuilder builder) => builder.HasDefaultSchema("test"); - public static void Seed(EnumContext context) + public static async Task SeedAsync(EnumContext context) { context.AddRange(EnumData.CreateSomeEnumEntities()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -311,8 +311,8 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build private EnumData _expectedData; - protected override void Seed(EnumContext context) - => EnumContext.Seed(context); + protected override Task SeedAsync(EnumContext context) + => EnumContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs index 06fd4c016..dc391c993 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FromSqlQueryNpgsqlTest.cs @@ -1,5 +1,6 @@ using System.Data.Common; using Microsoft.EntityFrameworkCore.TestModels.Northwind; +using Xunit.Sdk; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; @@ -14,8 +15,12 @@ public override Task Bad_data_error_handling_invalid_cast(bool async) public override Task Bad_data_error_handling_invalid_cast_projection(bool async) => base.Bad_data_error_handling_invalid_cast_projection(async); - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] + // The test attempts to project out a column with the wrong case; this works on other databases, and fails when EF tries to materialize. + // But in PG this fails at the database since PG is case-sensitive and the column does not exist. + public override Task FromSqlRaw_queryable_simple_different_cased_columns_and_not_enough_columns_throws(bool async) + => Assert.ThrowsAsync( + () => base.FromSqlRaw_queryable_simple_different_cased_columns_and_not_enough_columns_throws(async)); + public override async Task FromSqlInterpolated_queryable_multiple_composed_with_parameters_and_closure_parameters_interpolated( bool async) { diff --git a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs index 6c46a8ff5..fb7483f05 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FunkyDataQueryNpgsqlTest.cs @@ -77,10 +77,10 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(FunkyDataContext context) + protected override async Task SeedAsync(FunkyDataContext context) { context.FunkyCustomers.AddRange(GetExpectedData().Set()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } } diff --git a/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs index 7648c8287..bf18e9670 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs @@ -140,8 +140,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(FuzzyStringMatchContext context) - => FuzzyStringMatchContext.Seed(context); + protected override Task SeedAsync(FuzzyStringMatchContext context) + => FuzzyStringMatchContext.SeedAsync(context); } /// @@ -190,7 +190,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } - public static void Seed(FuzzyStringMatchContext context) + public static async Task SeedAsync(FuzzyStringMatchContext context) { for (var i = 1; i <= 9; i++) { @@ -199,7 +199,7 @@ public static void Seed(FuzzyStringMatchContext context) new FuzzyStringMatchTestEntity { Id = i, Text = text }); } - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs index 81b30ae7d..e2c0054d5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs @@ -49,7 +49,7 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(GearsOfWarContext context) + protected override async Task SeedAsync(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. @@ -85,10 +85,10 @@ protected override void Seed(GearsOfWarContext context) context.LocustLeaders.AddRange(locustLeaders); context.Factions.AddRange(factions); context.LocustHighCommands.AddRange(locustHighCommands); - context.SaveChanges(); + await context.SaveChangesAsync(); GearsOfWarData.WireUp2(locustLeaders, factions); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs index fa10d5213..684786a6a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs @@ -103,7 +103,7 @@ public override async Task Where_datetimeoffset_utcnow(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Timeline" <> now() """); @@ -133,7 +133,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01T00:00:00' """); @@ -145,7 +145,7 @@ public override async Task Where_datetimeoffset_hour_component(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('hour', m."Timeline" AT TIME ZONE 'UTC')::int = 10 """); @@ -163,7 +163,7 @@ await AssertQuery( """ @__dateTimeOffset_Date_0='0002-03-01T00:00:00.0000000' -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", 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 """); @@ -188,7 +188,7 @@ await AssertQuery( @__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" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", 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) """); @@ -297,7 +297,7 @@ public override async Task Where_TimeSpan_Minutes(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE floor(date_part('minute', m."Duration"))::int = 2 """); @@ -309,7 +309,7 @@ public override async Task Where_TimeSpan_Seconds(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE floor(date_part('second', m."Duration"))::int = 3 """); @@ -321,7 +321,7 @@ public override async Task Where_TimeSpan_Milliseconds(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE floor(date_part('millisecond', m."Duration"))::int % 1000 = 456 """); @@ -337,7 +337,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") / 86400.0 < 0.042000000000000003 """); @@ -353,7 +353,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") / 3600.0 < 1.02 """); @@ -369,7 +369,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") / 60.0 < 61.0 """); @@ -385,7 +385,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") < 3700.0 """); @@ -401,7 +401,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('epoch', m."Duration") / 0.001 < 3700000.0 """); @@ -491,7 +491,7 @@ public override async Task Where_DateOnly_Month(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('month', m."Date")::int = 11 """); @@ -503,7 +503,7 @@ public override async Task Where_DateOnly_Day(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('day', m."Date")::int = 10 """); @@ -515,7 +515,7 @@ public override async Task Where_DateOnly_DayOfYear(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('doy', m."Date")::int = 314 """); @@ -527,7 +527,7 @@ public override async Task Where_DateOnly_DayOfWeek(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE floor(date_part('dow', m."Date"))::int = 6 """); @@ -539,7 +539,7 @@ public override async Task Where_DateOnly_AddYears(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE CAST(m."Date" + INTERVAL '3 years' AS date) = DATE '1993-11-10' """); @@ -551,7 +551,7 @@ public override async Task Where_DateOnly_AddMonths(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE CAST(m."Date" + INTERVAL '3 months' AS date) = DATE '1991-02-10' """); @@ -563,7 +563,7 @@ public override async Task Where_DateOnly_AddDays(bool async) AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Date" + 3 = DATE '1990-11-13' """); @@ -644,7 +644,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('hour', m."Time")::int = 10 """); @@ -658,7 +658,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('minute', m."Time")::int = 15 """); @@ -672,7 +672,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_part('second', m."Time")::int = 50 """); @@ -689,7 +689,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" + INTERVAL '3 hours' = TIME '13:15:50.5' """); @@ -703,7 +703,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" + INTERVAL '3 mins' = TIME '10:18:50.5' """); @@ -717,7 +717,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" + INTERVAL '03:00:00' = TIME '13:15:50.5' """); @@ -731,7 +731,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" >= TIME '10:00:00' AND m."Time" < TIME '11:00:00' """); @@ -745,7 +745,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time" - TIME '10:00:00' = INTERVAL '00:15:50.5' """); @@ -762,7 +762,7 @@ public virtual async Task TimeOnly_FromTimeSpan() AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Duration"::time without time zone = TIME '01:02:03' LIMIT 2 @@ -779,7 +779,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE m."Time"::interval = INTERVAL '15:30:10' """); diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs index b8bc094a2..3e26de810 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs @@ -598,7 +598,7 @@ public class JsonDomQueryContext(DbContextOptions options) : PoolableDbContext(o public DbSet JsonbEntities { get; set; } public DbSet JsonEntities { get; set; } - public static void Seed(JsonDomQueryContext context) + public static async Task SeedAsync(JsonDomQueryContext context) { var (customer1, customer2, customer3) = (CreateCustomer1(), CreateCustomer2(), CreateCustomer3()); @@ -640,7 +640,8 @@ public static void Seed(JsonDomQueryContext context) CustomerDocument = customer3, CustomerElement = customer3.RootElement }); - context.SaveChanges(); + + await context.SaveChangesAsync(); static JsonDocument CreateCustomer1() => JsonDocument.Parse( @@ -765,8 +766,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(JsonDomQueryContext context) - => JsonDomQueryContext.Seed(context); + protected override Task SeedAsync(JsonDomQueryContext context) + => JsonDomQueryContext.SeedAsync(context); } #endregion diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs index 096206a07..55668f31f 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs @@ -709,7 +709,7 @@ public class JsonPocoQueryContext(DbContextOptions options) : PoolableDbContext( public DbSet JsonbEntities { get; set; } public DbSet JsonEntities { get; set; } - public static void Seed(JsonPocoQueryContext context) + public static async Task SeedAsync(JsonPocoQueryContext context) { context.JsonbEntities.AddRange( new JsonbEntity @@ -727,7 +727,8 @@ public static void Seed(JsonPocoQueryContext context) ToplevelArray = ["one", "two", "three"] }, new JsonEntity { Id = 2, Customer = CreateCustomer2() }); - context.SaveChanges(); + + await context.SaveChangesAsync(); static Customer CreateCustomer1() => new() @@ -861,8 +862,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(JsonPocoQueryContext context) - => JsonPocoQueryContext.Seed(context); + protected override Task SeedAsync(JsonPocoQueryContext context) + => JsonPocoQueryContext.SeedAsync(context); } public class Customer diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs index 53c34d6a3..9af98af68 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs @@ -34,6 +34,17 @@ public override async Task Basic_json_projection_owner_entity_NoTracking(bool as """); } + public override async Task Basic_json_projection_owner_entity_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owner_entity_duplicated(bool async) { await base.Basic_json_projection_owner_entity_duplicated(async); @@ -56,6 +67,17 @@ public override async Task Basic_json_projection_owner_entity_duplicated_NoTrack """); } + public override async Task Basic_json_projection_owner_entity_duplicated_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_duplicated_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."Name", j."OwnedCollection", j."OwnedCollection" +FROM "JsonEntitiesSingleOwned" AS j +"""); + } + public override async Task Basic_json_projection_owner_entity_twice(bool async) { await base.Basic_json_projection_owner_entity_twice(async); @@ -78,6 +100,17 @@ public override async Task Basic_json_projection_owner_entity_twice_NoTracking(b """); } + public override async Task Basic_json_projection_owner_entity_twice_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_twice_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Project_json_reference_in_tracking_query_fails(bool async) { await base.Project_json_reference_in_tracking_query_fails(async); @@ -113,6 +146,17 @@ public override async Task Basic_json_projection_owned_reference_root(bool async """); } + public override async Task Basic_json_projection_owned_reference_root_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_root_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot", j."Id" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owned_reference_duplicated2(bool async) { await base.Basic_json_projection_owned_reference_duplicated2(async); @@ -125,6 +169,18 @@ ORDER BY j."Id" NULLS FIRST """); } + public override async Task Basic_json_projection_owned_reference_duplicated2_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_duplicated2_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot", j."Id", j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedReferenceLeaf}', j."OwnedReferenceRoot", j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedReferenceLeaf}' +FROM "JsonEntitiesBasic" AS j +ORDER BY j."Id" NULLS FIRST +"""); + } + public override async Task Basic_json_projection_owned_reference_duplicated(bool async) { await base.Basic_json_projection_owned_reference_duplicated(async); @@ -137,6 +193,18 @@ ORDER BY j."Id" NULLS FIRST """); } + public override async Task Basic_json_projection_owned_reference_duplicated_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_duplicated_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot", j."Id", j."OwnedReferenceRoot" -> 'OwnedReferenceBranch', j."OwnedReferenceRoot", j."OwnedReferenceRoot" -> 'OwnedReferenceBranch' +FROM "JsonEntitiesBasic" AS j +ORDER BY j."Id" NULLS FIRST +"""); + } + public override async Task Basic_json_projection_owned_collection_root(bool async) { await base.Basic_json_projection_owned_collection_root(async); @@ -148,6 +216,17 @@ public override async Task Basic_json_projection_owned_collection_root(bool asyn """); } + public override async Task Basic_json_projection_owned_collection_root_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_collection_root_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedCollectionRoot", j."Id" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owned_reference_branch(bool async) { await base.Basic_json_projection_owned_reference_branch(async); @@ -159,6 +238,17 @@ public override async Task Basic_json_projection_owned_reference_branch(bool asy """); } + public override async Task Basic_json_projection_owned_reference_branch_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_branch_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot" -> 'OwnedReferenceBranch', j."Id" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owned_collection_branch(bool async) { await base.Basic_json_projection_owned_collection_branch(async); @@ -170,6 +260,17 @@ public override async Task Basic_json_projection_owned_collection_branch(bool as """); } + public override async Task Basic_json_projection_owned_collection_branch_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_collection_branch_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."OwnedReferenceRoot" -> 'OwnedCollectionBranch', j."Id" +FROM "JsonEntitiesBasic" AS j +"""); + } + public override async Task Basic_json_projection_owned_reference_leaf(bool async) { await base.Basic_json_projection_owned_reference_leaf(async); @@ -232,7 +333,7 @@ public override async Task Json_projection_enum_with_custom_conversion(bool asyn AssertSql( """ -SELECT j."Id", CAST(j.json_reference_custom_naming ->> 'CustomEnum' AS integer) AS "Enum" +SELECT j."Id", CAST(j.json_reference_custom_naming ->> '1CustomEnum' AS integer) AS "Enum" FROM "JsonEntitiesCustomNaming" AS j """); } @@ -456,7 +557,7 @@ public override async Task Custom_naming_projection_owned_reference(bool async) AssertSql( """ -SELECT j.json_reference_custom_naming -> 'CustomOwnedReferenceBranch', j."Id" +SELECT j.json_reference_custom_naming -> 'Custom#OwnedReferenceBranch`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?独角兽π獨角獸', j."Id" FROM "JsonEntitiesCustomNaming" AS j """); } @@ -479,7 +580,7 @@ public override async Task Custom_naming_projection_owned_scalar(bool async) AssertSql( """ -SELECT CAST(j.json_reference_custom_naming #>> '{CustomOwnedReferenceBranch,CustomFraction}' AS double precision) +SELECT CAST(j.json_reference_custom_naming #>> ARRAY['Custom#OwnedReferenceBranch`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?独角兽π獨角獸','ユニコーンFraction一角獣']::text[] AS double precision) FROM "JsonEntitiesCustomNaming" AS j """); } @@ -490,7 +591,7 @@ public override async Task Custom_naming_projection_everything(bool async) AssertSql( """ -SELECT j."Id", j."Title", j.json_collection_custom_naming, j.json_reference_custom_naming, j.json_reference_custom_naming, j.json_reference_custom_naming -> 'CustomOwnedReferenceBranch', j.json_collection_custom_naming, j.json_reference_custom_naming -> 'CustomOwnedCollectionBranch', j.json_reference_custom_naming ->> 'CustomName', CAST(j.json_reference_custom_naming #>> '{CustomOwnedReferenceBranch,CustomFraction}' AS double precision) +SELECT j."Id", j."Title", j.json_collection_custom_naming, j.json_reference_custom_naming, j.json_reference_custom_naming, j.json_reference_custom_naming -> 'Custom#OwnedReferenceBranch`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?独角兽π獨角獸', j.json_collection_custom_naming, j.json_reference_custom_naming -> 'CustomOwnedCollectionBranch', j.json_reference_custom_naming ->> 'CustomName', CAST(j.json_reference_custom_naming #>> ARRAY['Custom#OwnedReferenceBranch`-=[]\;'',./~!@#$%^&*()_+{}|:"<>?独角兽π獨角獸','ユニコーンFraction一角獣']::text[] AS double precision) FROM "JsonEntitiesCustomNaming" AS j """); } @@ -1566,7 +1667,12 @@ public override async Task Json_collection_of_primitives_SelectMany(bool async) { await base.Json_collection_of_primitives_SelectMany(async); - AssertSql(""); + AssertSql( + """ +SELECT n.value +FROM "JsonEntitiesBasic" AS j +JOIN LATERAL unnest((ARRAY(SELECT CAST(element AS text) FROM jsonb_array_elements_text(j."OwnedReferenceRoot" -> 'Names') WITH ORDINALITY AS t(element) ORDER BY ordinality))) AS n(value) ON TRUE +"""); } public override async Task Json_collection_of_primitives_index_used_in_predicate(bool async) @@ -2267,7 +2373,7 @@ public override async Task Json_all_types_entity_projection(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j """); } @@ -2300,7 +2406,7 @@ public override async Task Json_boolean_predicate(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE CAST(j."Reference" ->> 'TestBoolean' AS boolean) """); @@ -2312,7 +2418,7 @@ public override async Task Json_boolean_predicate_negated(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE NOT (CAST(j."Reference" ->> 'TestBoolean' AS boolean)) """); @@ -2346,7 +2452,7 @@ public override async Task Json_predicate_on_default_string(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (j."Reference" ->> 'TestDefaultString') <> 'MyDefaultStringInReference1' OR (j."Reference" ->> 'TestDefaultString') IS NULL """); @@ -2358,7 +2464,7 @@ public override async Task Json_predicate_on_max_length_string(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (j."Reference" ->> 'TestMaxLengthString') <> 'Foo' OR (j."Reference" ->> 'TestMaxLengthString') IS NULL """); @@ -2370,7 +2476,7 @@ public override async Task Json_predicate_on_string_condition(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE CASE WHEN NOT (CAST(j."Reference" ->> 'TestBoolean' AS boolean)) THEN j."Reference" ->> 'TestMaxLengthString' @@ -2385,19 +2491,31 @@ public override async Task Json_predicate_on_byte(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestByte' AS smallint)) <> 3 OR (CAST(j."Reference" ->> 'TestByte' AS smallint)) IS NULL """); } + public override async Task Json_predicate_on_byte_array(bool async) + { + await base.Json_predicate_on_byte_array(async); + + AssertSql( + """ +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +FROM "JsonEntitiesAllTypes" AS j +WHERE (decode(j."Reference" ->> 'TestByteArray', 'base64')) <> BYTEA E'\\x010203' OR (decode(j."Reference" ->> 'TestByteArray', 'base64')) IS NULL +"""); + } + public override async Task Json_predicate_on_character(bool async) { await base.Json_predicate_on_character(async); AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestCharacter' AS character(1))) <> 'z' OR (CAST(j."Reference" ->> 'TestCharacter' AS character(1))) IS NULL """); @@ -2409,7 +2527,7 @@ public override async Task Json_predicate_on_datetime(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDateTime' AS timestamp without time zone)) <> TIMESTAMP '2000-01-03T00:00:00' OR (CAST(j."Reference" ->> 'TestDateTime' AS timestamp without time zone)) IS NULL """); @@ -2421,7 +2539,7 @@ public override async Task Json_predicate_on_datetimeoffset(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDateTimeOffset' AS timestamp with time zone)) <> TIMESTAMPTZ '2000-01-04T00:00:00+03:02' OR (CAST(j."Reference" ->> 'TestDateTimeOffset' AS timestamp with time zone)) IS NULL """); @@ -2433,7 +2551,7 @@ public override async Task Json_predicate_on_decimal(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDecimal' AS numeric(18,3))) <> 1.35 OR (CAST(j."Reference" ->> 'TestDecimal' AS numeric(18,3))) IS NULL """); @@ -2445,7 +2563,7 @@ public override async Task Json_predicate_on_double(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDouble' AS double precision)) <> 33.25 OR (CAST(j."Reference" ->> 'TestDouble' AS double precision)) IS NULL """); @@ -2457,7 +2575,7 @@ public override async Task Json_predicate_on_enum(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestEnum' AS integer)) <> 2 OR (CAST(j."Reference" ->> 'TestEnum' AS integer)) IS NULL """); @@ -2469,7 +2587,7 @@ public override async Task Json_predicate_on_enumwithintconverter(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestEnumWithIntConverter' AS integer)) <> -3 OR (CAST(j."Reference" ->> 'TestEnumWithIntConverter' AS integer)) IS NULL """); @@ -2481,7 +2599,7 @@ public override async Task Json_predicate_on_guid(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestGuid' AS uuid)) <> '00000000-0000-0000-0000-000000000000' OR (CAST(j."Reference" ->> 'TestGuid' AS uuid)) IS NULL """); @@ -2493,7 +2611,7 @@ public override async Task Json_predicate_on_int16(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestInt16' AS smallint)) <> 3 OR (CAST(j."Reference" ->> 'TestInt16' AS smallint)) IS NULL """); @@ -2505,7 +2623,7 @@ public override async Task Json_predicate_on_int32(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestInt32' AS integer)) <> 33 OR (CAST(j."Reference" ->> 'TestInt32' AS integer)) IS NULL """); @@ -2517,7 +2635,7 @@ public override async Task Json_predicate_on_int64(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestInt64' AS bigint)) <> 333 OR (CAST(j."Reference" ->> 'TestInt64' AS bigint)) IS NULL """); @@ -2529,7 +2647,7 @@ public override async Task Json_predicate_on_nullableenum1(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) <> -1 OR (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) IS NULL """); @@ -2541,7 +2659,7 @@ public override async Task Json_predicate_on_nullableenum2(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableEnum' AS integer)) IS NOT NULL """); @@ -2553,7 +2671,7 @@ public override async Task Json_predicate_on_nullableenumwithconverter1(bool asy AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) <> 2 OR (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) IS NULL """); @@ -2565,7 +2683,7 @@ public override async Task Json_predicate_on_nullableenumwithconverter2(bool asy AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableEnumWithIntConverter' AS integer)) IS NOT NULL """); @@ -2577,7 +2695,7 @@ public override async Task Json_predicate_on_nullableenumwithconverterthathandle AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (j."Reference" ->> 'TestNullableEnumWithConverterThatHandlesNulls') <> 'One' OR (j."Reference" ->> 'TestNullableEnumWithConverterThatHandlesNulls') IS NULL """); @@ -2599,7 +2717,7 @@ public override async Task Json_predicate_on_nullableint321(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableInt32' AS integer)) <> 100 OR (CAST(j."Reference" ->> 'TestNullableInt32' AS integer)) IS NULL """); @@ -2611,7 +2729,7 @@ public override async Task Json_predicate_on_nullableint322(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestNullableInt32' AS integer)) IS NOT NULL """); @@ -2623,7 +2741,7 @@ public override async Task Json_predicate_on_signedbyte(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestSignedByte' AS smallint)) <> 100 OR (CAST(j."Reference" ->> 'TestSignedByte' AS smallint)) IS NULL """); @@ -2635,7 +2753,7 @@ public override async Task Json_predicate_on_single(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestSingle' AS real)) <> 10.4 OR (CAST(j."Reference" ->> 'TestSingle' AS real)) IS NULL """); @@ -2647,7 +2765,7 @@ public override async Task Json_predicate_on_timespan(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestTimeSpan' AS interval)) <> INTERVAL '03:02:00' OR (CAST(j."Reference" ->> 'TestTimeSpan' AS interval)) IS NULL """); @@ -2659,7 +2777,7 @@ public override async Task Json_predicate_on_dateonly(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestDateOnly' AS date)) <> DATE '0003-02-01' OR (CAST(j."Reference" ->> 'TestDateOnly' AS date)) IS NULL """); @@ -2671,7 +2789,7 @@ public override async Task Json_predicate_on_timeonly(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestTimeOnly' AS time without time zone)) <> TIME '03:02:00' OR (CAST(j."Reference" ->> 'TestTimeOnly' AS time without time zone)) IS NULL """); @@ -2683,7 +2801,7 @@ public override async Task Json_predicate_on_unisgnedint16(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestUnsignedInt16' AS integer)) <> 100 OR (CAST(j."Reference" ->> 'TestUnsignedInt16' AS integer)) IS NULL """); @@ -2695,7 +2813,7 @@ public override async Task Json_predicate_on_unsignedint32(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestUnsignedInt32' AS bigint)) <> 1000 OR (CAST(j."Reference" ->> 'TestUnsignedInt32' AS bigint)) IS NULL """); @@ -2707,7 +2825,7 @@ public override async Task Json_predicate_on_unsignedint64(bool async) AssertSql( """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE (CAST(j."Reference" ->> 'TestUnsignedInt64' AS numeric(20,0))) <> 10000.0 OR (CAST(j."Reference" ->> 'TestUnsignedInt64' AS numeric(20,0))) IS NULL """); @@ -2821,8 +2939,6 @@ public override async Task Json_predicate_on_string_Y_N_converted_to_bool(bool a """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_basic(bool async) { await base.FromSql_on_entity_with_json_basic(async); @@ -2836,8 +2952,6 @@ public override async Task FromSql_on_entity_with_json_basic(bool async) """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_project_json_reference(bool async) { await base.FromSql_on_entity_with_json_project_json_reference(async); @@ -2851,8 +2965,6 @@ public override async Task FromSql_on_entity_with_json_project_json_reference(bo """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_project_json_collection(bool async) { await base.FromSql_on_entity_with_json_project_json_collection(async); @@ -2866,8 +2978,6 @@ public override async Task FromSql_on_entity_with_json_project_json_collection(b """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_inheritance_on_base(bool async) { await base.FromSql_on_entity_with_json_inheritance_on_base(async); @@ -2881,8 +2991,6 @@ public override async Task FromSql_on_entity_with_json_inheritance_on_base(bool """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_inheritance_on_derived(bool async) { await base.FromSql_on_entity_with_json_inheritance_on_derived(async); @@ -2897,8 +3005,6 @@ public override async Task FromSql_on_entity_with_json_inheritance_on_derived(bo """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_inheritance_project_reference_on_base(bool async) { await base.FromSql_on_entity_with_json_inheritance_project_reference_on_base(async); @@ -2913,8 +3019,6 @@ ORDER BY m."Id" NULLS FIRST """); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] public override async Task FromSql_on_entity_with_json_inheritance_project_reference_on_derived(bool async) { await base.FromSql_on_entity_with_json_inheritance_project_reference_on_derived(async); @@ -2930,6 +3034,335 @@ ORDER BY m."Id" NULLS FIRST """); } + public override async Task Json_projection_using_queryable_methods_on_top_of_JSON_collection_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_using_queryable_methods_on_top_of_JSON_collection_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_nested_collection_anonymous_projection_in_projection_NoTrackingWithIdentityResolution(bool async) + { + await base.Json_nested_collection_anonymous_projection_in_projection_NoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_nested_collection_and_element_using_parameter_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nested_collection_and_element_using_parameter_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_nested_collection_and_element_using_parameter_AsNoTrackingWithIdentityResolution2(bool async) + { + await base.Json_projection_nested_collection_and_element_using_parameter_AsNoTrackingWithIdentityResolution2(async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_different_values_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_different_values_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_projected_before_owner_nested_AsNoTrackingWithIdentityResolution2( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_projected_before_owner_nested_AsNoTrackingWithIdentityResolution2( + async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_projected_after_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_projected_after_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( +); + } + + public override async Task + Json_projection_second_element_through_collection_element_constant_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_constant_projected_before_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( +); + } + + public override async Task Json_branch_collection_distinct_and_other_collection_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_branch_collection_distinct_and_other_collection_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_collection_SelectMany_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_collection_SelectMany_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_deduplication_with_collection_indexer_in_target_AsNoTrackingWithIdentityResolution( + bool async) + { + await base.Json_projection_deduplication_with_collection_indexer_in_target_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_nested_collection_and_element_wrong_order_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nested_collection_and_element_wrong_order_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_second_element_projected_before_entire_collection_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_entire_collection_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_second_element_projected_before_owner_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_owner_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_second_element_projected_before_owner_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_owner_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( +); + } + + public override async Task Json_projection_collection_element_and_reference_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_collection_element_and_reference_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,1}', j."OwnedReferenceRoot" -> 'OwnedReferenceBranch' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_nothing_interesting_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nothing_interesting_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."Name" +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_owner_entity_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_owner_entity_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_nested_collection_anonymous_projection_of_primitives_in_projection_NoTrackingWithIdentityResolution(bool async) + { + await base.Json_nested_collection_anonymous_projection_of_primitives_in_projection_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", s.ordinality, s.c, s.c0, s.c1, s.c2, s.ordinality0 +FROM "JsonEntitiesBasic" AS j +LEFT JOIN LATERAL ( + SELECT o.ordinality, o0."Date" AS c, o0."Enum" AS c0, o0."Enums" AS c1, o0."Fraction" AS c2, o0.ordinality AS ordinality0 + FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedCollectionBranch" jsonb)) WITH ORDINALITY AS o + LEFT JOIN LATERAL ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( + "Date" timestamp without time zone, + "Enum" integer, + "Enums" integer[], + "Fraction" numeric(18,2) + )) WITH ORDINALITY AS o0 ON TRUE +) AS s ON TRUE +ORDER BY j."Id" NULLS FIRST, s.ordinality NULLS FIRST +"""); + } + + public override async Task Json_projection_second_element_through_collection_element_constant_projected_after_owner_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_through_collection_element_constant_projected_after_owner_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_reference_collection_and_collection_element_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_reference_collection_and_collection_element_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedReferenceLeaf}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task + Json_projection_second_element_through_collection_element_parameter_correctly_projected_after_owner_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_second_element_through_collection_element_parameter_correctly_projected_after_owner_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( + """ +@__prm_0='1' + +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf}', j."OwnedReferenceRoot" #> ARRAY['OwnedCollectionBranch',0,'OwnedCollectionLeaf',@__prm_0]::text[], @__prm_0 +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task + Json_projection_only_second_element_through_collection_element_constant_projected_nested_AsNoTrackingWithIdentityResolution( + bool async) + { + await base + .Json_projection_only_second_element_through_collection_element_constant_projected_nested_AsNoTrackingWithIdentityResolution( + async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_only_second_element_through_collection_element_parameter_projected_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_only_second_element_through_collection_element_parameter_projected_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +@__prm1_0='0' +@__prm2_1='1' + +SELECT j."Id", j."OwnedReferenceRoot" #> ARRAY['OwnedCollectionBranch',@__prm1_0,'OwnedCollectionLeaf',@__prm2_1]::text[], @__prm1_0, @__prm2_1 +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_second_element_through_collection_element_constant_different_values_projected_before_owner_nested_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_through_collection_element_constant_different_values_projected_before_owner_nested_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,1,OwnedCollectionLeaf}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_nested_collection_and_element_correct_order_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nested_collection_and_element_correct_order_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf}', j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,0,OwnedCollectionLeaf,1}' +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_nested_collection_element_using_parameter_and_the_owner_in_correct_order_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_nested_collection_element_using_parameter_and_the_owner_in_correct_order_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +@__prm_0='0' + +SELECT j."Id", j."OwnedReferenceRoot", j."OwnedReferenceRoot" #> ARRAY['OwnedCollectionBranch',@__prm_0,'OwnedCollectionLeaf',1]::text[], @__prm_0 +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_second_element_projected_before_owner_as_well_as_root_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_owner_as_well_as_root_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedCollectionBranch,1}', j."OwnedReferenceRoot", j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + + public override async Task Json_projection_second_element_projected_before_owner_nested_as_well_as_root_AsNoTrackingWithIdentityResolution(bool async) + { + await base.Json_projection_second_element_projected_before_owner_nested_as_well_as_root_AsNoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT j."Id", j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedCollectionLeaf,1}', j."OwnedReferenceRoot" #> '{OwnedReferenceBranch,OwnedCollectionLeaf}', j."OwnedReferenceRoot" -> 'OwnedReferenceBranch', j."EntityBasicId", j."Name", j."OwnedCollectionRoot", j."OwnedReferenceRoot" +FROM "JsonEntitiesBasic" AS j +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); @@ -2937,12 +3370,55 @@ public virtual void Check_all_tests_overridden() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class JsonQueryNpgsqlFixture : JsonQueryFixtureBase + public class JsonQueryNpgsqlFixture : JsonQueryFixtureBase, IQueryFixtureBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; private JsonQueryData _expectedData; + private readonly IReadOnlyDictionary _entityAsserters; + + public JsonQueryNpgsqlFixture() + { + var entityAsserters = base.EntityAsserters.ToDictionary(); + + entityAsserters[typeof(JsonEntityAllTypes)] = (object e, object a) => + { + Assert.Equal(e == null, a == null); + if (a != null) + { + var ee = (JsonEntityAllTypes)e; + var aa = (JsonEntityAllTypes)a; + + Assert.Equal(ee.Id, aa.Id); + + AssertAllTypes(ee.Reference, aa.Reference); + + Assert.Equal(ee.Collection?.Count ?? 0, aa.Collection?.Count ?? 0); + for (var i = 0; i < ee.Collection.Count; i++) + { + AssertAllTypes(ee.Collection[i], aa.Collection[i]); + } + } + }; + + entityAsserters[typeof(JsonOwnedAllTypes)] = (object e, object a) => + { + Assert.Equal(e == null, a == null); + if (a != null) + { + var ee = (JsonOwnedAllTypes)e; + var aa = (JsonOwnedAllTypes)a; + + AssertAllTypes(ee, aa); + } + }; + + _entityAsserters = entityAsserters; + } + + IReadOnlyDictionary IQueryFixtureBase.EntityAsserters + => _entityAsserters; protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { @@ -2978,6 +3454,77 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Ignore(j => j.TestDoubleCollection); b.Ignore(j => j.TestInt16Collection); }); + + // These use collection types which are unsupported for arrays at the Npgsql level - we currently only support List/array. + modelBuilder.Entity( + b => + { + b.Ignore(j => j.TestInt64Collection); + b.Ignore(j => j.TestGuidCollection); + }); + + // Ignore nested collections - these aren't supported on PostgreSQL (no arrays of arrays). + // TODO: Remove these after syncing to 9.0.0-rc.1, and extending from the relational test base and fixture + modelBuilder.Entity( + b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); + + modelBuilder.Entity().OwnsOne( + x => x.Reference, b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); + + modelBuilder.Entity().OwnsMany( + x => x.Collection, b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); } public override ISetSource GetExpectedData() @@ -3009,7 +3556,7 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(JsonQueryContext context) + protected override async Task SeedAsync(JsonQueryContext context) { // The test data contains DateTimeOffsets with various offsets, which we don't support. Change these to UTC. // Also chop sub-microsecond precision which PostgreSQL does not support. @@ -3053,7 +3600,66 @@ protected override void Seed(JsonQueryContext context) dto => new DateTimeOffset(dto.Ticks - (dto.Ticks % (TimeSpan.TicksPerMillisecond / 1000)), TimeSpan.Zero)).ToList(); } - context.SaveChanges(); + await context.SaveChangesAsync(); + } + + public static new void AssertAllTypes(JsonOwnedAllTypes expected, JsonOwnedAllTypes actual) + { + Assert.Equal(expected.TestDefaultString, actual.TestDefaultString); + Assert.Equal(expected.TestMaxLengthString, actual.TestMaxLengthString); + Assert.Equal(expected.TestBoolean, actual.TestBoolean); + Assert.Equal(expected.TestCharacter, actual.TestCharacter); + Assert.Equal(expected.TestDateTime, actual.TestDateTime); + Assert.Equal(expected.TestDateTimeOffset, actual.TestDateTimeOffset); + Assert.Equal(expected.TestDouble, actual.TestDouble); + Assert.Equal(expected.TestGuid, actual.TestGuid); + Assert.Equal(expected.TestInt16, actual.TestInt16); + Assert.Equal(expected.TestInt32, actual.TestInt32); + Assert.Equal(expected.TestInt64, actual.TestInt64); + Assert.Equal(expected.TestSignedByte, actual.TestSignedByte); + Assert.Equal(expected.TestSingle, actual.TestSingle); + Assert.Equal(expected.TestTimeSpan, actual.TestTimeSpan); + Assert.Equal(expected.TestDateOnly, actual.TestDateOnly); + Assert.Equal(expected.TestTimeOnly, actual.TestTimeOnly); + Assert.Equal(expected.TestUnsignedInt16, actual.TestUnsignedInt16); + Assert.Equal(expected.TestUnsignedInt32, actual.TestUnsignedInt32); + Assert.Equal(expected.TestUnsignedInt64, actual.TestUnsignedInt64); + Assert.Equal(expected.TestNullableInt32, actual.TestNullableInt32); + Assert.Equal(expected.TestEnum, actual.TestEnum); + Assert.Equal(expected.TestEnumWithIntConverter, actual.TestEnumWithIntConverter); + Assert.Equal(expected.TestNullableEnum, actual.TestNullableEnum); + Assert.Equal(expected.TestNullableEnumWithIntConverter, actual.TestNullableEnumWithIntConverter); + Assert.Equal(expected.TestNullableEnumWithConverterThatHandlesNulls, actual.TestNullableEnumWithConverterThatHandlesNulls); + + AssertPrimitiveCollection(expected.TestDefaultStringCollection, actual.TestDefaultStringCollection); + AssertPrimitiveCollection(expected.TestMaxLengthStringCollection, actual.TestMaxLengthStringCollection); + AssertPrimitiveCollection(expected.TestBooleanCollection, actual.TestBooleanCollection); + AssertPrimitiveCollection(expected.TestCharacterCollection, actual.TestCharacterCollection); + AssertPrimitiveCollection(expected.TestDateTimeCollection, actual.TestDateTimeCollection); + AssertPrimitiveCollection(expected.TestDateTimeOffsetCollection, actual.TestDateTimeOffsetCollection); + AssertPrimitiveCollection(expected.TestDoubleCollection, actual.TestDoubleCollection); + AssertPrimitiveCollection(expected.TestGuidCollection, actual.TestGuidCollection); + AssertPrimitiveCollection((IList)expected.TestInt16Collection, (IList)actual.TestInt16Collection); + AssertPrimitiveCollection(expected.TestInt32Collection, actual.TestInt32Collection); + AssertPrimitiveCollection(expected.TestInt64Collection, actual.TestInt64Collection); + AssertPrimitiveCollection(expected.TestSignedByteCollection, actual.TestSignedByteCollection); + AssertPrimitiveCollection(expected.TestSingleCollection, actual.TestSingleCollection); + AssertPrimitiveCollection(expected.TestTimeSpanCollection, actual.TestTimeSpanCollection); + AssertPrimitiveCollection(expected.TestDateOnlyCollection, actual.TestDateOnlyCollection); + AssertPrimitiveCollection(expected.TestTimeOnlyCollection, actual.TestTimeOnlyCollection); + AssertPrimitiveCollection(expected.TestUnsignedInt16Collection, actual.TestUnsignedInt16Collection); + AssertPrimitiveCollection(expected.TestUnsignedInt32Collection, actual.TestUnsignedInt32Collection); + AssertPrimitiveCollection(expected.TestUnsignedInt64Collection, actual.TestUnsignedInt64Collection); + AssertPrimitiveCollection(expected.TestNullableInt32Collection, actual.TestNullableInt32Collection); + AssertPrimitiveCollection(expected.TestEnumCollection, actual.TestEnumCollection); + AssertPrimitiveCollection(expected.TestEnumWithIntConverterCollection, actual.TestEnumWithIntConverterCollection); + AssertPrimitiveCollection(expected.TestNullableEnumCollection, actual.TestNullableEnumCollection); + AssertPrimitiveCollection( + expected.TestNullableEnumWithIntConverterCollection, actual.TestNullableEnumWithIntConverterCollection); + + // AssertPrimitiveCollection( + // expected.TestNullableEnumWithConverterThatHandlesNullsCollection, + // actual.TestNullableEnumWithConverterThatHandlesNullsCollection); } } } diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs index 403b2be92..5504d001c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs @@ -251,7 +251,7 @@ public class JsonStringQueryContext(DbContextOptions options) : PoolableDbContex { public DbSet JsonEntities { get; set; } - public static void Seed(JsonStringQueryContext context) + public static async Task SeedAsync(JsonStringQueryContext context) { const string customer1 = @" { @@ -331,7 +331,8 @@ public static void Seed(JsonStringQueryContext context) CustomerJsonb = array, CustomerJson = array }); - context.SaveChanges(); + + await context.SaveChangesAsync(); } } @@ -359,8 +360,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(JsonStringQueryContext context) - => JsonStringQueryContext.Seed(context); + protected override Task SeedAsync(JsonStringQueryContext context) + => JsonStringQueryContext.SeedAsync(context); } #endregion diff --git a/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs index 91781976a..c09820ac1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LTreeQueryTest.cs @@ -506,7 +506,7 @@ public class LTreeQueryContext(DbContextOptions options) : PoolableDbContext(opt { public DbSet LTreeEntities { get; set; } - public static void Seed(LTreeQueryContext context) + public static async Task SeedAsync(LTreeQueryContext context) { var ltreeEntities = new LTreeEntity[] { @@ -527,7 +527,7 @@ public static void Seed(LTreeQueryContext context) } context.LTreeEntities.AddRange(ltreeEntities); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -560,8 +560,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(LTreeQueryContext context) - => LTreeQueryContext.Seed(context); + protected override Task SeedAsync(LTreeQueryContext context) + => LTreeQueryContext.SeedAsync(context); } #endregion diff --git a/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs index 13a4dcb34..3e0201d9a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LegacyTimestampQueryTest.cs @@ -111,10 +111,13 @@ public LegacyTimestampQueryFixture() NpgsqlTypeMappingSource.LegacyTimestampBehavior = true; } - public override void Dispose() - => NpgsqlTypeMappingSource.LegacyTimestampBehavior = false; + public override Task DisposeAsync() + { + NpgsqlTypeMappingSource.LegacyTimestampBehavior = false; + return Task.CompletedTask; + } - protected override void Seed(TimestampQueryContext context) + protected override async Task SeedAsync(TimestampQueryContext context) { using var ctx = CreateContext(); @@ -136,7 +139,8 @@ protected override void Seed(TimestampQueryContext context) TimestampDateTime = DateTime.SpecifyKind(utcDateTime2.ToLocalTime(), DateTimeKind.Unspecified), TimestampDateTimeOffset = new DateTimeOffset(utcDateTime2) }); - ctx.SaveChanges(); + + await ctx.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/MathQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/MathQueryTest.cs deleted file mode 100644 index c45f7e47b..000000000 --- a/test/EFCore.PG.FunctionalTests/Query/MathQueryTest.cs +++ /dev/null @@ -1,367 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; - -/// -/// Provides unit tests for PostgreSQL-specific math functions. -/// -/// -/// See: -/// - https://www.postgresql.org/docs/current/static/functions-math.html -/// - https://www.postgresql.org/docs/current/static/functions-conditional.html#FUNCTIONS-GREATEST-LEAST -/// -public class MathQueryTest : IClassFixture -{ - private MathQueryNpgsqlFixture Fixture { get; } - - // ReSharper disable once UnusedParameter.Local - public MathQueryTest(MathQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) - { - Fixture = fixture; - Fixture.TestSqlLoggerFactory.Clear(); - Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - #region GREATEST - - //[Fact] - public void Max_ushort_ushort() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.UShort, x.UShort)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"UShort\", x.\"UShort\")"); - } - - //[Fact] - public void Max_uint_uint() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.UInt, x.UInt)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"UInt\", x.\"UInt\")"); - } - - //[Fact] - public void Max_ulong_ulong() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.ULong, x.ULong)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"ULong\", x.\"ULong\")"); - } - - [Fact] - public void Max_short_short() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Short, x.Short)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Short\", x.\"Short\")"); - } - - [Fact] - public void Max_int_int() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Int, x.Int)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Int\", x.\"Int\")"); - } - - [Fact] - public void Max_long_long() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Long, x.Long)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Long\", x.\"Long\")"); - } - - [Fact] - public void Max_float_float() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Float, x.Float)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Float\", x.\"Float\")"); - } - - [Fact] - public void Max_double_double() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Double, x.Double)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Double\", x.\"Double\")"); - } - - [Fact] - public void Max_decimal_decimal() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Decimal, x.Decimal)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Decimal\", x.\"Decimal\")"); - } - - //[Fact] - public void Max_sbyte_sbyte() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.SByte, x.SByte)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"SByte\", x.\"SByte\")"); - } - - //[Fact] - public void Max_byte_byte() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Max(x.Byte, x.Byte)) - .ToArray(); - - AssertContainsSql("SELECT GREATEST(x.\"Byte\", x.\"Byte\")"); - } - - #endregion - - #region Least - - //[Fact] - public void Min_ushort_ushort() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.UShort, x.UShort)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"UShort\", x.\"UShort\")"); - } - - //[Fact] - public void Min_uint_uint() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.UInt, x.UInt)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"UInt\", x.\"UInt\")"); - } - - //[Fact] - public void Min_ulong_ulong() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.ULong, x.ULong)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"ULong\", x.\"ULong\")"); - } - - [Fact] - public void Min_short_short() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Short, x.Short)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Short\", x.\"Short\")"); - } - - [Fact] - public void Min_int_int() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Int, x.Int)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Int\", x.\"Int\")"); - } - - [Fact] - public void Min_long_long() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Long, x.Long)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Long\", x.\"Long\")"); - } - - [Fact] - public void Min_float_float() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Float, x.Float)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Float\", x.\"Float\")"); - } - - [Fact] - public void Min_double_double() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Double, x.Double)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Double\", x.\"Double\")"); - } - - [Fact] - public void Min_decimal_decimal() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Decimal, x.Decimal)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Decimal\", x.\"Decimal\")"); - } - - //[Fact] - public void Min_sbyte_sbyte() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.SByte, x.SByte)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"SByte\", x.\"SByte\")"); - } - - //[Fact] - public void Min_byte_byte() - { - using var ctx = CreateContext(); - var _ = - ctx.MathTestEntities - .Select(x => Math.Min(x.Byte, x.Byte)) - .ToArray(); - - AssertContainsSql("SELECT LEAST(x.\"Byte\", x.\"Byte\")"); - } - - #endregion - - #region Fixtures - - /// - /// Represents a fixture suitable for testing GREATEST(...) and LEAST(...)/ - /// - public class MathQueryNpgsqlFixture : SharedStoreFixtureBase - { - protected override string StoreName - => "MathQueryTest"; - - protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; - - public TestSqlLoggerFactory TestSqlLoggerFactory - => (TestSqlLoggerFactory)ListLoggerFactory; - } - - /// - /// Represents an entity suitable for testing GREATEST(...) and LEAST(...) operators. - /// - public class MathTestEntity - { - public int Id { get; set; } - public ushort UShort { get; set; } - public uint UInt { get; set; } - public ulong ULong { get; set; } - public short Short { get; set; } - public int Int { get; set; } - public long Long { get; set; } - public float Float { get; set; } - public double Double { get; set; } - public decimal Decimal { get; set; } - public byte Byte { get; set; } - public sbyte SByte { get; set; } - } - - /// - /// Represents a database suitable for testing GREATEST(...) and LEAST(...). - /// - public class MathContext : PoolableDbContext - { - /// - /// Represents a set of entities with numeric properties. - /// - public DbSet MathTestEntities { get; set; } - - /// - public MathContext(DbContextOptions options) - : base(options) - { - } - } - - #endregion - - #region Helpers - - protected MathContext CreateContext() - => Fixture.CreateContext(); - - /// - /// Asserts that the SQL fragment appears in the logs. - /// - /// The SQL statement or fragment to search for in the logs. - public void AssertContainsSql(string sql) - => Assert.Contains( - sql.Replace(Environment.NewLine, " ").Remove('\r').Remove('\n'), - Fixture.TestSqlLoggerFactory.Sql.Replace(Environment.NewLine, " ").Remove('\r').Remove('\n')); - - #endregion -} diff --git a/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs index d62f2518c..5f896f298 100644 --- a/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/MultirangeQueryNpgsqlTest.cs @@ -730,8 +730,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(MultirangeContext context) - => MultirangeContext.Seed(context); + protected override Task SeedAsync(MultirangeContext context) + => MultirangeContext.SeedAsync(context); } public class MultirangeTestEntity @@ -753,7 +753,7 @@ public class MultirangeContext(DbContextOptions options) : PoolableDbContext(opt { public DbSet TestEntities { get; set; } - public static void Seed(MultirangeContext context) + public static async Task SeedAsync(MultirangeContext context) { context.TestEntities.AddRange( new MultirangeTestEntity @@ -789,7 +789,7 @@ public static void Seed(MultirangeContext context) ] }); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs index 0af672aef..3301d9e0b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs @@ -1458,8 +1458,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(NetContext context) - => NetContext.Seed(context); + protected override Task SeedAsync(NetContext context) + => NetContext.SeedAsync(context); } /// @@ -1537,7 +1537,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } - public static void Seed(NetContext context) + public static async Task SeedAsync(NetContext context) { for (var i = 1; i <= 9; i++) { @@ -1557,7 +1557,7 @@ public static void Seed(NetContext context) }); } - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs index c6fa0961b..1ed6bed17 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NodaTimeQueryNpgsqlTest.cs @@ -1869,10 +1869,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.HasPostgresExtension("btree_gist"); } - public static void Seed(NodaTimeContext context) + public static async Task SeedAsync(NodaTimeContext context) { context.AddRange(NodaTimeData.CreateNodaTimeTypes()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -1928,8 +1928,8 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build return optionsBuilder; } - protected override void Seed(NodaTimeContext context) - => NodaTimeContext.Seed(context); + protected override Task SeedAsync(NodaTimeContext context) + => NodaTimeContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs index 90ef8b15c..fa4e4ab1b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs @@ -57,11 +57,11 @@ public override async Task Multidimensional_array_is_not_supported() // supported). var contextFactory = await InitializeAsync( mb => mb.Entity().Property("MultidimensionalArray"), - seed: context => + seed: async context => { var entry = context.Add(new TestEntity()); entry.Property("MultidimensionalArray").CurrentValue = new[,] { { 1, 2 }, { 3, 4 } }; - context.SaveChanges(); + await context.SaveChangesAsync(); }); await using var context = contextFactory.CreateContext(); @@ -77,9 +77,22 @@ await Assert.ThrowsAsync( public override async Task Column_collection_inside_json_owned_entity() { - var exception = await Assert.ThrowsAsync(() => base.Column_collection_inside_json_owned_entity()); - - Assert.Equal(exception.Message, NpgsqlStrings.Ef7JsonMappingNotSupported); + await base.Column_collection_inside_json_owned_entity(); + + AssertSql( + """ +SELECT t."Id", t."Owned" +FROM "TestOwner" AS t +WHERE cardinality((ARRAY(SELECT CAST(element AS text) FROM jsonb_array_elements_text(t."Owned" -> 'Strings') WITH ORDINALITY AS t(element) ORDER BY ordinality))) = 2 +LIMIT 2 +""", + // + """ +SELECT t."Id", t."Owned" +FROM "TestOwner" AS t +WHERE ((ARRAY(SELECT CAST(element AS text) FROM jsonb_array_elements_text(t."Owned" -> 'Strings') WITH ORDINALITY AS t(element) ORDER BY ordinality)))[2] = 'bar' +LIMIT 2 +"""); } protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs index 4b9f7a5da..0b13fc070 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs @@ -234,10 +234,10 @@ public void ToDate() var count = context.Orders.Count(c => EF.Functions.ToDate(c.OrderDate.ToString(), "YYYY-MM-DD") < new DateOnly(2000, 01, 01)); Assert.Equal(830, count); AssertSql( -""" + """ SELECT count(*)::int FROM "Orders" AS o -WHERE to_date(o."OrderDate"::text, 'YYYY-MM-DD') < DATE '2000-01-01' +WHERE to_date(COALESCE(o."OrderDate"::text, ''), 'YYYY-MM-DD') < DATE '2000-01-01' """); } @@ -251,7 +251,7 @@ public void ToTimestamp() """ SELECT count(*)::int FROM "Orders" AS o -WHERE to_timestamp(o."OrderDate"::text, 'YYYY-MM-DD') < TIMESTAMPTZ '2000-01-01T00:00:00Z' +WHERE to_timestamp(COALESCE(o."OrderDate"::text, ''), 'YYYY-MM-DD') < TIMESTAMPTZ '2000-01-01T00:00:00Z' """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs index 2950d6723..48f0fa784 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs @@ -45,28 +45,21 @@ public override Task Where_mathf_round2(bool async) public override Task Convert_ToString(bool async) => AssertTranslationFailed(() => base.Convert_ToString(async)); -// [ConditionalTheory] -// [MemberData(nameof(IsAsyncData))] -// public virtual async Task String_Join_non_aggregate(bool async) -// { -// var param = "param"; -// string nullParam = null; -// -// await AssertQuery( -// async, -// ss => ss.Set().Where( -// c => string.Join("|", c.CustomerID, c.CompanyName, param, nullParam, "constant", null) -// == "ALFKI|Alfreds Futterkiste|param||constant|")); -// -// AssertSql( -// """ -// @__param_0='param' -// -// 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 concat_ws('|', c."CustomerID", c."CompanyName", COALESCE(@__param_0, ''), COALESCE(NULL, ''), 'constant', '') = 'ALFKI|Alfreds Futterkiste|param||constant|' -// """); -// } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public override async Task String_Join_non_aggregate(bool async) + { + await base.String_Join_non_aggregate(async); + + AssertSql( + """ +@__foo_0='foo' + +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 concat_ws('|', c."CompanyName", @__foo_0, '', 'bar') = 'Around the Horn|foo||bar' +"""); + } #region Substring diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs index 1f497fa40..d413e5c8c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindGroupByQueryNpgsqlTest.cs @@ -2670,15 +2670,18 @@ public override async Task GroupBy_Count_in_projection(bool async) SELECT o."OrderID", o."OrderDate", EXISTS ( SELECT 1 FROM "Order Details" AS o0 - WHERE o."OrderID" = o0."OrderID" AND o0."ProductID" < 25) AS "HasOrderDetails", ( - SELECT count(*)::int - FROM ( - SELECT 1 - FROM "Order Details" AS o1 - INNER JOIN "Products" AS p ON o1."ProductID" = p."ProductID" - WHERE o."OrderID" = o1."OrderID" AND o1."ProductID" < 25 - GROUP BY p."ProductName" - ) AS s) > 1 AS "HasMultipleProducts" + WHERE o."OrderID" = o0."OrderID" AND o0."ProductID" < 25) AS "HasOrderDetails", CASE + WHEN ( + SELECT count(*)::int + FROM ( + SELECT 1 + FROM "Order Details" AS o1 + INNER JOIN "Products" AS p ON o1."ProductID" = p."ProductID" + WHERE o."OrderID" = o1."OrderID" AND o1."ProductID" < 25 + GROUP BY p."ProductName" + ) AS s) > 1 THEN TRUE + ELSE FALSE +END AS "HasMultipleProducts" FROM "Orders" AS o WHERE o."OrderDate" IS NOT NULL """); diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs index 89f18e27e..d6db7b54d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs @@ -24,7 +24,7 @@ public override async Task Query_expression_with_to_string_and_contains(bool asy """ SELECT o."CustomerID" FROM "Orders" AS o -WHERE o."OrderDate" IS NOT NULL AND o."EmployeeID"::text LIKE '%7%' +WHERE o."OrderDate" IS NOT NULL AND COALESCE(o."EmployeeID"::text, '') LIKE '%7%' """); } @@ -199,6 +199,18 @@ ORDER BY o0."CustomerID" NULLS FIRST """); } + public override async Task Where_bitwise_binary_xor(bool async) + { + await base.Where_bitwise_binary_xor(async); + + AssertSql( + """ +SELECT o."OrderID", o."CustomerID", o."EmployeeID", o."OrderDate" +FROM "Orders" AS o +WHERE (o."OrderID" # 1) = 10249 +"""); + } + // TODO: Array tests can probably move to the dedicated ArrayQueryTest suite #region Array contains diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs index 18109a3c8..552db3a89 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindWhereQueryNpgsqlTest.cs @@ -29,7 +29,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 date_trunc('day', now()::timestamp) = date_trunc('day', now()::timestamp) """); } @@ -172,15 +171,19 @@ public override Task Where_datetimeoffset_utcnow(bool async) public override async Task Where_bitwise_xor(bool async) { - // Cannot eval 'where (([c].CustomerID == \"ALFKI\") ^ True)'. Issue #16645. - await AssertTranslationFailed(() => base.Where_bitwise_xor(async)); + await base.Where_bitwise_xor(async); - AssertSql(); + AssertSql( + """ +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" +FROM "Customers" AS c +WHERE (c."CustomerID" = 'ALFKI') <> TRUE +"""); } public override async Task Where_compare_constructed_equal(bool async) { - // Anonymous type to constant comparison. Issue #14672. + // Anonymous type to constant comparison. Issue #14672. await AssertTranslationFailed(() => base.Where_compare_constructed_equal(async)); AssertSql(); @@ -481,10 +484,29 @@ public async Task Row_value_not_equals() { await using var ctx = CreateContext(); + // Everything is non-nullable, so we use the nicer row value comparison syntax + _ = await ctx.Customers + .Where(c => !ValueTuple.Create(c.CustomerID, c.CustomerID).Equals(ValueTuple.Create("OCEAN", "OCEAN"))) + .CountAsync(); + + AssertSql( + """ +SELECT count(*)::int +FROM "Customers" AS c +WHERE (c."CustomerID", c."CustomerID") <> ('OCEAN', 'OCEAN') +"""); + } + + [ConditionalFact] + public async Task Row_value_not_equals_with_nullable() + { + await using var ctx = CreateContext(); + _ = await ctx.Customers .Where(c => !ValueTuple.Create(c.City, c.CustomerID).Equals(ValueTuple.Create("Buenos Aires", "OCEAN"))) .CountAsync(); + // City is nullable, so we must extract that comparison out of the row value AssertSql( """ SELECT count(*)::int diff --git a/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlFixture.cs deleted file mode 100644 index 497d71540..000000000 --- a/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlFixture.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; - -public class NullSemanticsQueryNpgsqlFixture : NullSemanticsQueryFixtureBase -{ - protected override ITestStoreFactory TestStoreFactory - => NpgsqlTestStoreFactory.Instance; -} diff --git a/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlTest.cs index a5bb64c03..eb0ba354a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NullSemanticsQueryNpgsqlTest.cs @@ -1,10 +1,12 @@ using Microsoft.EntityFrameworkCore.TestModels.NullSemanticsModel; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; // ReSharper disable once UnusedMember.Global -public class NullSemanticsQueryNpgsqlTest : NullSemanticsQueryTestBase +public class NullSemanticsQueryNpgsqlTest : NullSemanticsQueryTestBase { public NullSemanticsQueryNpgsqlTest(NullSemanticsQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) @@ -180,4 +182,26 @@ protected override NullSemanticsContext CreateContext(bool useRelationalNulls = return context; } + + public class NullSemanticsQueryNpgsqlFixture : NullSemanticsQueryFixtureBase + { + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + // The base implementations maps this function to bool constants with BoolTypeMapping.Default which doesn't work with PG; + // override to use NpgsqlBoolTypeMapping instead. + modelBuilder.HasDbFunction( + typeof(NullSemanticsQueryFixtureBase).GetMethod(nameof(BoolSwitch))!, + b => b.HasTranslation(args => new CaseExpression( + operand: args[0], + [ + new CaseWhenClause(new SqlConstantExpression(true, typeMapping: NpgsqlBoolTypeMapping.Default), args[1]), + new CaseWhenClause(new SqlConstantExpression(false, typeMapping: NpgsqlBoolTypeMapping.Default), args[2]) + ]))); + } + } } diff --git a/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs index 3bbbe57d7..97bdda412 100644 --- a/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/OperatorsQueryNpgsqlTest.cs @@ -15,21 +15,46 @@ public override async Task Bitwise_and_on_expression_with_like_and_null_check_be { await base.Bitwise_and_on_expression_with_like_and_null_check_being_compared_to_false(); - AssertSql(""); + AssertSql( + """ +SELECT o."Value" AS "Value1", o0."Value" AS "Value2", o1."Value" AS "Value3" +FROM "OperatorEntityString" AS o +CROSS JOIN "OperatorEntityString" AS o0 +CROSS JOIN "OperatorEntityBool" AS o1 +WHERE ((o0."Value" LIKE 'B' AND o0."Value" IS NOT NULL) OR o1."Value") AND o."Value" IS NOT NULL +ORDER BY o."Id" NULLS FIRST, o0."Id" NULLS FIRST, o1."Id" NULLS FIRST +"""); } public override async Task Complex_predicate_with_bitwise_and_modulo_and_negation() { await base.Complex_predicate_with_bitwise_and_modulo_and_negation(); - AssertSql(""); + AssertSql( + """ +SELECT o."Value" AS "Value0", o0."Value" AS "Value1", o1."Value" AS "Value2", o2."Value" AS "Value3" +FROM "OperatorEntityLong" AS o +CROSS JOIN "OperatorEntityLong" AS o0 +CROSS JOIN "OperatorEntityLong" AS o1 +CROSS JOIN "OperatorEntityLong" AS o2 +WHERE (o0."Value" % 2) / o."Value" & ((o2."Value" | o1."Value") - o."Value") - o1."Value" * o1."Value" >= ((o0."Value" / (~o2."Value")) % 2) % ((~o."Value") + 1) +ORDER BY o."Id" NULLS FIRST, o0."Id" NULLS FIRST, o1."Id" NULLS FIRST, o2."Id" NULLS FIRST +"""); } public override async Task Complex_predicate_with_bitwise_and_arithmetic_operations() { await base.Complex_predicate_with_bitwise_and_arithmetic_operations(); - AssertSql(""); + AssertSql( + """ +SELECT o."Value" AS "Value0", o0."Value" AS "Value1", o1."Value" AS "Value2" +FROM "OperatorEntityInt" AS o +CROSS JOIN "OperatorEntityInt" AS o0 +CROSS JOIN "OperatorEntityBool" AS o1 +WHERE (o0."Value" & o."Value" + o."Value" & o."Value") / 1 > o0."Value" & 10 AND o1."Value" +ORDER BY o."Id" NULLS FIRST, o0."Id" NULLS FIRST, o1."Id" NULLS FIRST +"""); } public override async Task Or_on_two_nested_binaries_and_another_simple_comparison() @@ -44,7 +69,7 @@ CROSS JOIN "OperatorEntityString" AS o0 CROSS JOIN "OperatorEntityString" AS o1 CROSS JOIN "OperatorEntityString" AS o2 CROSS JOIN "OperatorEntityInt" AS o3 -WHERE ((o."Value" = 'A' AND o."Value" IS NOT NULL AND o0."Value" = 'A' AND o0."Value" IS NOT NULL) OR (o1."Value" = 'B' AND o1."Value" IS NOT NULL AND o2."Value" = 'B' AND o2."Value" IS NOT NULL)) AND o3."Value" = 2 +WHERE ((o."Value" = 'A' AND o0."Value" = 'A') OR (o1."Value" = 'B' AND o2."Value" = 'B')) AND o3."Value" = 2 ORDER BY o."Id" NULLS FIRST, o0."Id" NULLS FIRST, o1."Id" NULLS FIRST, o2."Id" NULLS FIRST, o3."Id" NULLS FIRST """); } @@ -129,12 +154,12 @@ LIMIT 2 public virtual async Task AtTimeZone_and_addition() { var contextFactory = await InitializeAsync( - seed: context => + seed: async context => { context.Set().AddRange( new OperatorEntityDateTime { Id = 1, Value = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc) }, new OperatorEntityDateTime { Id = 2, Value = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc) }); - context.SaveChanges(); + await context.SaveChangesAsync(); }, onModelCreating: modelBuilder => modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever()); @@ -160,7 +185,7 @@ public class OperatorEntityDateTime : OperatorEntityBase public DateTime Value { get; set; } } - protected override void Seed(OperatorsContext ctx) + protected override async Task Seed(OperatorsContext ctx) { ctx.Set().AddRange(ExpectedData.OperatorEntitiesString); ctx.Set().AddRange(ExpectedData.OperatorEntitiesInt); @@ -170,6 +195,6 @@ protected override void Seed(OperatorsContext ctx) ctx.Set().AddRange(ExpectedData.OperatorEntitiesNullableBool); // ctx.Set().AddRange(ExpectedData.OperatorEntitiesDateTimeOffset); - ctx.SaveChanges(); + await ctx.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 7d34b3711..9b8cd9926 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -197,6 +197,20 @@ public override async Task Inline_collection_Contains_with_mixed_value_types(boo """ @__i_0='11' +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Int" IN (999, @__i_0, p."Id", p."Id" + p."Int") +"""); + } + + public override async Task Inline_collection_List_Contains_with_mixed_value_types(bool async) + { + await base.Inline_collection_List_Contains_with_mixed_value_types(async); + + AssertSql( + """ +@__i_0='11' + SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE p."Int" IN (999, @__i_0, p."Id", p."Id" + p."Int") @@ -239,6 +253,18 @@ WHERE LEAST(30, p."Int") = 30 """); } + public override async Task Inline_collection_List_Min_with_two_values(bool async) + { + await base.Inline_collection_List_Min_with_two_values(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."Int") = 30 +"""); + } + public override async Task Inline_collection_Max_with_two_values(bool async) { await base.Inline_collection_Max_with_two_values(async); @@ -251,6 +277,18 @@ WHERE GREATEST(30, p."Int") = 30 """); } + public override async Task Inline_collection_List_Max_with_two_values(bool async) + { + await base.Inline_collection_List_Max_with_two_values(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."Int") = 30 +"""); + } + public override async Task Inline_collection_Min_with_three_values(bool async) { await base.Inline_collection_Min_with_three_values(async); @@ -259,6 +297,20 @@ public override async Task Inline_collection_Min_with_three_values(bool async) """ @__i_0='25' +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."Int", @__i_0) = 25 +"""); + } + + public override async Task Inline_collection_List_Min_with_three_values(bool async) + { + await base.Inline_collection_List_Min_with_three_values(async); + + AssertSql( + """ +@__i_0='25' + SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE LEAST(30, p."Int", @__i_0) = 25 @@ -279,6 +331,72 @@ WHERE GREATEST(30, p."Int", @__i_0) = 35 """); } + public override async Task Inline_collection_List_Max_with_three_values(bool async) + { + await base.Inline_collection_List_Max_with_three_values(async); + + AssertSql( + """ +@__i_0='35' + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."Int", @__i_0) = 35 +"""); + } + + public override async Task Inline_collection_of_nullable_value_type_Min(bool async) + { + await base.Inline_collection_of_nullable_value_type_Min(async); + + AssertSql( + """ +@__i_0='25' (Nullable = true) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."Int", @__i_0) = 25 +"""); + } + + public override async Task Inline_collection_of_nullable_value_type_Max(bool async) + { + await base.Inline_collection_of_nullable_value_type_Max(async); + + AssertSql( + """ +@__i_0='35' (Nullable = true) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."Int", @__i_0) = 35 +"""); + } + + public override async Task Inline_collection_of_nullable_value_type_with_null_Min(bool async) + { + await base.Inline_collection_of_nullable_value_type_with_null_Min(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE LEAST(30, p."NullableInt", NULL) = 30 +"""); + } + + public override async Task Inline_collection_of_nullable_value_type_with_null_Max(bool async) + { + await base.Inline_collection_of_nullable_value_type_with_null_Max(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE GREATEST(30, p."NullableInt", NULL) = 30 +"""); + } + public override async Task Parameter_collection_Count(bool async) { await base.Parameter_collection_Count(async); @@ -312,6 +430,28 @@ public override async Task Parameter_collection_of_ints_Contains_int(bool async) """ @__ints_0={ '10', '999' } (DbType = Object) +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."Int" = ANY (@__ints_0) AND p."Int" = ANY (@__ints_0) IS NOT NULL) +"""); + } + + public override async Task Parameter_collection_HashSet_of_ints_Contains_int(bool async) + { + await base.Parameter_collection_HashSet_of_ints_Contains_int(async); + + AssertSql( + """ +@__ints_0={ '10', '999' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Int" = ANY (@__ints_0) +""", + // + """ +@__ints_0={ '10', '999' } (DbType = Object) + SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE NOT (p."Int" = ANY (@__ints_0) AND p."Int" = ANY (@__ints_0) IS NOT NULL) @@ -648,6 +788,36 @@ WHERE cardinality(p."Ints") = 2 """); } + public override async Task Column_collection_Count_with_predicate(bool async) + { + await base.Column_collection_Count_with_predicate(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1) = 2 +"""); + } + + public override async Task Column_collection_Where_Count(bool async) + { + await base.Column_collection_Where_Count(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1) = 2 +"""); + } + public override async Task Column_collection_index_int(bool async) { await base.Column_collection_index_int(async); @@ -736,6 +906,38 @@ ORDER BY v._ord NULLS FIRST """); } + public override async Task Inline_collection_value_index_Column(bool async) + { + await base.Inline_collection_value_index_Column(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT v."Value" + FROM (VALUES (0, 1::int), (1, p."Int"), (2, 3)) AS v(_ord, "Value") + ORDER BY v._ord NULLS FIRST + LIMIT 1 OFFSET p."Int") = 1 +"""); + } + + public override async Task Inline_collection_List_value_index_Column(bool async) + { + await base.Inline_collection_List_value_index_Column(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT v."Value" + FROM (VALUES (0, 1::int), (1, p."Int"), (2, 3)) AS v(_ord, "Value") + ORDER BY v._ord NULLS FIRST + LIMIT 1 OFFSET p."Int") = 1 +"""); + } + public override async Task Parameter_collection_index_Column_equal_Column(bool async) { await base.Parameter_collection_index_Column_equal_Column(async); @@ -776,6 +978,66 @@ public override async Task Column_collection_ElementAt(bool async) """); } + public override async Task Column_collection_First(bool async) + { + await base.Column_collection_First(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + LIMIT 1) = 1 +"""); + } + + public override async Task Column_collection_FirstOrDefault(bool async) + { + await base.Column_collection_FirstOrDefault(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE COALESCE(( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + LIMIT 1), 0) = 1 +"""); + } + + public override async Task Column_collection_Single(bool async) + { + await base.Column_collection_Single(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + LIMIT 1) = 1 +"""); + } + + public override async Task Column_collection_SingleOrDefault(bool async) + { + await base.Column_collection_SingleOrDefault(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE COALESCE(( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + LIMIT 1), 0) = 1 +"""); + } + public override async Task Column_collection_Skip(bool async) { await base.Column_collection_Skip(async); @@ -809,6 +1071,79 @@ public override async Task Column_collection_Skip_Take(bool async) SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" FROM "PrimitiveCollectionsEntity" AS p WHERE 11 = ANY (p."Ints"[2:3]) +"""); + } + + public override async Task Column_collection_Where_Skip(bool async) + { + await base.Column_collection_Where_Skip(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM ( + SELECT 1 + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 + OFFSET 1 + ) AS i0) = 3 +"""); + } + + public override async Task Column_collection_Where_Take(bool async) + { + await base.Column_collection_Where_Take(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM ( + SELECT 1 + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 + LIMIT 2 + ) AS i0) = 2 +"""); + } + + public override async Task Column_collection_Where_Skip_Take(bool async) + { + await base.Column_collection_Where_Skip_Take(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM ( + SELECT 1 + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 + LIMIT 2 OFFSET 1 + ) AS i0) = 1 +"""); + } + + public override async Task Column_collection_Contains_over_subquery(bool async) + { + await base.Column_collection_Contains_over_subquery(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE 11 IN ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 +) """); } @@ -828,6 +1163,22 @@ ORDER BY i.value DESC NULLS LAST """); } + public override async Task Column_collection_Where_ElementAt(bool async) + { + await base.Column_collection_Where_ElementAt(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 + LIMIT 1 OFFSET 0) = 11 +"""); + } + public override async Task Column_collection_Any(bool async) { await base.Column_collection_Any(async); @@ -869,6 +1220,34 @@ JOIN LATERAL unnest(p."Ints") AS i(value) ON TRUE """); } + public override async Task Column_collection_SelectMany_with_filter(bool async) + { + await base.Column_collection_SelectMany_with_filter(async); + + AssertSql( + """ +SELECT i0.value +FROM "PrimitiveCollectionsEntity" AS p +JOIN LATERAL ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 1 +) AS i0 ON TRUE +"""); + } + + public override async Task Column_collection_SelectMany_with_Select_to_anonymous_type(bool async) + { + await base.Column_collection_SelectMany_with_Select_to_anonymous_type(async); + + AssertSql( + """ +SELECT i.value AS "Original", i.value + 1 AS "Incremented" +FROM "PrimitiveCollectionsEntity" AS p +JOIN LATERAL unnest(p."Ints") AS i(value) ON TRUE +"""); + } + public override async Task Column_collection_projection_from_top_level(bool async) { await base.Column_collection_projection_from_top_level(async); @@ -1009,6 +1388,26 @@ FROM unnest(p."Ints") AS i(value) """); } + public override async Task Column_collection_Where_Union(bool async) + { + await base.Column_collection_Where_Union(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM ( + SELECT i.value + FROM unnest(p."Ints") AS i(value) + WHERE i.value > 100 + UNION + VALUES (50::int) + ) AS u) = 2 +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Parameter_collection_Concat_Column_collection_Concat_parameter(bool async) @@ -1084,6 +1483,13 @@ await AssertQuery( """); } + public override async Task Column_collection_Where_equality_inline_collection(bool async) + { + await base.Column_collection_Where_equality_inline_collection(async); + + AssertSql(); + } + public override async Task Parameter_collection_in_subquery_Union_column_collection_as_compiled_query(bool async) { await base.Parameter_collection_in_subquery_Union_column_collection_as_compiled_query(async); @@ -1252,7 +1658,7 @@ public override async Task Project_collection_of_datetimes_filtered(bool async) LEFT JOIN LATERAL ( SELECT d.value, d.ordinality FROM unnest(p."DateTimes") WITH ORDINALITY AS d(value) - WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 OR d.value AT TIME ZONE 'UTC' IS NULL + WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 ) AS d0 ON TRUE ORDER BY p."Id" NULLS FIRST, d0.ordinality NULLS FIRST """); @@ -1396,7 +1802,7 @@ LEFT JOIN LATERAL unnest(p."Ints") WITH ORDINALITY AS i0(value) ON TRUE LEFT JOIN LATERAL ( SELECT d.value, d.ordinality FROM unnest(p."DateTimes") WITH ORDINALITY AS d(value) - WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 OR d.value AT TIME ZONE 'UTC' IS NULL + WHERE date_part('day', d.value AT TIME ZONE 'UTC')::int <> 1 ) AS d1 ON TRUE LEFT JOIN LATERAL ( SELECT d0.value, d0.ordinality @@ -1420,6 +1826,43 @@ ORDER BY p."Id" NULLS FIRST """); } + public override async Task Project_inline_collection(bool async) + { + await base.Project_inline_collection(async); + + AssertSql( + """ +SELECT ARRAY[p."String",'foo']::text[] +FROM "PrimitiveCollectionsEntity" AS p +"""); + } + + public override async Task Project_inline_collection_with_Union(bool async) + { + await base.Project_inline_collection_with_Union(async); + + AssertSql( + """ +SELECT p."Id", u."Value" +FROM "PrimitiveCollectionsEntity" AS p +LEFT JOIN LATERAL ( + SELECT v."Value" + FROM (VALUES (p."String")) AS v("Value") + UNION + SELECT p0."String" AS "Value" + FROM "PrimitiveCollectionsEntity" AS p0 +) AS u ON TRUE +ORDER BY p."Id" NULLS FIRST +"""); + } + + public override async Task Project_inline_collection_with_Concat(bool async) + { + await base.Project_inline_collection_with_Concat(async); + + AssertSql(); + } + public override async Task Nested_contains_with_Lists_and_no_inferred_type_mapping(bool async) { await base.Nested_contains_with_Lists_and_no_inferred_type_mapping(async); diff --git a/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs b/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs index 8b626b5c9..26bb1e04d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs @@ -20,16 +20,16 @@ public QueryBugsTest(NpgsqlFixture fixture, ITestOutputHelper testOutputHelper) #region Bug920 [Fact] - public void Bug920() + public async Task Bug920() { - using var _ = CreateDatabase920(); + using var _ = await CreateDatabase920Async(); using var context = new Bug920Context(_options); context.Entities.Add(new Bug920Entity { Enum = Bug920Enum.Two }); context.SaveChanges(); } - private NpgsqlTestStore CreateDatabase920() - => CreateTestStore(() => new Bug920Context(_options), _ => ClearLog()); + private Task CreateDatabase920Async() + => CreateTestStoreAsync(() => new Bug920Context(_options), _ => ClearLog()); public enum Bug920Enum { One, Two } @@ -50,17 +50,17 @@ private class Bug920Context(DbContextOptions options) : DbContext(options) private DbContextOptions _options; - private NpgsqlTestStore CreateTestStore( + private async Task CreateTestStoreAsync( Func contextCreator, Action contextInitializer) where TContext : DbContext, IDisposable { - var testStore = NpgsqlTestStore.CreateInitialized("QueryBugsTest"); + var testStore = await NpgsqlTestStore.CreateInitializedAsync("QueryBugsTest"); _options = Fixture.CreateOptions(testStore); - using var context = contextCreator(); - context.Database.EnsureCreatedResiliently(); + await using var context = contextCreator(); + await context.Database.EnsureCreatedResilientlyAsync(); contextInitializer?.Invoke(context); return testStore; } diff --git a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs index 7021a6497..29772f60e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs @@ -626,8 +626,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(RangeContext context) - => RangeContext.Seed(context); + protected override Task SeedAsync(RangeContext context) + => RangeContext.SeedAsync(context); public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) { @@ -662,7 +662,7 @@ protected override void OnModelCreating(ModelBuilder builder) => builder.HasPostgresRange("doublerange", "double precision") .HasPostgresRange("test", "Schema_Range", "real"); - public static void Seed(RangeContext context) + public static async Task SeedAsync(RangeContext context) { context.RangeTestEntities.AddRange( new RangeTestEntity @@ -688,7 +688,7 @@ public static void Seed(RangeContext context) UserDefinedRangeWithSchema = new NpgsqlRange(5, 15) }); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/SharedTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SharedTypeQueryNpgsqlTest.cs index 6fa1f5c1b..91ef5653a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SharedTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SharedTypeQueryNpgsqlTest.cs @@ -9,9 +9,4 @@ protected override ITestStoreFactory TestStoreFactory public override Task Can_use_shared_type_entity_type_in_query_filter_with_from_sql(bool async) => Task.CompletedTask; // https://github.com/dotnet/efcore/issues/25661 - - [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/30367")] - public override void Ad_hoc_query_for_shared_type_entity_type_works() - { - } } diff --git a/test/EFCore.PG.FunctionalTests/Query/SqlQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SqlQueryNpgsqlTest.cs index 20f43c4e8..d666a26f1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SqlQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SqlQueryNpgsqlTest.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using Xunit.Sdk; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; @@ -40,6 +41,12 @@ public override async Task SqlQueryRaw_queryable_simple_columns_out_of_order_and """); } + // The test attempts to project out a column with the wrong case; this works on other databases, and fails when EF tries to materialize. + // But in PG this fails at the database since PG is case-sensitive and the column does not exist. + public override Task SqlQueryRaw_queryable_simple_different_cased_columns_and_not_enough_columns_throws(bool async) + => Assert.ThrowsAsync( + () => base.SqlQueryRaw_queryable_simple_different_cased_columns_and_not_enough_columns_throws(async)); + public override async Task SqlQueryRaw_queryable_composed(bool async) { await base.SqlQueryRaw_queryable_composed(async); diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs index b0703bb02..a6510bd5e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs @@ -46,12 +46,12 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(GearsOfWarContext context) + protected override Task SeedAsync(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); + => SeedForNpgsqlAsync(context); - public static void SeedForNpgsql(GearsOfWarContext context) + public static async Task SeedForNpgsqlAsync(GearsOfWarContext context) { var squads = GearsOfWarData.CreateSquads(); var missions = GearsOfWarData.CreateMissions(); @@ -85,10 +85,10 @@ public static void SeedForNpgsql(GearsOfWarContext context) context.LocustLeaders.AddRange(locustLeaders); context.Factions.AddRange(factions); context.LocustHighCommands.AddRange(locustHighCommands); - context.SaveChanges(); + await context.SaveChangesAsync(); GearsOfWarData.WireUp2(locustLeaders, factions); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs index 5f047dd73..5fae337e4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs @@ -48,7 +48,7 @@ await AssertQuery( @__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" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", 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) """); @@ -66,7 +66,7 @@ await AssertQuery( """ @__dateTimeOffset_Date_0='0002-03-01T00:00:00.0000000' -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", 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 """); @@ -82,7 +82,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01T00:00:00' """); diff --git a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs index a6dce923f..68ca376e4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs @@ -46,12 +46,12 @@ public override ISetSource GetExpectedData() return _expectedData; } - protected override void Seed(GearsOfWarContext context) + protected override Task SeedAsync(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); + => SeedForNpgsqlAsync(context); - public static void SeedForNpgsql(GearsOfWarContext context) + public static async Task SeedForNpgsqlAsync(GearsOfWarContext context) { var squads = GearsOfWarData.CreateSquads(); var missions = GearsOfWarData.CreateMissions(); @@ -85,10 +85,10 @@ public static void SeedForNpgsql(GearsOfWarContext context) context.LocustLeaders.AddRange(locustLeaders); context.Factions.AddRange(factions); context.LocustHighCommands.AddRange(locustHighCommands); - context.SaveChanges(); + await context.SaveChangesAsync(); GearsOfWarData.WireUp2(locustLeaders, factions); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs index 9247f53db..dafbe74e1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs @@ -52,7 +52,7 @@ await AssertQuery( @__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" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", 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) """); @@ -70,7 +70,7 @@ await AssertQuery( """ @__dateTimeOffset_Date_0='0002-03-01T00:00:00.0000000' -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", 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 """); @@ -86,7 +86,7 @@ await AssertQuery( AssertSql( """ -SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" +SELECT m."Id", m."CodeName", m."Date", m."Difficulty", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01T00:00:00' """); diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index 2feac4876..429c4cc9c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -829,10 +829,10 @@ public class TimestampQueryContext(DbContextOptions options) : PoolableDbContext { public DbSet Entities { get; set; } - public static void Seed(TimestampQueryContext context) + public static async Task SeedAsync (TimestampQueryContext context) { context.Entities.AddRange(TimestampData.CreateEntities()); - context.SaveChanges(); + await context.SaveChangesAsync(); } } @@ -876,8 +876,8 @@ public TestSqlLoggerFactory TestSqlLoggerFactory private TimestampData _expectedData; - protected override void Seed(TimestampQueryContext context) - => TimestampQueryContext.Seed(context); + protected override Task SeedAsync(TimestampQueryContext context) + => TimestampQueryContext.SeedAsync(context); public Func GetContextCreator() => CreateContext; diff --git a/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs index d42779741..c78819df2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs @@ -232,8 +232,8 @@ protected override ITestStoreFactory TestStoreFactory public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override void Seed(TrigramsContext context) - => TrigramsContext.Seed(context); + protected override Task SeedAsync(TrigramsContext context) + => TrigramsContext.SeedAsync(context); } /// @@ -282,7 +282,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } - public static void Seed(TrigramsContext context) + public static async Task SeedAsync(TrigramsContext context) { for (var i = 1; i <= 9; i++) { @@ -291,7 +291,7 @@ public static void Seed(TrigramsContext context) new TrigramsTestEntity { Id = i, Text = text }); } - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs index 02ed4cdf2..549194272 100644 --- a/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs +++ b/test/EFCore.PG.FunctionalTests/Query/UdfDbFunctionNpgsqlTests.cs @@ -671,11 +671,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .HasParameter("startDate").Metadata.TypeMapping = typeMappingSource.GetMapping("timestamp without time zone"); } - protected override void Seed(DbContext context) + protected override async Task SeedAsync(DbContext context) { - base.Seed(context); + await base.SeedAsync(context); - context.Database.ExecuteSqlRaw( + await context.Database.ExecuteSqlRawAsync( """ CREATE FUNCTION "CustomerOrderCount" ("customerId" INTEGER) RETURNS INTEGER AS $$ SELECT COUNT("Id")::INTEGER FROM "Orders" WHERE "CustomerId" = $1 $$ @@ -776,7 +776,7 @@ EXCEPTION WHEN OTHERS THEN $$ LANGUAGE PLPGSQL; """); - context.SaveChanges(); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs b/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs index 9b4d29b8a..21a0e97fe 100644 --- a/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs +++ b/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs @@ -2,7 +2,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; -public class SequenceEndToEndTest : IDisposable +public class SequenceEndToEndTest : IAsyncLifetime { [ConditionalFact] public void Can_use_sequence_end_to_end() @@ -389,8 +389,14 @@ private class Unicon public string Name { get; set; } } - protected NpgsqlTestStore TestStore { get; } = NpgsqlTestStore.CreateInitialized("SequenceEndToEndTest"); + protected NpgsqlTestStore TestStore { get; private set; } - public void Dispose() - => TestStore.Dispose(); + public async Task InitializeAsync() + => TestStore = await NpgsqlTestStore.CreateInitializedAsync("SequenceEndToEndTest"); + + public Task DisposeAsync() + { + TestStore.Dispose(); + return Task.CompletedTask; + } } diff --git a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs index 32e4e50e7..fa81d3a87 100644 --- a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs @@ -11,5 +11,6 @@ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransact // This test requires DbConnection to be used with the test store, but SpatialNpgsqlFixture must set useConnectionString to true // in order to properly set up the NetTopologySuite internally with the data source. public override void Mutation_of_tracked_values_does_not_mutate_values_in_store() - => Assert.Throws(() => base.Mutation_of_tracked_values_does_not_mutate_values_in_store()); + { + } } diff --git a/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs index 1133cb7f3..c290140b1 100644 --- a/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/StoreGeneratedFixupNpgsqlTest.cs @@ -6,9 +6,9 @@ public class StoreGeneratedFixupNpgsqlTest(StoreGeneratedFixupNpgsqlTest.StoreGe : StoreGeneratedFixupRelationalTestBase(fixture) { [Fact] - public void Temp_values_are_replaced_on_save() - => ExecuteWithStrategyInTransaction( - context => + public Task Temp_values_are_replaced_on_save() + => ExecuteWithStrategyInTransactionAsync( + async context => { var entry = context.Add(new TestTemp()); @@ -17,7 +17,7 @@ public void Temp_values_are_replaced_on_save() var tempValue = entry.Property(e => e.Id).CurrentValue; - context.SaveChanges(); + await context.SaveChangesAsync(); Assert.False(entry.Property(e => e.Id).IsTemporary); Assert.NotEqual(tempValue, entry.Property(e => e.Id).CurrentValue); diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs index 0c45ed887..389023fd6 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryContext.cs @@ -54,14 +54,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) e.HasIndex(ae => ae.NonNullableText); }); - public static void Seed(ArrayQueryContext context) + public static async Task SeedAsync(ArrayQueryContext context) { var arrayEntities = ArrayQueryData.CreateArrayEntities(); context.SomeEntities.AddRange(arrayEntities); - context.SomeEntityContainers.Add( - new ArrayContainerEntity { Id = 1, ArrayEntities = arrayEntities.ToList() } - ); - context.SaveChanges(); + context.SomeEntityContainers.Add(new ArrayContainerEntity { Id = 1, ArrayEntities = arrayEntities.ToList() }); + await context.SaveChangesAsync(); } } diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs index 7ed735a7f..8b4ba0381 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlTestStore.cs @@ -1,6 +1,5 @@ using System.Data; using System.Data.Common; -using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; @@ -20,13 +19,12 @@ public class NpgsqlTestStore : RelationalTestStore public static readonly string NorthwindConnectionString = CreateConnectionString(Northwind); - public static NpgsqlTestStore GetNorthwindStore() - => (NpgsqlTestStore)NpgsqlNorthwindTestStoreFactory.Instance - .GetOrCreate(NpgsqlNorthwindTestStoreFactory.Name).Initialize(null, (Func?)null); + public static async Task GetNorthwindStoreAsync() + => (NpgsqlTestStore)await NpgsqlNorthwindTestStoreFactory.Instance + .GetOrCreate(NpgsqlNorthwindTestStoreFactory.Name).InitializeAsync(null, (Func?)null); - // ReSharper disable once UnusedMember.Global - public static NpgsqlTestStore GetOrCreateInitialized(string name) - => new NpgsqlTestStore(name).InitializeNpgsql(null, (Func?)null, null); + public static Task GetOrCreateInitializedAsync(string name) + => new NpgsqlTestStore(name).InitializeNpgsqlAsync(null, (Func?)null, null); public static NpgsqlTestStore GetOrCreate( string name, @@ -39,9 +37,8 @@ public static NpgsqlTestStore GetOrCreate( public static NpgsqlTestStore Create(string name, string? connectionStringOptions = null) => new(name, connectionStringOptions: connectionStringOptions, shared: false); - public static NpgsqlTestStore CreateInitialized(string name) - => new NpgsqlTestStore(name, shared: false) - .InitializeNpgsql(null, (Func?)null, null); + public static Task CreateInitializedAsync(string name) + => new NpgsqlTestStore(name, shared: false).InitializeNpgsqlAsync(null, (Func?)null, null); public NpgsqlTestStore( string name, @@ -72,22 +69,22 @@ private static NpgsqlConnection CreateConnection(string name, string? connection => new(CreateConnectionString(name, connectionStringOptions)); // ReSharper disable once MemberCanBePrivate.Global - public NpgsqlTestStore InitializeNpgsql( + public async Task InitializeNpgsqlAsync( IServiceProvider? serviceProvider, Func? createContext, - Action? seed) - => (NpgsqlTestStore)Initialize(serviceProvider, createContext, seed); + Func? seed) + => (NpgsqlTestStore)await InitializeAsync(serviceProvider, createContext, seed); // ReSharper disable once UnusedMember.Global - public NpgsqlTestStore InitializeNpgsql( + public async Task InitializeNpgsqlAsync( IServiceProvider serviceProvider, Func createContext, - Action seed) - => InitializeNpgsql(serviceProvider, () => createContext(this), seed); + Func seed) + => await InitializeNpgsqlAsync(serviceProvider, () => createContext(this), seed); - protected override void Initialize(Func createContext, Action? seed, Action? clean) + protected override async Task InitializeAsync(Func createContext, Func? seed, Func? clean) { - if (CreateDatabase(clean)) + if (await CreateDatabaseAsync(clean)) { if (_scriptPath is not null) { @@ -100,15 +97,18 @@ protected override void Initialize(Func createContext, Action command.ExecuteNonQuery(), _additionalSql); } - seed?.Invoke(context); + if (seed is not null) + { + await seed(context); + } } } } @@ -129,40 +129,31 @@ public override DbContextOptionsBuilder AddProviderOptions(DbContextOptionsBuild : builder.UseNpgsql(_connectionString, npgsqlOptionsBuilder); } - private bool CreateDatabase(Action? clean) + private async Task CreateDatabaseAsync(Func? clean) { - using (var master = new NpgsqlConnection(CreateAdminConnectionString())) + await using var master = new NpgsqlConnection(CreateAdminConnectionString()); + + if (await DatabaseExistsAsync(Name)) { - if (DatabaseExists(Name)) + if (_scriptPath is not null) { - if (_scriptPath is not null) - { - return false; - } - - using (var context = new DbContext( - AddProviderOptions( - new DbContextOptionsBuilder() - .EnableServiceProviderCaching(false)) - .Options)) - { - clean?.Invoke(context); - Clean(context); - return true; - } + return false; } - ExecuteNonQuery(master, GetCreateDatabaseStatement(Name)); - WaitForExists((NpgsqlConnection)Connection); + await using var context = new DbContext( + AddProviderOptions(new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options); + clean?.Invoke(context); + await CleanAsync(context); + return true; } + await ExecuteNonQueryAsync(master, GetCreateDatabaseStatement(Name)); + await WaitForExistsAsync((NpgsqlConnection)Connection); + return true; } - private static void WaitForExists(NpgsqlConnection connection) - => WaitForExistsImplementation(connection); - - private static void WaitForExistsImplementation(NpgsqlConnection connection) + private static async Task WaitForExistsAsync(NpgsqlConnection connection) { var retryCount = 0; while (true) @@ -171,13 +162,13 @@ private static void WaitForExistsImplementation(NpgsqlConnection connection) { if (connection.State != ConnectionState.Closed) { - connection.Close(); + await connection.CloseAsync(); } NpgsqlConnection.ClearPool(connection); - connection.Open(); - connection.Close(); + await connection.OpenAsync(); + await connection.CloseAsync(); return; } catch (PostgresException e) @@ -188,7 +179,7 @@ private static void WaitForExistsImplementation(NpgsqlConnection connection) throw; } - Thread.Sleep(100); + await Task.Delay(100); } } } @@ -212,61 +203,46 @@ public void ExecuteScript(string scriptPath) }, ""); } - // ReSharper disable once UnusedMember.Local - private static void Clean(string name) - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(CreateConnectionString(name), b => b.ApplyConfiguration()) - .UseInternalServiceProvider( - new ServiceCollection() - .AddEntityFrameworkNpgsql() - .BuildServiceProvider()) - .Options; - - using (var context = new DbContext(options)) - { - context.Database.EnsureClean(); - } - } - private static string GetCreateDatabaseStatement(string name) - => $@"CREATE DATABASE ""{name}"""; + => $""" + CREATE DATABASE "{name}" + """; - private static bool DatabaseExists(string name) + private static async Task DatabaseExistsAsync(string name) { - using (var master = new NpgsqlConnection(CreateAdminConnectionString())) - { - return ExecuteScalar(master, $@"SELECT COUNT(*) FROM pg_database WHERE datname = '{name}'") > 0; - } + await using var master = new NpgsqlConnection(CreateAdminConnectionString()); + + return await ExecuteScalarAsync(master, $@"SELECT COUNT(*) FROM pg_database WHERE datname = '{name}'") > 0; } - public void DeleteDatabase() + public async Task DeleteDatabaseAsync() { - if (!DatabaseExists(Name)) + if (!await DatabaseExistsAsync(Name)) { return; } - using (var master = new NpgsqlConnection(CreateAdminConnectionString())) - { - ExecuteNonQuery(master, GetDisconnectDatabaseSql(Name)); - ExecuteNonQuery(master, GetDropDatabaseSql(Name)); + await using var master = new NpgsqlConnection(CreateAdminConnectionString()); - NpgsqlConnection.ClearAllPools(); - } + await ExecuteNonQueryAsync(master, GetDisconnectDatabaseSql(Name)); + await ExecuteNonQueryAsync(master, GetDropDatabaseSql(Name)); + + NpgsqlConnection.ClearAllPools(); } // Kill all connection to the database - // TODO: Pre-9.2 PG has column name procid instead of pid private static string GetDisconnectDatabaseSql(string name) - => $@" -REVOKE CONNECT ON DATABASE ""{name}"" FROM PUBLIC; + => $""" +REVOKE CONNECT ON DATABASE "{name}" FROM PUBLIC; SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity - WHERE datname = '{name}'"; + WHERE datname = '{name}' +"""; private static string GetDropDatabaseSql(string name) - => $@"DROP DATABASE ""{name}"""; + => $""" + DROP DATABASE "{name}" + """; public override void OpenConnection() => Connection.Open(); @@ -310,16 +286,15 @@ private static IEnumerable Query(DbConnection connection, string sql, obje => Execute( connection, command => { - using (var dataReader = command.ExecuteReader()) - { - var results = Enumerable.Empty(); - while (dataReader.Read()) - { - results = results.Concat(new[] { dataReader.GetFieldValue(0) }); - } + using var dataReader = command.ExecuteReader(); - return results; + var results = Enumerable.Empty(); + while (dataReader.Read()) + { + results = results.Concat([dataReader.GetFieldValue(0)]); } + + return results; }, sql, false, parameters); // ReSharper disable once UnusedMember.Global @@ -330,16 +305,15 @@ private static Task> QueryAsync(DbConnection connection, strin => ExecuteAsync( connection, async command => { - await using (var dataReader = await command.ExecuteReaderAsync()) - { - var results = Enumerable.Empty(); - while (await dataReader.ReadAsync()) - { - results = results.Concat(new[] { await dataReader.GetFieldValueAsync(0) }); - } + await using var dataReader = await command.ExecuteReaderAsync(); - return results; + var results = Enumerable.Empty(); + while (await dataReader.ReadAsync()) + { + results = results.Concat([await dataReader.GetFieldValueAsync(0)]); } + + return results; }, sql, false, parameters); private static T Execute( @@ -365,19 +339,18 @@ private static T ExecuteCommand( connection.Open(); try { - using (var transaction = useTransaction ? connection.BeginTransaction() : null) + using var transaction = useTransaction ? connection.BeginTransaction() : null; + + T result; + using (var command = CreateCommand(connection, sql, parameters)) { - T result; - using (var command = CreateCommand(connection, sql, parameters)) - { - command.Transaction = transaction; - result = execute(command); - } + command.Transaction = transaction; + result = execute(command); + } - transaction?.Commit(); + transaction?.Commit(); - return result; - } + return result; } finally { @@ -406,31 +379,33 @@ private static async Task ExecuteCommandAsync( { if (connection.State != ConnectionState.Closed) { - connection.Close(); + await connection.CloseAsync(); } await connection.OpenAsync(); try { - await using (var transaction = useTransaction ? connection.BeginTransaction() : null) - { - T result; - await using (var command = CreateCommand(connection, sql, parameters)) - { - result = await executeAsync(command); - } + await using var transaction = useTransaction ? await connection.BeginTransactionAsync() : null; - transaction?.Commit(); + T result; + await using (var command = CreateCommand(connection, sql, parameters)) + { + result = await executeAsync(command); + } - return result; + if (transaction is not null) + { + await transaction.CommitAsync(); } + + return result; } finally { if (connection.State == ConnectionState.Closed && connection.State != ConnectionState.Closed) { - connection.Close(); + await connection.CloseAsync(); } } } @@ -471,6 +446,9 @@ public static string CreateConnectionString(string name, string? options = null) private static string CreateAdminConnectionString() => CreateConnectionString("postgres"); - public override void Clean(DbContext context) - => context.Database.EnsureClean(); + public override Task CleanAsync(DbContext context) + { + context.Database.EnsureClean(); + return Task.CompletedTask; + } } diff --git a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs index 8b64a5cb1..20dc78487 100644 --- a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs @@ -512,7 +512,7 @@ public override async Task Edit_single_property_bool() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -534,7 +534,7 @@ public override async Task Edit_single_property_byte() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -555,7 +555,7 @@ public override async Task Edit_single_property_char() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -577,7 +577,7 @@ public override async Task Edit_single_property_datetime() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -599,7 +599,7 @@ public override async Task Edit_single_property_datetimeoffset() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -621,7 +621,7 @@ public override async Task Edit_single_property_decimal() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -643,7 +643,7 @@ public override async Task Edit_single_property_double() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -665,7 +665,7 @@ public override async Task Edit_single_property_guid() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -687,7 +687,7 @@ public override async Task Edit_single_property_int16() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -709,7 +709,7 @@ public override async Task Edit_single_property_int32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -731,7 +731,7 @@ public override async Task Edit_single_property_int64() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -753,7 +753,7 @@ public override async Task Edit_single_property_signed_byte() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -775,7 +775,7 @@ public override async Task Edit_single_property_single() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -797,7 +797,7 @@ public override async Task Edit_single_property_timespan() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -819,7 +819,7 @@ public override async Task Edit_single_property_uint16() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -841,7 +841,7 @@ public override async Task Edit_single_property_uint32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -863,7 +863,7 @@ public override async Task Edit_single_property_uint64() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -885,7 +885,7 @@ public override async Task Edit_single_property_nullable_int32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -907,7 +907,7 @@ public override async Task Edit_single_property_nullable_int32_set_to_null() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -929,7 +929,7 @@ public override async Task Edit_single_property_enum() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -951,7 +951,7 @@ public override async Task Edit_single_property_enum_with_int_converter() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -973,7 +973,7 @@ public override async Task Edit_single_property_nullable_enum() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -995,7 +995,7 @@ public override async Task Edit_single_property_nullable_enum_set_to_null() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1017,7 +1017,7 @@ public override async Task Edit_single_property_nullable_enum_with_int_converter """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1039,7 +1039,7 @@ public override async Task Edit_single_property_nullable_enum_with_int_converter """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1061,7 +1061,7 @@ public override async Task Edit_single_property_nullable_enum_with_converter_tha """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1083,7 +1083,7 @@ public override async Task Edit_single_property_nullable_enum_with_converter_tha """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1096,8 +1096,8 @@ public override async Task Edit_two_properties_on_same_entity_updates_the_entire AssertSql( """ -@p0='{"TestBoolean":false,"TestBooleanCollection":[true,false],"TestByte":25,"TestByteCollection":null,"TestCharacter":"h","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2323-04-03","TestDateOnlyCollection":["3234-01-23","4331-01-21"],"TestDateTime":"2100-11-11T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2200-11-11T12:34:56-05:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-123450.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInCollection1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.2345,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"00000000-0000-0000-0000-000000000000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-12,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Baz","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Two","TestNullableEnumWithConverterThatHandlesNullsCollection":[-1,null,-7],"TestNullableEnumWithIntConverter":-3,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":90,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-18,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.4,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"05:07:08.0000000","TestTimeOnlyCollection":["13:42:23.0000000","07:17:25.0000000"],"TestTimeSpan":"06:05:04.003","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":12,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":12345,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567867,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) -@p1='{"TestBoolean":true,"TestBooleanCollection":[true,false],"TestByte":255,"TestByteCollection":null,"TestCharacter":"a","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2023-10-10","TestDateOnlyCollection":["1234-01-23","4321-01-21"],"TestDateTime":"2000-01-01T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2000-01-01T12:34:56-08:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-1234567890.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInReference1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.23456789,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"12345678-1234-4321-7777-987654321000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-1234,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Foo","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Three","TestNullableEnumWithConverterThatHandlesNullsCollection":[-1,null,-7],"TestNullableEnumWithIntConverter":2,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":78,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-128,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.234,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"11:12:13.0000000","TestTimeOnlyCollection":["11:42:23.0000000","07:17:27.0000000"],"TestTimeSpan":"10:09:08.007","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":1234,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":1234565789,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567890123456789,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) +@p0='{"TestBoolean":false,"TestBooleanCollection":[true,false],"TestByte":25,"TestByteArray":"","TestByteCollection":null,"TestCharacter":"h","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2323-04-03","TestDateOnlyCollection":["3234-01-23","4331-01-21"],"TestDateTime":"2100-11-11T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2200-11-11T12:34:56-05:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-123450.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInCollection1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.2345,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"00000000-0000-0000-0000-000000000000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-12,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Baz","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Two","TestNullableEnumWithIntConverter":-3,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":90,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-18,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.4,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"05:07:08.0000000","TestTimeOnlyCollection":["13:42:23.0000000","07:17:25.0000000"],"TestTimeSpan":"06:05:04.003","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":12,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":12345,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567867,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) +@p1='{"TestBoolean":true,"TestBooleanCollection":[true,false],"TestByte":255,"TestByteArray":"AQID","TestByteCollection":null,"TestCharacter":"a","TestCharacterCollection":["A","B","\u0022"],"TestDateOnly":"2023-10-10","TestDateOnlyCollection":["1234-01-23","4321-01-21"],"TestDateTime":"2000-01-01T12:34:56","TestDateTimeCollection":["2000-01-01T12:34:56","3000-01-01T12:34:56"],"TestDateTimeOffset":"2000-01-01T12:34:56-08:00","TestDateTimeOffsetCollection":["2000-01-01T12:34:56-08:00"],"TestDecimal":-1234567890.01,"TestDecimalCollection":[-1234567890.01],"TestDefaultString":"MyDefaultStringInReference1","TestDefaultStringCollection":["S1","\u0022S2\u0022","S3"],"TestDouble":-1.23456789,"TestDoubleCollection":[-1.23456789,1.23456789,0],"TestEnum":-1,"TestEnumCollection":[-1,-3,-7],"TestEnumWithIntConverter":2,"TestEnumWithIntConverterCollection":[-1,-3,-7],"TestGuid":"12345678-1234-4321-7777-987654321000","TestGuidCollection":["12345678-1234-4321-7777-987654321000"],"TestInt16":-1234,"TestInt16Collection":[-32768,0,32767],"TestInt32":32,"TestInt32Collection":[-2147483648,0,2147483647],"TestInt64":64,"TestInt64Collection":[-9223372036854775808,0,9223372036854775807],"TestMaxLengthString":"Foo","TestMaxLengthStringCollection":["S1","S2","S3"],"TestNullableEnum":-1,"TestNullableEnumCollection":[-1,null,-3,-7],"TestNullableEnumWithConverterThatHandlesNulls":"Three","TestNullableEnumWithIntConverter":2,"TestNullableEnumWithIntConverterCollection":[-1,null,-3,-7],"TestNullableInt32":78,"TestNullableInt32Collection":[null,-2147483648,0,null,2147483647,null],"TestSignedByte":-128,"TestSignedByteCollection":[-128,0,127],"TestSingle":-1.234,"TestSingleCollection":[-1.234,0,-1.234],"TestTimeOnly":"11:12:13.0000000","TestTimeOnlyCollection":["11:42:23.0000000","07:17:27.0000000"],"TestTimeSpan":"10:09:08.007","TestTimeSpanCollection":["10:09:08.007","-09:50:51.993"],"TestUnsignedInt16":1234,"TestUnsignedInt16Collection":[0,0,65535],"TestUnsignedInt32":1234565789,"TestUnsignedInt32Collection":[0,0,4294967295],"TestUnsignedInt64":1234567890123456789,"TestUnsignedInt64Collection":[0,0,18446744073709551615]}' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0}', @p0), "Reference" = @p1 @@ -1105,7 +1105,7 @@ public override async Task Edit_two_properties_on_same_entity_updates_the_entire """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1336,7 +1336,7 @@ public override async Task Edit_single_property_collection_of_bool() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1358,7 +1358,7 @@ public override async Task Edit_single_property_collection_of_byte() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1388,7 +1388,7 @@ public override async Task Edit_single_property_collection_of_datetime() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1410,7 +1410,7 @@ public override async Task Edit_single_property_collection_of_datetimeoffset() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1432,7 +1432,7 @@ public override async Task Edit_single_property_collection_of_decimal() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1454,7 +1454,7 @@ public override async Task Edit_single_property_collection_of_double() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1476,7 +1476,7 @@ public override async Task Edit_single_property_collection_of_guid() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1498,7 +1498,7 @@ public override async Task Edit_single_property_collection_of_int16() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1520,7 +1520,7 @@ public override async Task Edit_single_property_collection_of_int32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1542,7 +1542,7 @@ public override async Task Edit_single_property_collection_of_int64() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1564,7 +1564,7 @@ public override async Task Edit_single_property_collection_of_signed_byte() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1586,7 +1586,7 @@ public override async Task Edit_single_property_collection_of_single() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1608,7 +1608,7 @@ public override async Task Edit_single_property_collection_of_timespan() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1630,7 +1630,7 @@ public override async Task Edit_single_property_collection_of_dateonly() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1652,7 +1652,7 @@ public override async Task Edit_single_property_collection_of_timeonly() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1674,7 +1674,7 @@ public override async Task Edit_single_property_collection_of_uint16() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1696,7 +1696,7 @@ public override async Task Edit_single_property_collection_of_uint32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1718,7 +1718,7 @@ public override async Task Edit_single_property_collection_of_uint64() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1740,7 +1740,7 @@ public override async Task Edit_single_property_collection_of_nullable_int32() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1762,7 +1762,7 @@ public override async Task Edit_single_property_collection_of_nullable_int32_set """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1784,7 +1784,7 @@ public override async Task Edit_single_property_collection_of_enum() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1806,7 +1806,7 @@ public override async Task Edit_single_property_collection_of_enum_with_int_conv """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1828,7 +1828,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum() """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1850,7 +1850,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum_set_ """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1872,7 +1872,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum_with """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1894,29 +1894,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum_with """, // """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" -FROM "JsonEntitiesAllTypes" AS j -WHERE j."Id" = 1 -LIMIT 2 -"""); - } - - public override async Task Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls() - { - await base.Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls(); - - AssertSql( - """ -@p0='[-3]' (Nullable = false) (DbType = Object) -@p1='[-1]' (Nullable = false) (DbType = Object) -@p2='1' - -UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestNullableEnumWithConverterThatHandlesNullsCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestNullableEnumWithConverterThatHandlesNullsCollection}', @p1) -WHERE "Id" = @p2; -""", - // - """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1929,16 +1907,7 @@ public override async Task Edit_single_property_collection_of_nullable_enum_with AssertSql( """ -@p0='null' (Nullable = false) (DbType = Object) -@p1='null' (Nullable = false) (DbType = Object) -@p2='1' - -UPDATE "JsonEntitiesAllTypes" SET "Collection" = jsonb_set("Collection", '{0,TestNullableEnumWithConverterThatHandlesNullsCollection}', @p0), "Reference" = jsonb_set("Reference", '{TestNullableEnumWithConverterThatHandlesNullsCollection}', @p1) -WHERE "Id" = @p2; -""", - // - """ -SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestGuidCollection", j."TestInt32Collection", j."TestInt64Collection", j."TestMaxLengthStringCollection", j."TestNullableEnumWithConverterThatHandlesNullsCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" +SELECT j."Id", j."TestDateTimeCollection", j."TestDecimalCollection", j."TestDefaultStringCollection", j."TestEnumWithIntConverterCollection", j."TestInt32Collection", j."TestMaxLengthStringCollection", j."TestSignedByteCollection", j."TestSingleCollection", j."TestTimeSpanCollection", j."TestUnsignedInt32Collection", j."Collection", j."Reference" FROM "JsonEntitiesAllTypes" AS j WHERE j."Id" = 1 LIMIT 2 @@ -1982,6 +1951,9 @@ public override Task Edit_single_property_relational_collection_of_enum() public override Task Edit_single_property_relational_collection_of_int16() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_int16()); + public override Task Edit_single_property_relational_collection_of_guid() + => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_int16()); + public override Task Edit_single_property_relational_collection_of_nullable_enum() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_nullable_enum()); @@ -2007,8 +1979,53 @@ public override Task Edit_single_property_relational_collection_of_uint16() public override Task Edit_single_property_relational_collection_of_uint64() => Assert.ThrowsAsync(() => base.Edit_single_property_relational_collection_of_uint64()); + public override Task Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls() + => Assert.ThrowsAsync( + () => base.Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls()); + + public override Task Edit_single_property_relational_collection_of_nullable_enum_with_converter_that_handles_nulls() + => Assert.ThrowsAsync( + () => base.Edit_single_property_collection_of_nullable_enum_with_converter_that_handles_nulls()); + #endregion + #region Skipped tests because of nested collections outside of JSON (nested arrays not supported in PG) + + public override Task Edit_single_property_collection_of_collection_of_bool() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_char() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_double() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_int16() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_int32() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_int64() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_single() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_nullable_int32() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_nullable_int32_set_to_null() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_nullable_enum_set_to_null() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + public override Task Edit_single_property_collection_of_collection_of_nullable_enum_with_int_converter() + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + + #endregion Skipped tests because of nested collections outside of JSON (nested arrays not supported in PG) + protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); @@ -2054,6 +2071,77 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con b.Ignore(j => j.TestDoubleCollection); b.Ignore(j => j.TestInt16Collection); }); + + // These use collection types which are unsupported for arrays at the Npgsql level - we currently only support List/array. + modelBuilder.Entity( + b => + { + b.Ignore(j => j.TestInt64Collection); + b.Ignore(j => j.TestGuidCollection); + }); + + // Ignore nested collections - these aren't supported on PostgreSQL (no arrays of arrays). + // TODO: Remove these after syncing to 9.0.0-rc.1, and extending from the relational test base and fixture + modelBuilder.Entity( + b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); + + modelBuilder.Entity().OwnsOne( + x => x.Reference, b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); + + modelBuilder.Entity().OwnsMany( + x => x.Collection, b => + { + b.Ignore(j => j.TestDefaultStringCollectionCollection); + b.Ignore(j => j.TestMaxLengthStringCollectionCollection); + + b.Ignore(j => j.TestInt16CollectionCollection); + b.Ignore(j => j.TestInt32CollectionCollection); + b.Ignore(j => j.TestInt64CollectionCollection); + b.Ignore(j => j.TestDoubleCollectionCollection); + b.Ignore(j => j.TestSingleCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestBooleanCollectionCollection); + b.Ignore(j => j.TestCharacterCollectionCollection); + + b.Ignore(j => j.TestNullableInt32CollectionCollection); + b.Ignore(j => j.TestNullableEnumCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithIntConverterCollectionCollection); + b.Ignore(j => j.TestNullableEnumWithConverterThatHandlesNullsCollection); + }); } } } diff --git a/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs index 6b7a7869f..56ea743cf 100644 --- a/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ValueConvertersEndToEndNpgsqlTest.cs @@ -6,7 +6,7 @@ public class ValueConvertersEndToEndNpgsqlTest(ValueConvertersEndToEndNpgsqlTest : ValueConvertersEndToEndTestBase(fixture) { [ConditionalTheory(Skip = "DateTime and DateTimeOffset, https://github.com/dotnet/efcore/issues/26068")] - public override void Can_insert_and_read_back_with_conversions(int[] valueOrder) + public override Task Can_insert_and_read_back_with_conversions(int[] valueOrder) => base.Can_insert_and_read_back_with_conversions(valueOrder); [ConditionalTheory] diff --git a/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj b/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj index db1b1e6d9..7f23750d4 100644 --- a/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj +++ b/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj @@ -3,6 +3,10 @@ Npgsql.EntityFrameworkCore.PostgreSQL.Tests Npgsql.EntityFrameworkCore.PostgreSQL + + $(NoWarn);NU1903 + false + critical diff --git a/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs b/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs index f7a4c5571..046271648 100644 --- a/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs +++ b/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs @@ -128,7 +128,6 @@ public void GetBeginIfExistsScript_works() Assert.Equal( """ - DO $EF$ BEGIN IF EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = 'Migration1') THEN From 318c46ce5b9c510f2e9bd19bdd5e119d78dbc175 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 1 Sep 2024 22:14:49 +0200 Subject: [PATCH 048/107] Bump version to 9.0.0-rc.1 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 64a9c63aa..503810c8e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0-preview.7 + 9.0.0-rc.1 latest true latest From 1a95104ec7f0379a97183c150bc3599d35965479 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 2 Sep 2024 11:14:01 +0200 Subject: [PATCH 049/107] Sync to EF Core 9.0.0-rc.1.24451.1 (#3260) --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- Directory.Packages.props | 15 +-- global.json | 2 +- .../Internal/NpgsqlHistoryRepository.cs | 4 +- .../Migrations/Internal/NpgsqlMigrator.cs | 29 ++--- .../NpgsqlParameterBasedSqlProcessor.cs | 6 +- ...NpgsqlParameterBasedSqlProcessorFactory.cs | 11 +- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 5 + .../Internal/NpgsqlSqlNullabilityProcessor.cs | 15 +-- .../EFCore.PG.FunctionalTests.csproj | 1 + .../ExecutionStrategyTest.cs | 4 +- .../JsonTypesNpgsqlTest.cs | 47 +++---- .../MigrationsInfrastructureNpgsqlTest.cs | 16 +-- .../Query/AdHocJsonQueryNpgsqlTest.cs | 20 +-- .../Query/JsonQueryNpgsqlTest.cs | 4 +- ...aredPrimitiveCollectionsQueryNpgsqlTest.cs | 15 +++ ...thwindAggregateOperatorsQueryNpgsqlTest.cs | 29 ++++- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 116 ++++++++++++++++-- .../NpgsqlDatabaseModelFactoryTest.cs | 4 +- .../Update/JsonUpdateNpgsqlTest.cs | 22 ++-- 21 files changed, 247 insertions(+), 122 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c6d4bc99..242f8e6f6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: pull_request: env: - dotnet_sdk_version: '9.0.100-preview.7.24407.12' + dotnet_sdk_version: '9.0.100-rc.1.24451.4' postgis_version: 3 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9f6bdb73b..31920ab09 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ on: - cron: '30 22 * * 6' env: - dotnet_sdk_version: '9.0.100-preview.7.24407.12' + dotnet_sdk_version: '9.0.100-rc.1.24451.4' jobs: analyze: diff --git a/Directory.Packages.props b/Directory.Packages.props index 719cf3228..fd299fb25 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - [9.0.0-preview.7.24405.3] - 9.0.0-preview.7.24405.7 + [9.0.0-rc.1.24451.1] + 9.0.0-rc.1.24431.7 8.0.3 @@ -21,15 +21,16 @@ - + + - - - - + + + + diff --git a/global.json b/global.json index a143424dc..8fcf651cb 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100-preview.7.24407.12", + "version": "9.0.100-rc.1.24451.4", "rollForward": "latestMajor", "allowPrerelease": true } diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index beaf86d7c..5fadf3eba 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -63,7 +63,7 @@ await Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalarAsync( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IDisposable GetDatabaseLock(TimeSpan timeout) + public override IDisposable GetDatabaseLock() { // TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a // transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. @@ -81,7 +81,7 @@ public override IDisposable GetDatabaseLock(TimeSpan timeout) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override Task GetDatabaseLockAsync(TimeSpan timeout, CancellationToken cancellationToken = default) + public override Task GetDatabaseLockAsync(CancellationToken cancellationToken = default) { // TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a // transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs index 6b7d1f16b..cf8d222c5 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs @@ -36,10 +36,13 @@ public NpgsqlMigrator( IModelRuntimeInitializer modelRuntimeInitializer, IDiagnosticsLogger logger, IRelationalCommandDiagnosticsLogger commandLogger, - IDatabaseProvider databaseProvider) + IDatabaseProvider databaseProvider, + IMigrationsModelDiffer migrationsModelDiffer, + IDesignTimeModel designTimeModel, + IDbContextOptions contextOptions) : base(migrationsAssembly, historyRepository, databaseCreator, migrationsSqlGenerator, rawSqlCommandBuilder, migrationCommandExecutor, connection, sqlGenerationHelper, currentContext, modelRuntimeInitializer, logger, - commandLogger, databaseProvider) + commandLogger, databaseProvider, migrationsModelDiffer, designTimeModel, contextOptions) { _historyRepository = historyRepository; _connection = connection; @@ -51,7 +54,7 @@ public NpgsqlMigrator( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override void Migrate(string? targetMigration = null) + public override void Migrate(string? targetMigration) { var appliedMigrations = _historyRepository.GetAppliedMigrations(); @@ -60,17 +63,15 @@ public override void Migrate(string? targetMigration = null) PopulateMigrations( appliedMigrations.Select(t => t.MigrationId), targetMigration, - out var migrationsToApply, - out var migrationsToRevert, - out _); + out var migratorData); - if (migrationsToRevert.Count + migrationsToApply.Count == 0) + if (migratorData.RevertedMigrations.Count + migratorData.AppliedMigrations.Count == 0) { return; } // If a PostgreSQL extension, enum or range was added, we want Npgsql to reload all types at the ADO.NET level. - var migrations = migrationsToApply.Count > 0 ? migrationsToApply : migrationsToRevert; + var migrations = migratorData.AppliedMigrations.Count > 0 ? migratorData.AppliedMigrations : migratorData.RevertedMigrations; var reloadTypes = migrations .SelectMany(m => m.UpOperations) .OfType() @@ -96,9 +97,7 @@ public override void Migrate(string? targetMigration = null) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override async Task MigrateAsync( - string? targetMigration = null, - CancellationToken cancellationToken = default) + public override async Task MigrateAsync(string? targetMigration, CancellationToken cancellationToken = default) { var appliedMigrations = await _historyRepository.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); @@ -107,17 +106,15 @@ public override async Task MigrateAsync( PopulateMigrations( appliedMigrations.Select(t => t.MigrationId), targetMigration, - out var migrationsToApply, - out var migrationsToRevert, - out _); + out var migratorData); - if (migrationsToRevert.Count + migrationsToApply.Count == 0) + if (migratorData.RevertedMigrations.Count + migratorData.AppliedMigrations.Count == 0) { return; } // If a PostgreSQL extension, enum or range was added, we want Npgsql to reload all types at the ADO.NET level. - var migrations = migrationsToApply.Count > 0 ? migrationsToApply : migrationsToRevert; + var migrations = migratorData.AppliedMigrations.Count > 0 ? migratorData.AppliedMigrations : migratorData.RevertedMigrations; var reloadTypes = migrations .SelectMany(m => m.UpOperations) .OfType() diff --git a/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessor.cs index c79480a72..dbb97a5c4 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessor.cs @@ -16,8 +16,8 @@ public class NpgsqlParameterBasedSqlProcessor : RelationalParameterBasedSqlProce /// public NpgsqlParameterBasedSqlProcessor( RelationalParameterBasedSqlProcessorDependencies dependencies, - bool useRelationalNulls) - : base(dependencies, useRelationalNulls) + RelationalParameterBasedSqlProcessorParameters parameters) + : base(dependencies, parameters) { } @@ -48,7 +48,7 @@ protected override Expression ProcessSqlNullability( Check.NotNull(selectExpression, nameof(selectExpression)); Check.NotNull(parametersValues, nameof(parametersValues)); - return new NpgsqlSqlNullabilityProcessor(Dependencies, UseRelationalNulls).Process( + return new NpgsqlSqlNullabilityProcessor(Dependencies, Parameters).Process( selectExpression, parametersValues, out canCache); } } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs b/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs index d3d0d342d..fa467d127 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs @@ -23,11 +23,10 @@ public NpgsqlParameterBasedSqlProcessorFactory( } /// - /// 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. + /// Creates a new . /// - public virtual RelationalParameterBasedSqlProcessor Create(bool useRelationalNulls) - => new NpgsqlParameterBasedSqlProcessor(_dependencies, useRelationalNulls); + /// Parameters for . + /// A relational parameter based sql processor. + public RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters) + => new NpgsqlParameterBasedSqlProcessor(_dependencies, parameters); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index e81ebcf0a..a6d4a6871 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -767,6 +767,11 @@ protected override Expression VisitValues(ValuesExpression valuesExpression) /// protected override void GenerateValues(ValuesExpression valuesExpression) { + if (valuesExpression.RowValues is null) + { + throw new UnreachableException(); + } + if (valuesExpression.RowValues.Count == 0) { throw new InvalidOperationException(RelationalStrings.EmptyCollectionNotSupportedAsInlineQueryRoot); diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs index 09644eb44..215219f19 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs @@ -11,14 +11,15 @@ public class NpgsqlSqlNullabilityProcessor : SqlNullabilityProcessor private readonly ISqlExpressionFactory _sqlExpressionFactory; /// - /// Creates a new instance of the class. + /// 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. /// - /// Parameter object containing dependencies for this class. - /// A bool value indicating whether relational null semantics are in use. public NpgsqlSqlNullabilityProcessor( RelationalParameterBasedSqlProcessorDependencies dependencies, - bool useRelationalNulls) - : base(dependencies, useRelationalNulls) + RelationalParameterBasedSqlProcessorParameters parameters) + : base(dependencies, parameters) { _sqlExpressionFactory = dependencies.SqlExpressionFactory; } @@ -101,7 +102,7 @@ SqlExpression VisitRowValueComparison( // visit that (that adds the compensation). We then chain all such expressions together with AND. var valueBinaryExpression = Visit( _sqlExpressionFactory.MakeBinary( - operatorType, visitedLeftValue, visitedRightValue, typeMapping: null, existingExpr: sqlBinaryExpression)!, + operatorType, visitedLeftValue, visitedRightValue, typeMapping: null, existingExpression: sqlBinaryExpression)!, allowOptimizedExpansion, out _); @@ -144,7 +145,7 @@ visitedRightValues is null ? rightRowValue : new PgRowValueExpression(visitedRightValues, leftRowValue.Type, leftRowValue.TypeMapping), typeMapping: null, - existingExpr: sqlBinaryExpression)!; + existingExpression: sqlBinaryExpression)!; } Check.DebugAssert(visitedLeftValues is not null, "visitedLeftValues is not null"); diff --git a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj index 084ec149f..4011ace02 100644 --- a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj +++ b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj @@ -21,6 +21,7 @@ + diff --git a/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs b/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs index f66ac9433..640b3c356 100644 --- a/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs +++ b/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs @@ -99,7 +99,7 @@ private void Test_commit_failure(bool realFailure, Action l.Id == CoreEventId.ExecutionStrategyRetrying)); + Assert.DoesNotContain(Fixture.TestSqlLoggerFactory.Log, l => l.Id == CoreEventId.ExecutionStrategyRetrying); } Assert.Equal(realFailure ? 3 : 2, connection.OpenCount); @@ -213,7 +213,7 @@ private async Task Test_commit_failure_async( } else { - Assert.Empty(Fixture.TestSqlLoggerFactory.Log.Where(l => l.Id == CoreEventId.ExecutionStrategyRetrying)); + Assert.DoesNotContain(Fixture.TestSqlLoggerFactory.Log, l => l.Id == CoreEventId.ExecutionStrategyRetrying); } Assert.Equal(realFailure ? 3 : 2, connection.OpenCount); diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index d5307dbbd..6806f8fa0 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -6,6 +6,7 @@ using System.Numerics; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; +using Xunit.Sdk; namespace Npgsql.EntityFrameworkCore.PostgreSQL; @@ -17,76 +18,76 @@ public class JsonTypesNpgsqlTest : JsonTypesRelationalTestBase // supported). public override Task Can_read_write_array_of_array_of_array_of_int_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_array_of_array_of_int_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_array_of_array_of_int_JSON_values()); public override Task Can_read_write_array_of_list_of_array_of_IPAddress_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_array_of_IPAddress_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_array_of_IPAddress_JSON_values()); public override Task Can_read_write_array_of_list_of_array_of_string_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_array_of_string_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_array_of_string_JSON_values()); public override Task Can_read_write_array_of_list_of_binary_JSON_values(string expected) - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_binary_JSON_values(expected)); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_binary_JSON_values(expected)); public override Task Can_read_write_array_of_list_of_GUID_JSON_values(string expected) - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_GUID_JSON_values(expected)); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_GUID_JSON_values(expected)); public override Task Can_read_write_array_of_list_of_int_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_int_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_int_JSON_values()); public override Task Can_read_write_array_of_list_of_IPAddress_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_IPAddress_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_IPAddress_JSON_values()); public override Task Can_read_write_array_of_list_of_string_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_string_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_string_JSON_values()); public override Task Can_read_write_array_of_list_of_ulong_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_ulong_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_array_of_list_of_ulong_JSON_values()); public override Task Can_read_write_list_of_array_of_GUID_JSON_values(string expected) - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_GUID_JSON_values(expected)); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_GUID_JSON_values(expected)); public override Task Can_read_write_list_of_array_of_int_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_int_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_int_JSON_values()); public override Task Can_read_write_list_of_array_of_IPAddress_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_IPAddress_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_IPAddress_JSON_values()); public override Task Can_read_write_list_of_array_of_list_of_array_of_binary_JSON_values(string expected) - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_array_of_binary_JSON_values(expected)); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_array_of_binary_JSON_values(expected)); public override Task Can_read_write_list_of_array_of_list_of_IPAddress_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_IPAddress_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_IPAddress_JSON_values()); public override Task Can_read_write_list_of_array_of_list_of_string_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_string_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_string_JSON_values()); public override Task Can_read_write_list_of_array_of_list_of_ulong_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_ulong_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_list_of_ulong_JSON_values()); public override Task Can_read_write_list_of_array_of_nullable_GUID_JSON_values(string expected) - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_GUID_JSON_values(expected)); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_GUID_JSON_values(expected)); public override Task Can_read_write_list_of_array_of_nullable_int_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_int_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_int_JSON_values()); public override Task Can_read_write_list_of_array_of_nullable_ulong_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_ulong_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_nullable_ulong_JSON_values()); public override Task Can_read_write_list_of_array_of_string_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_string_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_string_JSON_values()); public override Task Can_read_write_list_of_array_of_ulong_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_ulong_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_ulong_JSON_values()); public override Task Can_read_write_list_of_list_of_list_of_int_JSON_values() - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_list_of_list_of_int_JSON_values()); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_list_of_list_of_int_JSON_values()); #endregion Nested collections (unsupported) // IEnumerable property public override Task Can_read_write_list_of_array_of_binary_JSON_values(string expected) - => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_binary_JSON_values(expected)); + => Assert.ThrowsAsync(() => base.Can_read_write_list_of_array_of_binary_JSON_values(expected)); // public override Task Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) // { diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs index 996e3402b..ba84d9a0a 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs @@ -58,7 +58,8 @@ public async Task Empty_Migration_Creates_Database() { await using var context = new BloggingContext( Fixture.TestStore.AddProviderOptions( - new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options); + new DbContextOptionsBuilder().EnableServiceProviderCaching(false)) + .ConfigureWarnings(e => e.Log(RelationalEventId.PendingModelChangesWarning)).Options); var creator = (NpgsqlDatabaseCreator)context.GetService(); creator.RetryTimeout = TimeSpan.FromMinutes(10); @@ -185,21 +186,14 @@ protected override ITestStoreFactory TestStoreFactory public override MigrationsContext CreateContext() { var options = AddOptions( - new DbContextOptionsBuilder() + TestStore.AddProviderOptions(new DbContextOptionsBuilder()) .UseNpgsql( TestStore.ConnectionString, b => b.ApplyConfiguration() - .CommandTimeout(NpgsqlTestStore.CommandTimeout) - .SetPostgresVersion(TestEnvironment.PostgresVersion) - .ReverseNullOrdering())) - .UseInternalServiceProvider(CreateServiceProvider()) + .SetPostgresVersion(TestEnvironment.PostgresVersion))) + .UseInternalServiceProvider(ServiceProvider) .Options; return new MigrationsContext(options); } - - private static IServiceProvider CreateServiceProvider() - => new ServiceCollection() - .AddEntityFrameworkNpgsql() - .BuildServiceProvider(); } } } diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs index cec0ec78a..056aab9d1 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs @@ -7,7 +7,7 @@ public class AdHocJsonQueryNpgsqlTest : AdHocJsonQueryTestBase protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; - protected override async Task Seed29219(MyContext29219 ctx) + protected override async Task Seed29219(DbContext ctx) { var entity1 = new MyEntity29219 { @@ -28,7 +28,7 @@ protected override async Task Seed29219(MyContext29219 ctx) Collection = [new() { NonNullableScalar = 1001, NullableScalar = null }] }; - ctx.Entities.AddRange(entity1, entity2); + ctx.AddRange(entity1, entity2); await ctx.SaveChangesAsync(); await ctx.Database.ExecuteSqlAsync( @@ -38,7 +38,7 @@ await ctx.Database.ExecuteSqlAsync( """); } - protected override async Task Seed30028(MyContext30028 ctx) + protected override async Task Seed30028(DbContext ctx) { // complete await ctx.Database.ExecuteSqlAsync( @@ -77,14 +77,14 @@ await ctx.Database.ExecuteSqlAsync( """); } - protected override async Task Seed33046(Context33046 ctx) + protected override async Task Seed33046(DbContext ctx) => await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Reviews" ("Rounds", "Id") VALUES('[{"RoundNumber":11,"SubRounds":[{"SubRoundNumber":111},{"SubRoundNumber":112}]}]', 1) """); - protected override async Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) + protected override async Task SeedArrayOfPrimitives(DbContext ctx) { var entity1 = new MyEntityArrayOfPrimitives { @@ -126,11 +126,11 @@ protected override async Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives c ] }; - ctx.Entities.AddRange(entity1, entity2); + ctx.AddRange(entity1, entity2); await ctx.SaveChangesAsync(); } - protected override async Task SeedJunkInJson(MyContextJunkInJson ctx) + protected override async Task SeedJunkInJson(DbContext ctx) => await ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id") @@ -142,7 +142,7 @@ protected override async Task SeedJunkInJson(MyContextJunkInJson ctx) 1) """); - protected override async Task SeedTrickyBuffering(MyContextTrickyBuffering ctx) + protected override async Task SeedTrickyBuffering(DbContext ctx) => await ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO "Entities" ("Reference", "Id") @@ -150,7 +150,7 @@ protected override async Task SeedTrickyBuffering(MyContextTrickyBuffering ctx) '{"Name": "r1", "Number": 7, "JunkReference":{"Something": "SomeValue" }, "JunkCollection": [{"Foo": "junk value"}], "NestedReference": {"DoB": "2000-01-01T00:00:00Z"}, "NestedCollection": [{"DoB": "2000-02-01T00:00:00Z", "JunkReference": {"Something": "SomeValue"}}, {"DoB": "2000-02-02T00:00:00Z"}]}',1) """); - protected override async Task SeedShadowProperties(MyContextShadowProperties ctx) + protected override async Task SeedShadowProperties(DbContext ctx) => await ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id", "Name") @@ -163,7 +163,7 @@ protected override async Task SeedShadowProperties(MyContextShadowProperties ctx 'e1') """); - protected override async Task SeedNotICollection(MyContextNotICollection ctx) + protected override async Task SeedNotICollection(DbContext ctx) { await ctx.Database.ExecuteSqlAsync( $$""" diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs index 9af98af68..e4706b39b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs @@ -3,7 +3,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class JsonQueryNpgsqlTest : JsonQueryTestBase +public class JsonQueryNpgsqlTest : JsonQueryRelationalTestBase { public JsonQueryNpgsqlTest(JsonQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) @@ -3370,7 +3370,7 @@ public virtual void Check_all_tests_overridden() private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - public class JsonQueryNpgsqlFixture : JsonQueryFixtureBase, IQueryFixtureBase + public class JsonQueryNpgsqlFixture : JsonQueryRelationalFixture, IQueryFixtureBase { protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs index fa4e4ab1b..399c8cd84 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs @@ -1,3 +1,4 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; @@ -95,6 +96,20 @@ LIMIT 2 """); } + protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder) + { + new NpgsqlDbContextOptionsBuilder(optionsBuilder).TranslateParameterizedCollectionsToConstants(); + + return optionsBuilder; + } + + protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToParameters(DbContextOptionsBuilder optionsBuilder) + { + new NpgsqlDbContextOptionsBuilder(optionsBuilder).TranslateParameterizedCollectionsToParameters(); + + return optionsBuilder; + } + protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs index e5dc276ad..6765d9481 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs @@ -16,14 +16,33 @@ public NorthwindAggregateOperatorsQueryNpgsqlTest( } // Overriding to add equality tolerance because of floating point precision - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public override Task Average_over_max_subquery_is_client_eval(bool async) - => AssertAverage( + public override async Task Average_over_max_subquery(bool async) + { + await AssertAverage( async, ss => ss.Set().OrderBy(c => c.CustomerID).Take(3), selector: c => (decimal)c.Orders.Average(o => 5 + o.OrderDetails.Max(od => od.ProductID)), - asserter: (a, b) => Assert.Equal(a, b, 15)); + asserter: (e, a) => Assert.Equal(e, a, 10)); + + AssertSql( + """ +@__p_0='3' + +SELECT avg(( + SELECT avg(CAST(5 + ( + SELECT max(o0."ProductID") + FROM "Order Details" AS o0 + WHERE o."OrderID" = o0."OrderID") AS double precision)) + FROM "Orders" AS o + WHERE c0."CustomerID" = o."CustomerID")::numeric) +FROM ( + SELECT c."CustomerID" + FROM "Customers" AS c + ORDER BY c."CustomerID" NULLS FIRST + LIMIT @__p_0 +) AS c0 +"""); + } public override async Task Contains_with_local_uint_array_closure(bool async) { diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 9b8cd9926..7802ed3e7 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -148,18 +148,6 @@ public override async Task Inline_collection_Contains_with_three_values(bool asy """); } - public override async Task Inline_collection_Contains_with_EF_Constant(bool async) - { - await base.Inline_collection_Contains_with_EF_Constant(async); - - AssertSql( - """ -SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" -FROM "PrimitiveCollectionsEntity" AS p -WHERE p."Id" IN (2, 999, 1000) -"""); - } - public override async Task Inline_collection_Contains_with_all_parameters(bool async) { await base.Inline_collection_Contains_with_all_parameters(async); @@ -397,6 +385,68 @@ WHERE GREATEST(30, p."NullableInt", NULL) = 30 """); } + public override async Task Inline_collection_with_single_parameter_element_Contains(bool async) + { + await base.Inline_collection_with_single_parameter_element_Contains(async); + + AssertSql( + """ +@__i_0='2' + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Id" = @__i_0 +"""); + } + + public override async Task Inline_collection_with_single_parameter_element_Count(bool async) + { + await base.Inline_collection_with_single_parameter_element_Count(async); + + AssertSql( + """ +@__i_0='2' + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM (VALUES (@__i_0::int)) AS v("Value") + WHERE v."Value" > p."Id") = 1 +"""); + } + + public override async Task Inline_collection_Contains_with_EF_Parameter(bool async) + { + await base.Inline_collection_Contains_with_EF_Parameter(async); + + AssertSql( + """ +@__p_0={ '2', '999', '1000' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Id" = ANY (@__p_0) +"""); + } + + public override async Task Inline_collection_Count_with_column_predicate_with_EF_Parameter(bool async) + { + await base.Inline_collection_Count_with_column_predicate_with_EF_Parameter(async); + + AssertSql( + """ +@__p_0={ '2', '999', '1000' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM unnest(@__p_0) AS p0(value) + WHERE p0.value > p."Id") = 2 +"""); + } + public override async Task Parameter_collection_Count(bool async) { await base.Parameter_collection_Count(async); @@ -666,6 +716,48 @@ public override async Task Parameter_collection_null_Contains(bool async) """); } + public override async Task Parameter_collection_Contains_with_EF_Constant(bool async) + { + await base.Parameter_collection_Contains_with_EF_Constant(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Id" IN (2, 999, 1000) +"""); + } + + public override async Task Parameter_collection_Where_with_EF_Constant_Where_Any(bool async) + { + await base.Parameter_collection_Where_with_EF_Constant_Where_Any(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE EXISTS ( + SELECT 1 + FROM (VALUES (2), (999), (1000)) AS i("Value") + WHERE i."Value" > 0) +"""); + } + + public override async Task Parameter_collection_Count_with_column_predicate_with_EF_Constant(bool async) + { + await base.Parameter_collection_Count_with_column_predicate_with_EF_Constant(async); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE ( + SELECT count(*)::int + FROM (VALUES (2), (999), (1000)) AS i("Value") + WHERE i."Value" > p."Id") = 2 +"""); + } + [ConditionalTheory] // #3012 [MinimumPostgresVersion(14, 0)] // Multiranges were introduced in PostgreSQL 14 [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs index c26bcc273..5696d082b 100644 --- a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs @@ -396,11 +396,11 @@ FOREIGN KEY ("ForeignKeyId1", "ForeignKeyId2") REFERENCES "db2"."PrincipalTable" Assert.Equal("db2", sequence.Schema); Assert.Single(dbModel.Tables.Where(t => t.Schema == "db.2" && t.Name == "QuotedTableName")); - Assert.Empty(dbModel.Tables.Where(t => t.Schema == "db.2" && t.Name == "Table.With.Dot")); + Assert.DoesNotContain(dbModel.Tables, t => t.Schema == "db.2" && t.Name == "Table.With.Dot"); Assert.Single(dbModel.Tables.Where(t => t.Schema == "db.2" && t.Name == "SimpleTableName")); Assert.Single(dbModel.Tables.Where(t => t.Schema == "db.2" && t.Name == "JustTableName")); - Assert.Empty(dbModel.Tables.Where(t => t.Schema == "public" && t.Name == "QuotedTableName")); + Assert.DoesNotContain(dbModel.Tables, t => t.Schema == "public" && t.Name == "QuotedTableName"); Assert.Single(dbModel.Tables.Where(t => t.Schema == "public" && t.Name == "Table.With.Dot")); Assert.Single(dbModel.Tables.Where(t => t.Schema == "public" && t.Name == "SimpleTableName")); Assert.Single(dbModel.Tables.Where(t => t.Schema == "public" && t.Name == "JustTableName")); diff --git a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs index 20dc78487..4db135758 100644 --- a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs @@ -1992,37 +1992,37 @@ public override Task Edit_single_property_relational_collection_of_nullable_enum #region Skipped tests because of nested collections outside of JSON (nested arrays not supported in PG) public override Task Edit_single_property_collection_of_collection_of_bool() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_char() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_double() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_int16() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_int32() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_int64() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_single() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_nullable_int32() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_nullable_int32_set_to_null() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_nullable_enum_set_to_null() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); public override Task Edit_single_property_collection_of_collection_of_nullable_enum_with_int_converter() - => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); + => Assert.ThrowsAsync(() => base.Edit_single_property_collection_of_collection_of_bool()); #endregion Skipped tests because of nested collections outside of JSON (nested arrays not supported in PG) From 69fac7b277fd57351abc8d71b6619c04e80fbd5d Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 2 Sep 2024 11:29:21 +0200 Subject: [PATCH 050/107] Run CI tests on PG17 (beta) (#3256) --- .github/workflows/build.yml | 8 ++++---- .../Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 242f8e6f6..3c3dfafe4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,10 +29,10 @@ jobs: - os: ubuntu-22.04 pg_major: 16 config: Debug -# - os: ubuntu-22.04 -# pg_major: 17 -# config: Release -# pg_prerelease: 'PG Prerelease' + - os: ubuntu-22.04 + pg_major: 17 + config: Release + pg_prerelease: 'PG Prerelease' outputs: is_release: ${{ steps.analyze_tag.outputs.is_release }} diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 4be6a4d0c..823a273a1 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -1167,7 +1167,11 @@ private static void GetCollations( var commandText = $""" SELECT nspname, collname, collprovider, collcollate, collctype, - {(connection.PostgreSqlVersion >= new Version(15, 0) ? "colliculocale" : "NULL AS colliculocale")}, + {(connection.PostgreSqlVersion.Major switch { + >= 17 => "colllocale", + >= 15 => "colliculocale AS colllocale", + _ => "NULL AS colllocale" + })}, {(connection.PostgreSqlVersion >= new Version(12, 0) ? "collisdeterministic" : "true AS collisdeterministic")} FROM pg_collation coll JOIN pg_namespace ns ON ns.oid=coll.collnamespace @@ -1183,7 +1187,7 @@ nspname NOT IN ({internalSchemas}) { var schema = reader.GetString(reader.GetOrdinal("nspname")); var name = reader.GetString(reader.GetOrdinal("collname")); - var icuLocale = reader.GetValueOrDefault("colliculocale"); + var icuLocale = reader.GetValueOrDefault("colllocale"); var lcCollate = reader.GetValueOrDefault("collcollate"); var lcCtype = reader.GetValueOrDefault("collctype"); var providerCode = reader.GetChar(reader.GetOrdinal("collprovider")); From 1206e51b1464325ddc23e2b82c0133f15834fb86 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 2 Sep 2024 16:51:14 +0200 Subject: [PATCH 051/107] Various code style/cleanup (#3261) --- ...ologySuiteDataSourceConfigurationPlugin.cs | 2 - ...uiteAggregateMethodCallTranslatorPlugin.cs | 25 +- ...lNetTopologySuiteMemberTranslatorPlugin.cs | 2 +- ...TopologySuiteMethodCallTranslatorPlugin.cs | 12 +- ...TimeAggregateMethodCallTranslatorPlugin.cs | 14 +- .../NpgsqlNodaTimeMemberTranslatorPlugin.cs | 26 +- ...pgsqlNodaTimeMethodCallTranslatorPlugin.cs | 12 +- .../Storage/Internal/IntervalRangeMapping.cs | 3 +- .../Internal/LegacyTimestampInstantMapping.cs | 4 +- .../Storage/Internal/PeriodIntervalMapping.cs | 4 +- .../Storage/Internal/TimeTzMapping.cs | 4 +- .../Internal/TimestampLocalDateTimeMapping.cs | 4 +- .../Internal/TimestampTzInstantMapping.cs | 5 +- .../TimestampTzOffsetDateTimeMapping.cs | 4 +- .../TimestampTzZonedDateTimeMapping.cs | 4 +- .../Extensions/ExpressionVisitorExtensions.cs | 2 - .../NpgsqlPropertyExtensions.cs | 1 - .../Internal/INpgsqlSingletonOptions.cs | 5 +- .../Internal/NpgsqlOptionsExtension.cs | 2 - .../Internal/NpgsqlSingletonOptions.cs | 4 +- .../Conventions/NpgsqlConventionSetBuilder.cs | 1 - .../Internal/NpgsqlHistoryRepository.cs | 14 +- .../NpgsqlMigrationsSqlGenerator.cs | 12 +- .../Properties/NpgsqlStrings.Designer.cs | 4 +- ...qlAggregateMethodCallTranslatorProvider.cs | 7 +- .../Internal/NpgsqlArrayMethodTranslator.cs | 11 +- .../NpgsqlByteArrayMethodTranslator.cs | 13 +- .../NpgsqlDateTimeMemberTranslator.cs | 12 +- .../NpgsqlFullTextSearchMethodTranslator.cs | 30 +- .../NpgsqlJsonDbFunctionsTranslator.cs | 2 +- .../Internal/NpgsqlJsonDomTranslator.cs | 2 +- .../Internal/NpgsqlJsonPocoTranslator.cs | 6 +- .../Internal/NpgsqlLTreeTranslator.cs | 6 +- .../Internal/NpgsqlMathTranslator.cs | 33 +- .../NpgsqlMemberTranslatorProvider.cs | 9 +- .../NpgsqlMethodCallTranslatorProvider.cs | 9 +- .../NpgsqlMiscAggregateMethodTranslator.cs | 21 +- .../NpgsqlObjectToStringTranslator.cs | 10 +- ...pgsqlQueryableAggregateMethodTranslator.cs | 18 +- .../Internal/NpgsqlRangeTranslator.cs | 13 +- ...gsqlStatisticsAggregateMethodTranslator.cs | 6 +- .../Internal/NpgsqlStringMemberTranslator.cs | 2 +- .../Internal/NpgsqlStringMethodTranslator.cs | 50 +- .../NpgsqlTimeSpanMemberTranslator.cs | 4 +- .../Internal/PgUnnestExpression.cs | 2 +- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 1 - ...yableMethodTranslatingExpressionVisitor.cs | 12 +- .../Internal/NpgsqlSqlNullabilityProcessor.cs | 2 +- .../NpgsqlSqlTranslatingExpressionVisitor.cs | 19 +- .../Internal/NpgsqlDatabaseModelFactory.cs | 2 +- .../Mapping/NpgsqlIntervalTypeMapping.cs | 4 +- .../Mapping/NpgsqlTimestampTypeMapping.cs | 4 +- .../Mapping/NpgsqlTimestampTzTypeMapping.cs | 4 +- .../Storage/Internal/NpgsqlDatabaseCreator.cs | 7 +- .../ValueConversion/NpgsqlArrayConverter.cs | 9 +- src/Shared/NonCapturingLazyInitializer.cs | 2 - src/Shared/SharedTypeExtensions.cs | 2 - .../BuiltInDataTypesNpgsqlTest.cs | 25 +- ...FiltersInheritanceBulkUpdatesNpgsqlTest.cs | 14 +- .../TPHInheritanceBulkUpdatesNpgsqlFixture.cs | 4 +- .../ComputedColumnTest.cs | 12 +- .../ConcurrencyDetectorDisabledNpgsqlTest.cs | 8 +- .../ConcurrencyDetectorEnabledNpgsqlTest.cs | 8 +- .../ExecutionStrategyTest.cs | 41 +- .../ExistingConnectionTest.cs | 2 +- .../JsonTypesNpgsqlTest.cs | 1 - .../MaterializationInterceptionNpgsqlTest.cs | 2 - .../Migrations/MigrationsNpgsqlTest.cs | 44 +- .../NpgsqlMigrationsSqlGeneratorTest.cs | 6 +- .../NpgsqlModelBuilderTestBase.cs | 5 +- .../NpgsqlDatabaseCreatorTest.cs | 32 +- .../Query/FuzzyStringMatchQueryNpgsqlTest.cs | 18 +- .../Query/GearsOfWarFromSqlQueryNpgsqlTest.cs | 4 +- .../Query/JsonDomQueryTest.cs | 18 +- .../Query/JsonPocoQueryTest.cs | 12 +- .../Query/JsonStringQueryTest.cs | 86 ++-- .../LegacyNpgsqlNodaTimeTypeMappingTest.cs | 9 +- ...aredPrimitiveCollectionsQueryNpgsqlTest.cs | 1 - .../NorthwindFunctionsQueryNpgsqlTest.cs | 2 +- .../NorthwindMiscellaneousQueryNpgsqlTest.cs | 14 +- .../Query/SpatialQueryNpgsqlGeographyTest.cs | 8 +- .../Query/SqlExecutorNpgsqlTest.cs | 6 +- .../Query/TPHInheritanceQueryNpgsqlFixture.cs | 4 +- .../Query/TrigramsQueryNpgsqlTest.cs | 28 +- .../NpgsqlDatabaseModelFactoryTest.cs | 455 +++++++++++------- .../TestModels/Array/ArrayQueryData.cs | 12 +- .../TestUtilities/NpgsqlDatabaseCleaner.cs | 19 +- .../UpdatesNpgsqlTest.cs | 4 +- .../WithConstructorsNpgsqlTest.cs | 4 +- .../NpgsqlMetadataBuilderExtensionsTest.cs | 5 +- .../NpgsqlRelationalConnectionTest.cs | 1 - .../Scaffolding/NpgsqlCodeGeneratorTest.cs | 8 +- .../Storage/NpgsqlNodaTimeTypeMappingTest.cs | 21 +- .../Storage/NpgsqlTypeMappingSourceTest.cs | 7 +- .../Storage/NpgsqlTypeMappingTest.cs | 37 +- 95 files changed, 796 insertions(+), 695 deletions(-) diff --git a/src/EFCore.PG.NTS/Infrastructure/Internal/NetTopologySuiteDataSourceConfigurationPlugin.cs b/src/EFCore.PG.NTS/Infrastructure/Internal/NetTopologySuiteDataSourceConfigurationPlugin.cs index d258c2889..e8b06a835 100644 --- a/src/EFCore.PG.NTS/Infrastructure/Internal/NetTopologySuiteDataSourceConfigurationPlugin.cs +++ b/src/EFCore.PG.NTS/Infrastructure/Internal/NetTopologySuiteDataSourceConfigurationPlugin.cs @@ -1,5 +1,3 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; - namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; /// diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs index c3a57fffa..86de97978 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs @@ -28,10 +28,10 @@ public NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin( throw new ArgumentException($"Must be an {nameof(NpgsqlSqlExpressionFactory)}", nameof(sqlExpressionFactory)); } - Translators = new IAggregateMethodCallTranslator[] - { + Translators = + [ new NpgsqlNetTopologySuiteAggregateMethodTranslator(npgsqlSqlExpressionFactory, typeMappingSource) - }; + ]; } /// @@ -103,19 +103,18 @@ public NpgsqlNetTopologySuiteAggregateMethodTranslator( // https://postgis.net/docs/ST_ConvexHull.html return _sqlExpressionFactory.Function( "ST_ConvexHull", - new[] - { + [ _sqlExpressionFactory.AggregateFunction( "ST_Collect", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, - argumentsPropagateNullability: new[] { false }, + argumentsPropagateNullability: [false], typeof(Geometry), GetMapping()) - }, + ], nullable: true, - argumentsPropagateNullability: new[] { true }, + argumentsPropagateNullability: [true], typeof(Geometry), GetMapping()); } @@ -127,10 +126,10 @@ public NpgsqlNetTopologySuiteAggregateMethodTranslator( return _sqlExpressionFactory.Convert( _sqlExpressionFactory.AggregateFunction( "ST_Extent", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, - argumentsPropagateNullability: new[] { false }, + argumentsPropagateNullability: [false], typeof(Geometry), GetMapping()), typeof(Geometry), GetMapping()); @@ -140,10 +139,10 @@ public NpgsqlNetTopologySuiteAggregateMethodTranslator( { return _sqlExpressionFactory.AggregateFunction( method == UnionMethod ? "ST_Union" : "ST_Collect", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, - argumentsPropagateNullability: new[] { false }, + argumentsPropagateNullability: [false], typeof(Geometry), GetMapping()); } diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs index 0bbeb6e0c..e9fb5ac9f 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMemberTranslatorPlugin.cs @@ -20,7 +20,7 @@ public NpgsqlNetTopologySuiteMemberTranslatorPlugin( IRelationalTypeMappingSource typeMappingSource, ISqlExpressionFactory sqlExpressionFactory) { - Translators = new IMemberTranslator[] { new NpgsqlGeometryMemberTranslator(sqlExpressionFactory, typeMappingSource), }; + Translators = [new NpgsqlGeometryMemberTranslator(sqlExpressionFactory, typeMappingSource)]; } /// diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs index 0b5afb739..896589719 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs @@ -27,7 +27,7 @@ public NpgsqlNetTopologySuiteMethodCallTranslatorPlugin( throw new ArgumentException($"Must be an {nameof(NpgsqlSqlExpressionFactory)}", nameof(sqlExpressionFactory)); } - Translators = new IMethodCallTranslator[] { new NpgsqlGeometryMethodTranslator(npgsqlSqlExpressionFactory, typeMappingSource), }; + Translators = [new NpgsqlGeometryMethodTranslator(npgsqlSqlExpressionFactory, typeMappingSource)]; } /// @@ -90,7 +90,7 @@ public NpgsqlGeometryMethodTranslator( { nameof(NpgsqlNetTopologySuiteDbFunctionsExtensions.Transform) => _sqlExpressionFactory.Function( "ST_Transform", - new[] { arguments[1], arguments[2] }, + [arguments[1], arguments[2]], nullable: true, argumentsPropagateNullability: TrueArrays[2], method.ReturnType, @@ -98,7 +98,7 @@ public NpgsqlGeometryMethodTranslator( nameof(NpgsqlNetTopologySuiteDbFunctionsExtensions.Force2D) => _sqlExpressionFactory.Function( "ST_Force2D", - new[] { arguments[1] }, + [arguments[1]], nullable: true, TrueArrays[1], method.ReturnType, @@ -110,9 +110,9 @@ public NpgsqlGeometryMethodTranslator( arguments[2]), nameof(NpgsqlNetTopologySuiteDbFunctionsExtensions.Distance) => - TranslateGeometryMethod(arguments[1], method, new[] { arguments[2], arguments[3] }), + TranslateGeometryMethod(arguments[1], method, [arguments[2], arguments[3]]), nameof(NpgsqlNetTopologySuiteDbFunctionsExtensions.IsWithinDistance) => - TranslateGeometryMethod(arguments[1], method, new[] { arguments[2], arguments[3], arguments[4] }), + TranslateGeometryMethod(arguments[1], method, [arguments[2], arguments[3], arguments[4]]), _ => null }; @@ -147,7 +147,7 @@ public NpgsqlGeometryMethodTranslator( { return _sqlExpressionFactory.Function( "ST_GeometryN", - new[] { instance, OneBased(arguments[0]) }, + [instance, OneBased(arguments[0])], nullable: true, argumentsPropagateNullability: TrueArrays[2], method.ReturnType, diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin.cs index f4b7da247..32139bda9 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin.cs @@ -25,10 +25,10 @@ public NpgsqlNodaTimeAggregateMethodCallTranslatorPlugin( throw new ArgumentException($"Must be an {nameof(NpgsqlSqlExpressionFactory)}", nameof(sqlExpressionFactory)); } - Translators = new IAggregateMethodCallTranslator[] - { + Translators = + [ new NpgsqlNodaTimeAggregateMethodTranslator(npgsqlSqlExpressionFactory, typeMappingSource) - }; + ]; } /// @@ -87,19 +87,19 @@ public NpgsqlNodaTimeAggregateMethodTranslator( return method.Name switch { nameof(NpgsqlNodaTimeDbFunctionsExtensions.Sum) => _sqlExpressionFactory.AggregateFunction( - "sum", new[] { sqlExpression }, source, nullable: true, argumentsPropagateNullability: FalseArrays[1], + "sum", [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], returnType: sqlExpression.Type, sqlExpression.TypeMapping), nameof(NpgsqlNodaTimeDbFunctionsExtensions.Average) => _sqlExpressionFactory.AggregateFunction( - "avg", new[] { sqlExpression }, source, nullable: true, argumentsPropagateNullability: FalseArrays[1], + "avg", [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], returnType: sqlExpression.Type, sqlExpression.TypeMapping), nameof(NpgsqlNodaTimeDbFunctionsExtensions.RangeAgg) => _sqlExpressionFactory.AggregateFunction( - "range_agg", new[] { sqlExpression }, source, nullable: true, argumentsPropagateNullability: FalseArrays[1], + "range_agg", [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], returnType: method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType)), nameof(NpgsqlNodaTimeDbFunctionsExtensions.RangeIntersectAgg) => _sqlExpressionFactory.AggregateFunction( - "range_intersect_agg", new[] { sqlExpression }, source, nullable: true, argumentsPropagateNullability: FalseArrays[1], + "range_intersect_agg", [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], returnType: sqlExpression.Type, sqlExpression.TypeMapping), _ => null diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index e96117583..cda280630 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -21,10 +21,10 @@ public NpgsqlNodaTimeMemberTranslatorPlugin( IRelationalTypeMappingSource typeMappingSource, ISqlExpressionFactory sqlExpressionFactory) { - Translators = new IMemberTranslator[] - { - new NpgsqlNodaTimeMemberTranslator(typeMappingSource, (NpgsqlSqlExpressionFactory)sqlExpressionFactory), - }; + Translators = + [ + new NpgsqlNodaTimeMemberTranslator(typeMappingSource, (NpgsqlSqlExpressionFactory)sqlExpressionFactory) + ]; } /// @@ -196,7 +196,7 @@ SqlExpression TranslateDurationTotalMember(SqlExpression instance, double diviso return _sqlExpressionFactory.Not( _sqlExpressionFactory.Function( "lower_inf", - new[] { instance }, + [instance], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(bool))); @@ -207,7 +207,7 @@ SqlExpression TranslateDurationTotalMember(SqlExpression instance, double diviso return _sqlExpressionFactory.Not( _sqlExpressionFactory.Function( "upper_inf", - new[] { instance }, + [instance], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(bool))); @@ -223,7 +223,7 @@ SqlExpression TranslateDurationTotalMember(SqlExpression instance, double diviso SqlExpression Lower() => _sqlExpressionFactory.Function( "lower", - new[] { instance }, + [instance], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(Interval), @@ -232,7 +232,7 @@ SqlExpression Lower() SqlExpression Upper() => _sqlExpressionFactory.Function( "upper", - new[] { instance }, + [instance], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(Interval), @@ -270,7 +270,7 @@ SqlExpression Upper() SqlExpression Lower() => _sqlExpressionFactory.Function( "lower", - new[] { instance }, + [instance], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(LocalDate), @@ -279,7 +279,7 @@ SqlExpression Lower() SqlExpression Upper() => _sqlExpressionFactory.Function( "upper", - new[] { instance }, + [instance], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(LocalDate), @@ -304,7 +304,7 @@ SqlExpression Upper() "DayOfWeek" when GetDatePartExpression(instance, "dow", true) is var getValueExpression => _sqlExpressionFactory.Case( getValueExpression, - new[] { new CaseWhenClause(_sqlExpressionFactory.Constant(0), _sqlExpressionFactory.Constant(7)) }, + [new CaseWhenClause(_sqlExpressionFactory.Constant(0), _sqlExpressionFactory.Constant(7))], getValueExpression), // PG allows converting a timestamp directly to date, truncating the time; but given a timestamptz, it performs a time zone @@ -350,7 +350,7 @@ private SqlExpression GetDatePartExpressionDouble( { var result = _sqlExpressionFactory.Function( "date_part", - new[] { _sqlExpressionFactory.Constant(partName), instance }, + [_sqlExpressionFactory.Constant(partName), instance], nullable: true, argumentsPropagateNullability: TrueArrays[2], typeof(double)); @@ -359,7 +359,7 @@ private SqlExpression GetDatePartExpressionDouble( { result = _sqlExpressionFactory.Function( "floor", - new[] { result }, + [result], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(double)); diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMethodCallTranslatorPlugin.cs index 1a3a9d7c7..2fac8c08e 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMethodCallTranslatorPlugin.cs @@ -23,10 +23,10 @@ public NpgsqlNodaTimeMethodCallTranslatorPlugin( IRelationalTypeMappingSource typeMappingSource, ISqlExpressionFactory sqlExpressionFactory) { - Translators = new IMethodCallTranslator[] - { - new NpgsqlNodaTimeMethodCallTranslator(typeMappingSource, (NpgsqlSqlExpressionFactory)sqlExpressionFactory), - }; + Translators = + [ + new NpgsqlNodaTimeMethodCallTranslator(typeMappingSource, (NpgsqlSqlExpressionFactory)sqlExpressionFactory) + ]; } /// @@ -336,8 +336,8 @@ public NpgsqlNodaTimeMethodCallTranslator( static PgFunctionExpression IntervalPart(string datePart, SqlExpression parameter) => PgFunctionExpression.CreateWithNamedArguments( "make_interval", - new[] { parameter }, - new[] { datePart }, + [parameter], + [datePart], nullable: true, argumentsPropagateNullability: TrueArrays[1], builtIn: true, diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs index 6d0e08d12..e98372ccb 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs @@ -1,7 +1,8 @@ -// ReSharper disable once CheckNamespace + using System.Text; using NodaTime.Text; +// ReSharper disable once CheckNamespace using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; // ReSharper disable once CheckNamespace diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/LegacyTimestampInstantMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/LegacyTimestampInstantMapping.cs index d2b6b7116..c31a179ec 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/LegacyTimestampInstantMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/LegacyTimestampInstantMapping.cs @@ -88,7 +88,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{GenerateLiteralCore(value)}"""; + => $""" + "{GenerateLiteralCore(value)}" + """; private string GenerateLiteralCore(object value) { diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs index bd001d58a..99eba05e1 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs @@ -94,7 +94,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{GenerateLiteralCore(value)}"""; + => $""" + "{GenerateLiteralCore(value)}" + """; private string GenerateLiteralCore(object value) => PeriodPattern.NormalizingIso.Format((Period)value); diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs index 4cf64a6d8..7ffeb3116 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs @@ -111,7 +111,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{Pattern.Format((OffsetTime)value)}"""; + => $""" + "{Pattern.Format((OffsetTime)value)}" + """; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs index d9d069e0a..a3b2b47f5 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs @@ -97,7 +97,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{Format((LocalDateTime)value)}"""; + => $""" + "{Format((LocalDateTime)value)}" + """; private static string Format(LocalDateTime localDateTime) { diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs index 52da44391..bf6f741b2 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs @@ -1,4 +1,3 @@ -using System.Globalization; using System.Text.Json; using Microsoft.EntityFrameworkCore.Storage.Json; using NodaTime.Text; @@ -88,7 +87,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{Format((Instant)value)}"""; + => $""" + "{Format((Instant)value)}" + """; private static string Format(Instant instant) { diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs index 3fba21afe..8d54d819b 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs @@ -97,7 +97,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{Format((OffsetDateTime)value)}"""; + => $""" + "{Format((OffsetDateTime)value)}" + """; private static string Format(OffsetDateTime offsetDateTime) => OffsetDateTimePattern.ExtendedIso.Format(offsetDateTime); diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs index 03456b358..8ed3c499f 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs @@ -93,7 +93,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{Pattern.Format((ZonedDateTime)value)}"""; + => $""" + "{Pattern.Format((ZonedDateTime)value)}" + """; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Extensions/ExpressionVisitorExtensions.cs b/src/EFCore.PG/Extensions/ExpressionVisitorExtensions.cs index 25f9921f5..1007adc98 100644 --- a/src/EFCore.PG/Extensions/ExpressionVisitorExtensions.cs +++ b/src/EFCore.PG/Extensions/ExpressionVisitorExtensions.cs @@ -3,8 +3,6 @@ // ReSharper disable once CheckNamespace namespace System.Linq.Expressions; -#nullable enable - [DebuggerStepThrough] internal static class ExpressionVisitorExtensions { diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs index af88adbf5..ce01fb60e 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs @@ -1,4 +1,3 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal; diff --git a/src/EFCore.PG/Infrastructure/Internal/INpgsqlSingletonOptions.cs b/src/EFCore.PG/Infrastructure/Internal/INpgsqlSingletonOptions.cs index 97910f632..0f08a215f 100644 --- a/src/EFCore.PG/Infrastructure/Internal/INpgsqlSingletonOptions.cs +++ b/src/EFCore.PG/Infrastructure/Internal/INpgsqlSingletonOptions.cs @@ -1,7 +1,4 @@ -using System.Data.Common; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; - -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; /// /// Represents options for Npgsql that can only be set at the singleton level. diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index 60c5cb59a..285ed2989 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -2,8 +2,6 @@ using System.Globalization; using System.Net.Security; using System.Text; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; diff --git a/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs b/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs index 46c9f9a7c..5adba7a0c 100644 --- a/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs +++ b/src/EFCore.PG/Internal/NpgsqlSingletonOptions.cs @@ -1,7 +1,5 @@ -using System.Data.Common; -using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Internal; diff --git a/src/EFCore.PG/Metadata/Conventions/NpgsqlConventionSetBuilder.cs b/src/EFCore.PG/Metadata/Conventions/NpgsqlConventionSetBuilder.cs index 6b296e878..1e1d22ae9 100644 --- a/src/EFCore.PG/Metadata/Conventions/NpgsqlConventionSetBuilder.cs +++ b/src/EFCore.PG/Metadata/Conventions/NpgsqlConventionSetBuilder.cs @@ -1,5 +1,4 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Conventions; diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index 5fadf3eba..78fde2980 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -146,10 +146,12 @@ public override string GetCreateIfNotExistsScript() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override string GetBeginIfNotExistsScript(string migrationId) - => $@" + => $""" + DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM {SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} WHERE ""{MigrationIdColumnName}"" = '{migrationId}') THEN"; + IF NOT EXISTS(SELECT 1 FROM {SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} WHERE "{MigrationIdColumnName}" = '{migrationId}') THEN +"""; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -176,14 +178,6 @@ public override string GetEndIfScript() END $EF$; """; - private RelationalCommandParameterObject CreateRelationalCommandParameters() - => new( - Dependencies.Connection, - null, - null, - Dependencies.CurrentContext.Context, - Dependencies.CommandLogger, CommandSource.Migrations); - private sealed class DummyDisposable : IDisposable, IAsyncDisposable { public void Dispose() diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 0d0969390..800941791 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -84,7 +84,7 @@ void AddSequenceBumpingForSeeding() NpgsqlValueGenerationStrategy.IdentityByDefaultColumn or NpgsqlValueGenerationStrategy.IdentityAlwaysColumn or NpgsqlValueGenerationStrategy.SerialColumn)) - ?? Enumerable.Empty()) + ?? []) .Distinct() .ToArray(); @@ -116,12 +116,14 @@ or NpgsqlValueGenerationStrategy.IdentityAlwaysColumn // e.g. negative values seeded) builder .AppendLine( - @$"{selectOrPerform} setval( + $""" +{selectOrPerform} setval( pg_get_serial_sequence('{table}', '{unquotedColumn}'), GREATEST( (SELECT MAX({column}) FROM {table}) + 1, nextval(pg_get_serial_sequence('{table}', '{unquotedColumn}'))), - false);"); + false); +"""); } builder.EndCommand(); @@ -2194,11 +2196,11 @@ private string ColumnsToTsVector( "json" => string.Join( " || ", columnGroup.Select( c => - $@"json_to_tsvector({tsVectorConfigLiteral}, {JsonColumn(c)}, '""all""')")), + $"""json_to_tsvector({tsVectorConfigLiteral}, {JsonColumn(c)}, '"all"')""")), "jsonb" => string.Join( " || ", columnGroup.Select( c => - $@"jsonb_to_tsvector({tsVectorConfigLiteral}, {JsonColumn(c)}, '""all""')")), + $"""jsonb_to_tsvector({tsVectorConfigLiteral}, {JsonColumn(c)}, '"all"')""")), "null" => throw new InvalidOperationException( $"Column or index {columnOrIndexName} refers to unknown column in tsvector definition"), _ => throw new ArgumentOutOfRangeException() diff --git a/src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs b/src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs index 3bcd31e53..6e39cfd0e 100644 --- a/src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs +++ b/src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs @@ -1,11 +1,11 @@ // +#nullable enable + using System.Resources; using Microsoft.EntityFrameworkCore.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Diagnostics.Internal; -#nullable enable - namespace Npgsql.EntityFrameworkCore.PostgreSQL.Internal { /// diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlAggregateMethodCallTranslatorProvider.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlAggregateMethodCallTranslatorProvider.cs index 27574ff2b..7f8daed18 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlAggregateMethodCallTranslatorProvider.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlAggregateMethodCallTranslatorProvider.cs @@ -23,11 +23,10 @@ public NpgsqlAggregateMethodCallTranslatorProvider( var typeMappingSource = dependencies.RelationalTypeMappingSource; AddTranslators( - new IAggregateMethodCallTranslator[] - { - new NpgsqlQueryableAggregateMethodTranslator(sqlExpressionFactory, typeMappingSource), + [ + new NpgsqlQueryableAggregateMethodTranslator(sqlExpressionFactory, typeMappingSource), new NpgsqlStatisticsAggregateMethodTranslator(sqlExpressionFactory, typeMappingSource), new NpgsqlMiscAggregateMethodTranslator(sqlExpressionFactory, typeMappingSource, model) - }); + ]); } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs index 286c87646..e128dacea 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs @@ -84,7 +84,7 @@ public NpgsqlArrayMethodTranslator(NpgsqlSqlExpressionFactory sqlExpressionFacto { return _sqlExpressionFactory.Function( "get_byte", - new[] { arguments[0], arguments[1] }, + [arguments[0], arguments[1]], nullable: true, argumentsPropagateNullability: TrueArrays[2], typeof(byte)); @@ -159,7 +159,7 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) _sqlExpressionFactory.Subtract( _sqlExpressionFactory.Function( "array_position", - new[] { array, item, startIndex }, + [array, item, startIndex], nullable: true, TrueArrays[3], arrayOrList.Type), @@ -175,7 +175,7 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) return _sqlExpressionFactory.Function( "array_append", - new[] { array, item }, + [array, item], nullable: true, TrueArrays[2], arrayOrList.Type, @@ -188,11 +188,10 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) return _sqlExpressionFactory.Function( "array_cat", - new[] - { + [ _sqlExpressionFactory.ApplyTypeMapping(arrayOrList, inferredMapping), _sqlExpressionFactory.ApplyTypeMapping(arguments[0], inferredMapping) - }, + ], nullable: true, TrueArrays[2], arrayOrList.Type, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs index 3a9dc4a24..4a6a22659 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlByteArrayMethodTranslator.cs @@ -52,16 +52,15 @@ public NpgsqlByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor // We have a byte value, but we need a bytea for PostgreSQL POSITION. var value = arguments[1] is SqlConstantExpression constantValue - ? (SqlExpression)_sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, typeMapping) + ? _sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, typeMapping) // Create bytea from non-constant byte: SELECT set_byte('\x00', 0, 8::smallint); : _sqlExpressionFactory.Function( "set_byte", - new[] - { + [ _sqlExpressionFactory.Constant(new[] { (byte)0 }, typeMapping), _sqlExpressionFactory.Constant(0), arguments[1] - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[3], typeof(byte[]), @@ -70,8 +69,8 @@ public NpgsqlByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor return _sqlExpressionFactory.GreaterThan( PgFunctionExpression.CreateWithArgumentSeparators( "position", - new[] { value, source }, - new[] { "IN" }, // POSITION(x IN y) + [value, source], + ["IN"], // POSITION(x IN y) nullable: true, argumentsPropagateNullability: TrueArrays[2], builtIn: true, @@ -85,7 +84,7 @@ public NpgsqlByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactor return _sqlExpressionFactory.Convert( _sqlExpressionFactory.Function( "get_byte", - new[] { arguments[0], _sqlExpressionFactory.Constant(0) }, + [arguments[0], _sqlExpressionFactory.Constant(0)], nullable: true, argumentsPropagateNullability: TrueArrays[2], typeof(byte)), diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs index 73c2b578f..2cbac35b8 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs @@ -70,7 +70,7 @@ public NpgsqlDateTimeMemberTranslator(IRelationalTypeMappingSource typeMappingSo case { } when NpgsqlTypeMappingSource.LegacyTimestampBehavior: return _sqlExpressionFactory.Function( "date_trunc", - new[] { _sqlExpressionFactory.Constant("day"), instance }, + [_sqlExpressionFactory.Constant("day"), instance], nullable: true, argumentsPropagateNullability: TrueArrays[2], returnType, @@ -79,7 +79,7 @@ public NpgsqlDateTimeMemberTranslator(IRelationalTypeMappingSource typeMappingSo case { TypeMapping: NpgsqlTimestampTzTypeMapping }: return _sqlExpressionFactory.Function( "date_trunc", - new[] { _sqlExpressionFactory.Constant("day"), instance, _sqlExpressionFactory.Constant("UTC") }, + [_sqlExpressionFactory.Constant("day"), instance, _sqlExpressionFactory.Constant("UTC")], nullable: true, argumentsPropagateNullability: TrueArrays[3], returnType, @@ -112,7 +112,7 @@ public NpgsqlDateTimeMemberTranslator(IRelationalTypeMappingSource typeMappingSo nameof(DateTime.Today) => _sqlExpressionFactory.Function( "date_trunc", - new[] { _sqlExpressionFactory.Constant("day"), LocalNow() }, + [_sqlExpressionFactory.Constant("day"), LocalNow()], nullable: false, argumentsPropagateNullability: FalseArrays[2], typeof(DateTime), @@ -172,7 +172,7 @@ SqlExpression LocalNow() var result = _sqlExpressionFactory.Function( "date_part", - new[] { _sqlExpressionFactory.Constant(partName), instance }, + [_sqlExpressionFactory.Constant(partName), instance], nullable: true, argumentsPropagateNullability: TrueArrays[2], typeof(double)); @@ -181,7 +181,7 @@ SqlExpression LocalNow() { result = _sqlExpressionFactory.Function( "floor", - new[] { result }, + [result], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(double)); @@ -217,7 +217,7 @@ SqlExpression LocalNow() nameof(DateTimeOffset.Date) => _sqlExpressionFactory.Function( "date_trunc", - new SqlExpression[] { _sqlExpressionFactory.Constant("day"), _sqlExpressionFactory.AtUtc(instance) }, + [_sqlExpressionFactory.Constant("day"), _sqlExpressionFactory.AtUtc(instance)], nullable: true, argumentsPropagateNullability: TrueArrays[2], typeof(DateTime), diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs index 7dc27b21c..e4db8fba8 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlFullTextSearchMethodTranslator.cs @@ -111,12 +111,11 @@ public NpgsqlFullTextSearchMethodTranslator( 3 => _sqlExpressionFactory.Function( rankFunctionName, - new[] - { + [ arguments[1].Type == typeof(float[]) ? arguments[1] : arguments[0], arguments[1].Type == typeof(float[]) ? arguments[0] : arguments[1], arguments[2] - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[3], typeof(float), @@ -124,7 +123,7 @@ public NpgsqlFullTextSearchMethodTranslator( 4 => _sqlExpressionFactory.Function( rankFunctionName, - new[] { arguments[1], arguments[0], arguments[2], arguments[3] }, + [arguments[1], arguments[0], arguments[2], arguments[3]], nullable: true, argumentsPropagateNullability: TrueArrays[4], method.ReturnType, @@ -181,7 +180,7 @@ public NpgsqlFullTextSearchMethodTranslator( arguments[1].Type == typeof(string) ? _sqlExpressionFactory.Function( "plainto_tsquery", - new[] { arguments[1] }, + [arguments[1]], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(NpgsqlTsQuery), @@ -211,7 +210,7 @@ public NpgsqlFullTextSearchMethodTranslator( nameof(NpgsqlFullTextSearchLinqExtensions.GetResultHeadline) when arguments.Count == 2 => _sqlExpressionFactory.Function( "ts_headline", - new[] { arguments[1], arguments[0] }, + [arguments[1], arguments[0]], nullable: true, argumentsPropagateNullability: TrueArrays[2], method.ReturnType), @@ -219,7 +218,7 @@ public NpgsqlFullTextSearchMethodTranslator( nameof(NpgsqlFullTextSearchLinqExtensions.GetResultHeadline) when arguments.Count == 3 => _sqlExpressionFactory.Function( "ts_headline", - new[] { arguments[1], arguments[0], arguments[2] }, + [arguments[1], arguments[0], arguments[2]], nullable: true, argumentsPropagateNullability: TrueArrays[3], method.ReturnType), @@ -227,8 +226,7 @@ public NpgsqlFullTextSearchMethodTranslator( nameof(NpgsqlFullTextSearchLinqExtensions.GetResultHeadline) when arguments.Count == 4 => _sqlExpressionFactory.Function( "ts_headline", - new[] - { + [ // For the regconfig parameter, if a constant string was provided, just pass it as a string - regconfig-accepting functions // will implicitly cast to regconfig. For (string!) parameters, we add an explicit cast, since regconfig actually is an OID // behind the scenes, and for parameter binary transfer no type coercion occurs. @@ -238,7 +236,7 @@ arguments[1] is SqlConstantExpression constant arguments[2], arguments[0], arguments[3] - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[4], method.ReturnType), @@ -310,8 +308,7 @@ arguments[1] is SqlConstantExpression constant SqlExpression ConfigAccepting(string functionName) => _sqlExpressionFactory.Function( - functionName, new[] - { + functionName, [ // For the regconfig parameter, if a constant string was provided, just pass it as a string - regconfig-accepting functions // will implicitly cast to regconfig. For (string!) parameters, we add an explicit cast, since regconfig actually is an OID // behind the scenes, and for parameter binary transfer no type coercion occurs. @@ -319,7 +316,7 @@ arguments[1] is SqlConstantExpression constant ? _sqlExpressionFactory.ApplyDefaultTypeMapping(constant) : _sqlExpressionFactory.Convert(arguments[1], typeof(string), _regconfigMapping), arguments[2] - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[2], method.ReturnType, @@ -327,8 +324,7 @@ arguments[1] is SqlConstantExpression constant SqlExpression DictionaryAccepting(string functionName) => _sqlExpressionFactory.Function( - functionName, new[] - { + functionName, [ // For the regdictionary parameter, if a constant string was provided, just pass it as a string - regdictionary-accepting functions // will implicitly cast to regdictionary. For (string!) parameters, we add an explicit cast, since regdictionary actually is an OID // behind the scenes, and for parameter binary transfer no type coercion occurs. @@ -336,7 +332,7 @@ arguments[1] is SqlConstantExpression constant ? _sqlExpressionFactory.ApplyDefaultTypeMapping(constant) : _sqlExpressionFactory.Convert(arguments[1], typeof(string), _regdictionaryMapping), arguments[2] - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[2], method.ReturnType, @@ -345,7 +341,7 @@ arguments[1] is SqlConstantExpression constant SqlExpression NonConfigAccepting(string functionName) => _sqlExpressionFactory.Function( functionName, - new[] { arguments[1] }, + [arguments[1]], nullable: true, argumentsPropagateNullability: TrueArrays[1], method.ReturnType, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDbFunctionsTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDbFunctionsTranslator.cs index b1ee9e709..122b3e0c5 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDbFunctionsTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDbFunctionsTranslator.cs @@ -72,7 +72,7 @@ public NpgsqlJsonDbFunctionsTranslator( { return _sqlExpressionFactory.Function( ((NpgsqlJsonTypeMapping)args[0].TypeMapping!).IsJsonb ? "jsonb_typeof" : "json_typeof", - new[] { args[0] }, + [args[0]], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(string)); diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs index c314b5926..7a94102cb 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs @@ -139,7 +139,7 @@ public NpgsqlJsonDomTranslator( { return _sqlExpressionFactory.Function( mapping.IsJsonb ? "jsonb_array_length" : "json_array_length", - new[] { instance }, + [instance], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(int)); diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs index 3a8118599..61a0946c3 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs @@ -105,7 +105,7 @@ public NpgsqlJsonPocoTranslator( => ConvertFromText( _sqlExpressionFactory.JsonTraversal( columnExpression, - new[] { member }, + [member], returnsText: true, typeof(string), _stringTypeMapping), @@ -132,7 +132,7 @@ PgJsonTraversalExpression prevPathTraversal case ColumnExpression { TypeMapping: NpgsqlJsonTypeMapping mapping }: return _sqlExpressionFactory.Function( mapping.IsJsonb ? "jsonb_array_length" : "json_array_length", - new[] { expression }, + [expression], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(int)); @@ -150,7 +150,7 @@ PgJsonTraversalExpression prevPathTraversal var jsonMapping = (NpgsqlJsonTypeMapping)traversal.Expression.TypeMapping!; return _sqlExpressionFactory.Function( jsonMapping.IsJsonb ? "jsonb_array_length" : "json_array_length", - new[] { newTraversal }, + [newTraversal], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(int)); diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLTreeTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLTreeTranslator.cs index 270797f14..4875c2275 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLTreeTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlLTreeTranslator.cs @@ -82,7 +82,7 @@ public NpgsqlLTreeTranslator( nameof(LTree.Subtree) => _sqlExpressionFactory.Function( "subltree", - new[] { instance!, arguments[0], arguments[1] }, + [instance!, arguments[0], arguments[1]], nullable: true, TrueArrays[3], typeof(LTree), @@ -112,7 +112,7 @@ public NpgsqlLTreeTranslator( nameof(LTree.LongestCommonAncestor) => _sqlExpressionFactory.Function( "lca", - new[] { arguments[0] }, + [arguments[0]], nullable: true, TrueArrays[1], typeof(LTree), @@ -139,7 +139,7 @@ public NpgsqlLTreeTranslator( => member.DeclaringType == typeof(LTree) && member.Name == nameof(LTree.NLevel) ? _sqlExpressionFactory.Function( "nlevel", - new[] { instance! }, + [instance!], nullable: true, TrueArrays[1], typeof(int)) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs index 9568ba113..07919734b 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMathTranslator.cs @@ -83,22 +83,22 @@ public class NpgsqlMathTranslator : IMethodCallTranslator { typeof(BigInteger).GetRuntimeMethod(nameof(BigInteger.Min), [typeof(BigInteger), typeof(BigInteger)])!, "LEAST" }, }; - private static readonly IEnumerable TruncateMethodInfos = new[] - { + private static readonly IEnumerable TruncateMethodInfos = + [ typeof(Math).GetRequiredRuntimeMethod(nameof(Math.Truncate), typeof(decimal)), typeof(Math).GetRequiredRuntimeMethod(nameof(Math.Truncate), typeof(double)), typeof(MathF).GetRequiredRuntimeMethod(nameof(MathF.Truncate), typeof(float)) - }; + ]; - private static readonly IEnumerable RoundMethodInfos = new[] - { + private static readonly IEnumerable RoundMethodInfos = + [ typeof(Math).GetRequiredRuntimeMethod(nameof(Math.Round), typeof(decimal)), typeof(Math).GetRequiredRuntimeMethod(nameof(Math.Round), typeof(double)), typeof(MathF).GetRequiredRuntimeMethod(nameof(MathF.Round), typeof(float)) - }; + ]; - private static readonly IEnumerable SignMethodInfos = new[] - { + private static readonly IEnumerable SignMethodInfos = + [ typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(decimal)])!, typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(double)])!, typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(float)])!, @@ -106,8 +106,8 @@ public class NpgsqlMathTranslator : IMethodCallTranslator typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(long)])!, typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(sbyte)])!, typeof(Math).GetRuntimeMethod(nameof(Math.Sign), [typeof(short)])!, - typeof(MathF).GetRuntimeMethod(nameof(MathF.Sign), [typeof(float)])!, - }; + typeof(MathF).GetRuntimeMethod(nameof(MathF.Sign), [typeof(float)])! + ]; private static readonly MethodInfo RoundDecimalTwoParams = typeof(Math).GetRuntimeMethod(nameof(Math.Round), [typeof(decimal), typeof(int)])!; @@ -188,9 +188,9 @@ public NpgsqlMathTranslator( // C# has Round over decimal/double/float only so our argument will be one of those types (compiler puts convert node) // In database result will be same type except for float which returns double which we need to cast back to float. - var result = (SqlExpression)_sqlExpressionFactory.Function( + var result = _sqlExpressionFactory.Function( "trunc", - new[] { argument }, + [argument], nullable: true, argumentsPropagateNullability: TrueArrays[1], argument.Type == typeof(float) ? typeof(double) : argument.Type); @@ -209,9 +209,9 @@ public NpgsqlMathTranslator( // C# has Round over decimal/double/float only so our argument will be one of those types (compiler puts convert node) // In database result will be same type except for float which returns double which we need to cast back to float. - var result = (SqlExpression)_sqlExpressionFactory.Function( + var result = _sqlExpressionFactory.Function( "round", - new[] { argument }, + [argument], nullable: true, argumentsPropagateNullability: TrueArrays[1], argument.Type == typeof(float) ? typeof(double) : argument.Type); @@ -244,11 +244,10 @@ public NpgsqlMathTranslator( { return _sqlExpressionFactory.Function( "round", - new[] - { + [ _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[0]), _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]) - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[2], method.ReturnType, diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs index 18db4afb2..28ab9785a 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs @@ -35,16 +35,15 @@ public NpgsqlMemberTranslatorProvider( JsonPocoTranslator = new NpgsqlJsonPocoTranslator(typeMappingSource, sqlExpressionFactory, model); AddTranslators( - new IMemberTranslator[] - { - new NpgsqlBigIntegerMemberTranslator(sqlExpressionFactory), + [ + new NpgsqlBigIntegerMemberTranslator(sqlExpressionFactory), new NpgsqlDateTimeMemberTranslator(typeMappingSource, sqlExpressionFactory), new NpgsqlJsonDomTranslator(typeMappingSource, sqlExpressionFactory, model), new NpgsqlLTreeTranslator(typeMappingSource, sqlExpressionFactory, model), JsonPocoTranslator, new NpgsqlRangeTranslator(typeMappingSource, sqlExpressionFactory, model, supportsMultiranges), new NpgsqlStringMemberTranslator(sqlExpressionFactory), - new NpgsqlTimeSpanMemberTranslator(sqlExpressionFactory), - }); + new NpgsqlTimeSpanMemberTranslator(sqlExpressionFactory) + ]); } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs index 46a19b4b5..63843eab3 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs @@ -40,9 +40,8 @@ public NpgsqlMethodCallTranslatorProvider( LTreeTranslator = new NpgsqlLTreeTranslator(typeMappingSource, sqlExpressionFactory, model); AddTranslators( - new IMethodCallTranslator[] - { - new NpgsqlArrayMethodTranslator(sqlExpressionFactory, jsonTranslator), + [ + new NpgsqlArrayMethodTranslator(sqlExpressionFactory, jsonTranslator), new NpgsqlByteArrayMethodTranslator(sqlExpressionFactory), new NpgsqlConvertTranslator(sqlExpressionFactory), new NpgsqlDateTimeMethodTranslator(typeMappingSource, sqlExpressionFactory), @@ -62,7 +61,7 @@ public NpgsqlMethodCallTranslatorProvider( new NpgsqlRegexIsMatchTranslator(sqlExpressionFactory), new NpgsqlRowValueTranslator(sqlExpressionFactory), new NpgsqlStringMethodTranslator(typeMappingSource, sqlExpressionFactory), - new NpgsqlTrigramsMethodTranslator(typeMappingSource, sqlExpressionFactory, model), - }); + new NpgsqlTrigramsMethodTranslator(typeMappingSource, sqlExpressionFactory, model) + ]); } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs index 1b972d54f..b938ecb96 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMiscAggregateMethodTranslator.cs @@ -71,11 +71,10 @@ public NpgsqlMiscAggregateMethodTranslator( return _sqlExpressionFactory.Coalesce( _sqlExpressionFactory.AggregateFunction( "string_agg", - new[] - { + [ sqlExpression, method == StringJoin ? arguments[0] : _sqlExpressionFactory.Constant(string.Empty, typeof(string)) - }, + ], source, nullable: true, // string_agg can return nulls regardless of the nullability of its arguments, since if there's an aggregate predicate @@ -93,7 +92,7 @@ public NpgsqlMiscAggregateMethodTranslator( case nameof(NpgsqlAggregateDbFunctionsExtensions.ArrayAgg): return _sqlExpressionFactory.AggregateFunction( "array_agg", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -105,7 +104,7 @@ public NpgsqlMiscAggregateMethodTranslator( case nameof(NpgsqlAggregateDbFunctionsExtensions.JsonAgg): return _sqlExpressionFactory.AggregateFunction( "json_agg", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -115,7 +114,7 @@ public NpgsqlMiscAggregateMethodTranslator( case nameof(NpgsqlAggregateDbFunctionsExtensions.JsonbAgg): return _sqlExpressionFactory.AggregateFunction( "jsonb_agg", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -125,7 +124,7 @@ public NpgsqlMiscAggregateMethodTranslator( case nameof(NpgsqlAggregateDbFunctionsExtensions.Sum): return _sqlExpressionFactory.AggregateFunction( "sum", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -135,7 +134,7 @@ public NpgsqlMiscAggregateMethodTranslator( case nameof(NpgsqlAggregateDbFunctionsExtensions.Average): return _sqlExpressionFactory.AggregateFunction( "avg", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -158,7 +157,7 @@ public NpgsqlMiscAggregateMethodTranslator( return _sqlExpressionFactory.AggregateFunction( isJsonb ? "jsonb_object_agg" : "json_object_agg", - new[] { keys, values }, + [keys, values], source, nullable: true, argumentsPropagateNullability: FalseArrays[2], @@ -176,7 +175,7 @@ public NpgsqlMiscAggregateMethodTranslator( return _sqlExpressionFactory.AggregateFunction( "range_agg", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -186,7 +185,7 @@ public NpgsqlMiscAggregateMethodTranslator( case nameof(NpgsqlRangeDbFunctionsExtensions.RangeIntersectAgg): return _sqlExpressionFactory.AggregateFunction( "range_intersect_agg", - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs index eddefd322..2a98b58ae 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlObjectToStringTranslator.cs @@ -73,25 +73,23 @@ public NpgsqlObjectToStringTranslator(IRelationalTypeMappingSource typeMappingSo { return _sqlExpressionFactory.Case( instance, - new[] - { + [ new CaseWhenClause( _sqlExpressionFactory.Constant(false), _sqlExpressionFactory.Constant(false.ToString())), new CaseWhenClause( _sqlExpressionFactory.Constant(true), _sqlExpressionFactory.Constant(true.ToString())) - }, + ], _sqlExpressionFactory.Constant(string.Empty)); } return _sqlExpressionFactory.Case( - new[] - { + [ new CaseWhenClause( instance, _sqlExpressionFactory.Constant(true.ToString())) - }, + ], _sqlExpressionFactory.Constant(false.ToString())); } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlQueryableAggregateMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlQueryableAggregateMethodTranslator.cs index b1d8fae15..1cffc45fe 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlQueryableAggregateMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlQueryableAggregateMethodTranslator.cs @@ -62,7 +62,7 @@ public NpgsqlQueryableAggregateMethodTranslator( ? _sqlExpressionFactory.Convert( _sqlExpressionFactory.AggregateFunction( "avg", - new[] { averageSqlExpression }, + [averageSqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -71,7 +71,7 @@ public NpgsqlQueryableAggregateMethodTranslator( averageSqlExpression.TypeMapping) : _sqlExpressionFactory.AggregateFunction( "avg", - new[] { averageSqlExpression }, + [averageSqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -86,7 +86,7 @@ public NpgsqlQueryableAggregateMethodTranslator( return _sqlExpressionFactory.Convert( _sqlExpressionFactory.AggregateFunction( "count", - new[] { countSqlExpression }, + [countSqlExpression], source, nullable: false, argumentsPropagateNullability: FalseArrays[1], @@ -100,7 +100,7 @@ public NpgsqlQueryableAggregateMethodTranslator( var longCountSqlExpression = (source.Selector as SqlExpression) ?? _sqlExpressionFactory.Fragment("*"); return _sqlExpressionFactory.AggregateFunction( "count", - new[] { longCountSqlExpression }, + [longCountSqlExpression], source, nullable: false, argumentsPropagateNullability: FalseArrays[1], @@ -112,7 +112,7 @@ public NpgsqlQueryableAggregateMethodTranslator( && source.Selector is SqlExpression maxSqlExpression: return _sqlExpressionFactory.AggregateFunction( "max", - new[] { maxSqlExpression }, + [maxSqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -125,7 +125,7 @@ public NpgsqlQueryableAggregateMethodTranslator( && source.Selector is SqlExpression minSqlExpression: return _sqlExpressionFactory.AggregateFunction( "min", - new[] { minSqlExpression }, + [minSqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -147,7 +147,7 @@ public NpgsqlQueryableAggregateMethodTranslator( return _sqlExpressionFactory.Convert( _sqlExpressionFactory.AggregateFunction( "sum", - new[] { sumSqlExpression }, + [sumSqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -161,7 +161,7 @@ public NpgsqlQueryableAggregateMethodTranslator( return _sqlExpressionFactory.Convert( _sqlExpressionFactory.AggregateFunction( "sum", - new[] { sumSqlExpression }, + [sumSqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -172,7 +172,7 @@ public NpgsqlQueryableAggregateMethodTranslator( return _sqlExpressionFactory.AggregateFunction( "sum", - new[] { sumSqlExpression }, + [sumSqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs index 9e0bb9113..2b6747fdf 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlRangeTranslator.cs @@ -56,7 +56,7 @@ public NpgsqlRangeTranslator( return _sqlExpressionFactory.Not( _sqlExpressionFactory.Function( "isempty", - new[] { arguments[0] }, + [arguments[0]], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(bool))); @@ -76,11 +76,10 @@ public NpgsqlRangeTranslator( return _sqlExpressionFactory.Function( "range_merge", - new[] - { + [ _sqlExpressionFactory.ApplyTypeMapping(arguments[0], inferredMapping), _sqlExpressionFactory.ApplyTypeMapping(arguments[1], inferredMapping) - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[2], method.ReturnType, @@ -95,7 +94,7 @@ public NpgsqlRangeTranslator( return _sqlExpressionFactory.Function( "range_merge", - new[] { arguments[0] }, + [arguments[0]], nullable: true, argumentsPropagateNullability: TrueArrays[1], method.ReturnType, @@ -153,7 +152,7 @@ public NpgsqlRangeTranslator( return _sqlExpressionFactory.Function( member.Name == nameof(NpgsqlRange.LowerBound) ? "lower" : "upper", - new[] { instance }, + [instance], nullable: true, argumentsPropagateNullability: TrueArrays[1], returnType, @@ -174,7 +173,7 @@ public NpgsqlRangeTranslator( SqlExpression SingleArgBoolFunction(string name, SqlExpression argument) => _sqlExpressionFactory.Function( name, - new[] { argument }, + [argument], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(bool)); diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStatisticsAggregateMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStatisticsAggregateMethodTranslator.cs index f902412b5..1bb84be83 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStatisticsAggregateMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStatisticsAggregateMethodTranslator.cs @@ -63,7 +63,7 @@ public NpgsqlStatisticsAggregateMethodTranslator( { return _sqlExpressionFactory.AggregateFunction( functionName, - new[] { sqlExpression }, + [sqlExpression], source, nullable: true, argumentsPropagateNullability: FalseArrays[1], @@ -101,7 +101,7 @@ public NpgsqlStatisticsAggregateMethodTranslator( return method.Name == nameof(NpgsqlAggregateDbFunctionsExtensions.RegrCount) ? _sqlExpressionFactory.AggregateFunction( functionName, - new[] { y, x }, + [y, x], source, nullable: true, argumentsPropagateNullability: FalseArrays[2], @@ -109,7 +109,7 @@ public NpgsqlStatisticsAggregateMethodTranslator( _longTypeMapping) : _sqlExpressionFactory.AggregateFunction( functionName, - new[] { y, x }, + [y, x], source, nullable: true, argumentsPropagateNullability: FalseArrays[2], diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs index d56c7a9d3..a64f931b1 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMemberTranslator.cs @@ -35,7 +35,7 @@ public NpgsqlStringMemberTranslator(ISqlExpressionFactory sqlExpressionFactory) ? _sqlExpressionFactory.Convert( _sqlExpressionFactory.Function( "length", - new[] { instance! }, + [instance!], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(long)), diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs index dbd1c13be..d56f63380 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlStringMethodTranslator.cs @@ -169,7 +169,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I var argument = arguments[0]; return _sqlExpressionFactory.Function( "substr", - new[] { argument, _sqlExpressionFactory.Constant(1), _sqlExpressionFactory.Constant(1) }, + [argument, _sqlExpressionFactory.Constant(1), _sqlExpressionFactory.Constant(1)], nullable: true, argumentsPropagateNullability: TrueArrays[3], method.ReturnType); @@ -180,17 +180,16 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I var argument = arguments[0]; return _sqlExpressionFactory.Function( "substr", - new[] - { + [ argument, _sqlExpressionFactory.Function( "length", - new[] { argument }, + [argument], nullable: true, - argumentsPropagateNullability: new[] { true }, + argumentsPropagateNullability: [true], typeof(int)), _sqlExpressionFactory.Constant(1) - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[3], method.ReturnType); @@ -209,11 +208,10 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I return _sqlExpressionFactory.Subtract( _sqlExpressionFactory.Function( "strpos", - new[] - { + [ _sqlExpressionFactory.ApplyTypeMapping(instance!, stringTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(argument, stringTypeMapping) - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[2], method.ReturnType), @@ -228,12 +226,11 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I return _sqlExpressionFactory.Function( "replace", - new[] - { + [ _sqlExpressionFactory.ApplyTypeMapping(instance!, stringTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(oldValue, stringTypeMapping), _sqlExpressionFactory.ApplyTypeMapping(newValue, stringTypeMapping) - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[3], method.ReturnType, @@ -244,7 +241,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I { return _sqlExpressionFactory.Function( method == ToLower ? "lower" : "upper", - new[] { instance! }, + [instance!], nullable: true, argumentsPropagateNullability: TrueArrays[1], method.ReturnType, @@ -275,7 +272,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I _sqlExpressionFactory.Equal( _sqlExpressionFactory.Function( "btrim", - new[] { argument, _whitespace }, + [argument, _whitespace], nullable: true, argumentsPropagateNullability: TrueArrays[2], argument.Type, @@ -310,13 +307,12 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I return _sqlExpressionFactory.Function( isTrimStart ? "ltrim" : isTrimEnd ? "rtrim" : "btrim", - new[] - { + [ instance!, trimChars is null || trimChars.Length == 0 ? _whitespace : _sqlExpressionFactory.Constant(new string(trimChars)) - }, + ], nullable: true, argumentsPropagateNullability: TrueArrays[2], instance!.Type, @@ -385,7 +381,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I return _sqlExpressionFactory.Function( "array_to_string", - new[] { arguments[1], arguments[0], _sqlExpressionFactory.Constant("") }, + [arguments[1], arguments[0], _sqlExpressionFactory.Constant("")], nullable: true, argumentsPropagateNullability: TrueArrays[3], typeof(string)); @@ -400,7 +396,7 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I { return _sqlExpressionFactory.Function( "reverse", - new[] { arguments[1] }, + [arguments[1]], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(string), @@ -412,9 +408,9 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I // Note that string_to_array always returns text[], regardless of the input type return _sqlExpressionFactory.Function( "string_to_array", - new[] { arguments[1], arguments[2] }, + [arguments[1], arguments[2]], nullable: true, - argumentsPropagateNullability: new[] { true, false }, + argumentsPropagateNullability: [true, false], typeof(string[]), _typeMappingSource.FindMapping(typeof(string[]))); } @@ -424,9 +420,9 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I // Note that string_to_array always returns text[], regardless of the input type return _sqlExpressionFactory.Function( "string_to_array", - new[] { arguments[1], arguments[2], arguments[3] }, + [arguments[1], arguments[2], arguments[3]], nullable: true, - argumentsPropagateNullability: new[] { true, false, false }, + argumentsPropagateNullability: [true, false, false], typeof(string[]), _typeMappingSource.FindMapping(typeof(string[]))); } @@ -435,9 +431,9 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I { return _sqlExpressionFactory.Function( "to_date", - new[] { arguments[1], arguments[2] }, + [arguments[1], arguments[2]], nullable: true, - argumentsPropagateNullability: new[] { true, true }, + argumentsPropagateNullability: [true, true], typeof(DateOnly), _typeMappingSource.FindMapping(typeof(DateOnly)) ); @@ -447,9 +443,9 @@ public NpgsqlStringMethodTranslator(NpgsqlTypeMappingSource typeMappingSource, I { return _sqlExpressionFactory.Function( "to_timestamp", - new[] { arguments[1], arguments[2] }, + [arguments[1], arguments[2]], nullable: true, - argumentsPropagateNullability: new[] { true, true }, + argumentsPropagateNullability: [true, true], typeof(DateTime), _typeMappingSource.FindMapping(typeof(DateTime)) ); diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs index b4b59966b..a191699f2 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlTimeSpanMemberTranslator.cs @@ -68,7 +68,7 @@ SqlExpression Floor(SqlExpression value) => _sqlExpressionFactory.Convert( _sqlExpressionFactory.Function( "floor", - new[] { value }, + [value], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(double)), @@ -76,7 +76,7 @@ SqlExpression Floor(SqlExpression value) SqlExpression DatePart(string part, SqlExpression value) => _sqlExpressionFactory.Function( - "date_part", new[] { _sqlExpressionFactory.Constant(part), value }, + "date_part", [_sqlExpressionFactory.Constant(part), value], nullable: true, argumentsPropagateNullability: FalseTrueArray, returnType); diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs index d55fd4b8d..5d1a6b31a 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgUnnestExpression.cs @@ -59,7 +59,7 @@ public PgUnnestExpression(string alias, SqlExpression array, string columnName, } private PgUnnestExpression(string alias, SqlExpression array, ColumnInfo? columnInfo, bool withOrdinality = true) - : base(alias, "unnest", new[] { array }, columnInfo is null ? null : [columnInfo.Value], withOrdinality) + : base(alias, "unnest", [array], columnInfo is null ? null : [columnInfo.Value], withOrdinality) { } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index a6d4a6871..40bbb33e8 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.NetworkInformation; -using System.Text.Json; using System.Text.RegularExpressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index 2224b09a2..9453c824b 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -125,7 +125,7 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis { // TODO: For geometry collection support (not yet supported), see #2850. selectExpression = new SelectExpression( - [new TableValuedFunctionExpression(tableAlias, "ST_Dump", new[] { sqlExpression })], + [new TableValuedFunctionExpression(tableAlias, "ST_Dump", [sqlExpression])], new ColumnExpression("geom", tableAlias, elementClrType.UnwrapNullableType(), elementTypeMapping, isElementNullable), identifier: [], // TODO _queryCompilationContext.SqlAliasManager); @@ -240,7 +240,7 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr // Construct the json_to_recordset around the JsonScalarExpression, and wrap it in a SelectExpression var jsonToRecordSetExpression = new PgTableValuedFunctionExpression( - tableAlias, functionName, new[] { jsonScalarExpression }, columnInfos, withOrdinality: true); + tableAlias, functionName, [jsonScalarExpression], columnInfos, withOrdinality: true); #pragma warning disable EF1001 // SelectExpression constructors are currently internal var selectExpression = CreateSelect( @@ -367,7 +367,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT _sqlExpressionFactory.GreaterThan( _sqlExpressionFactory.Function( "cardinality", - new[] { array }, + [array], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(int)), @@ -603,7 +603,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT _sqlExpressionFactory.IsNotNull( _sqlExpressionFactory.Function( "array_position", - new[] { array, translatedItem }, + [array, translatedItem], nullable: true, argumentsPropagateNullability: FalseArrays[2], typeof(int)))); @@ -613,7 +613,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT source, _sqlExpressionFactory.Contains( array, - _sqlExpressionFactory.NewArrayOrConstant(new[] { translatedItem }, array.Type, array.TypeMapping))); + _sqlExpressionFactory.NewArrayOrConstant([translatedItem], array.Type, array.TypeMapping))); // For constant arrays (new[] { 1, 2, 3 }) or inline arrays (new[] { 1, param, 3 }), don't do anything PG-specific for since // the general EF Core mechanism is fine for that case: item IN (1, 2, 3). @@ -658,7 +658,7 @@ static IEnumerable GetAllNavigationsInHierarchy(IEntityType entityT { var translation = _sqlExpressionFactory.Function( "cardinality", - new[] { array }, + [array], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(int)); diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs index 215219f19..6424f37e5 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlNullabilityProcessor.cs @@ -288,7 +288,7 @@ protected virtual SqlExpression VisitAny(PgAnyExpression anyExpression, bool all _sqlExpressionFactory.IsNotNull( _sqlExpressionFactory.Function( "array_position", - new[] { array, _sqlExpressionFactory.Constant(null, item.Type, item.TypeMapping) }, + [array, _sqlExpressionFactory.Constant(null, item.Type, item.TypeMapping)], nullable: true, argumentsPropagateNullability: FalseArrays[2], typeof(int))))); diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 9c4029c40..9ce217f02 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -100,7 +100,7 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) { return _sqlExpressionFactory.Function( "length", - new[] { sqlOperand }, + [sqlOperand], nullable: true, argumentsPropagateNullability: TrueArrays[1], typeof(int)); @@ -195,8 +195,8 @@ when binaryExpression.Left.Type.UnwrapNullableType().FullName == "NodaTime.Local return PgFunctionExpression.CreateWithNamedArguments( "make_interval", - new[] { subtraction }, - new[] { "days" }, + [subtraction], + ["days"], nullable: true, argumentsPropagateNullability: TrueArrays[1], builtIn: true, @@ -477,13 +477,12 @@ when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPref translation = _sqlExpressionFactory.Function( methodType is StartsEndsWithContains.StartsWith ? "left" : "right", - new[] - { + [ translatedInstance, _sqlExpressionFactory.Function( - "length", new[] { translatedPattern }, nullable: true, - argumentsPropagateNullability: new[] { true }, typeof(int)) - }, nullable: true, argumentsPropagateNullability: new[] { true, true }, typeof(string), + "length", [translatedPattern], nullable: true, + argumentsPropagateNullability: [true], typeof(int)) + ], nullable: true, argumentsPropagateNullability: [true, true], typeof(string), stringTypeMapping); // LEFT/RIGHT of a citext return a text, so for non-default text mappings we apply an explicit cast. @@ -513,8 +512,8 @@ when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPref _sqlExpressionFactory.IsNotNull(translatedPattern), _sqlExpressionFactory.GreaterThan( _sqlExpressionFactory.Function( - "strpos", new[] { translatedInstance, translatedPattern }, nullable: true, - argumentsPropagateNullability: new[] { true, true }, typeof(int)), + "strpos", [translatedInstance, translatedPattern], nullable: true, + argumentsPropagateNullability: [true, true], typeof(int)), _sqlExpressionFactory.Constant(0)))); break; diff --git a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs index 823a273a1..4af78bfc7 100644 --- a/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs +++ b/src/EFCore.PG/Scaffolding/Internal/NpgsqlDatabaseModelFactory.cs @@ -19,7 +19,7 @@ public class NpgsqlDatabaseModelFactory : DatabaseModelFactory { #region Fields - private const string NamePartRegex = @"(?:(?:""(?(?:(?:"""")|[^""])+)"")|(?[^\.\[""]+))"; + private const string NamePartRegex = """(?:(?:"(?(?:(?:"")|[^"])+)")|(?[^\.\["]+))"""; private static readonly Regex SchemaTableNameExtractor = new( diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs index c5846088d..7bf603944 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlIntervalTypeMapping.cs @@ -79,7 +79,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{FormatTimeSpanAsInterval((TimeSpan)value)}"""; + => $""" + "{FormatTimeSpanAsInterval((TimeSpan)value)}" + """; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs index c1e4ac41f..21e3fccf0 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs @@ -76,7 +76,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => $@"""{GenerateLiteralCore(value)}"""; + => $""" + "{GenerateLiteralCore(value)}" + """; private string GenerateLiteralCore(object value) => FormatDateTime((DateTime)value); diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs index 0cba8cfe3..ef1ace200 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs @@ -84,7 +84,9 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateEmbeddedNonNullSqlLiteral(object value) - => @$"""{Format(value)}"""; + => $""" + "{Format(value)}" + """; private static string Format(object value) => value switch diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs index b4fc8190a..72f8e6c5f 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs @@ -170,16 +170,15 @@ private IReadOnlyList CreateCreateOperations() var designTimeModel = Dependencies.CurrentContext.Context.GetService().Model; return Dependencies.MigrationsSqlGenerator.Generate( - new[] - { - new NpgsqlCreateDatabaseOperation + [ + new NpgsqlCreateDatabaseOperation { Name = _connection.DbConnection.Database, Template = designTimeModel.GetDatabaseTemplate(), Collation = designTimeModel.GetCollation(), Tablespace = designTimeModel.GetTablespace() } - }); + ]); } /// diff --git a/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs b/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs index c27fb5a87..f04221b5a 100644 --- a/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs +++ b/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs @@ -153,9 +153,8 @@ private static Expression> ArrayConversionExpression>( // First, check if the given array value is null and return null immediately if so @@ -213,7 +212,7 @@ private static Expression ForLoop( var initAssign = Assign(loopVar, initValue); var breakLabel = Label("LoopBreak"); var loop = Block( - new[] { loopVar }, + [loopVar], initAssign, Loop( IfThenElse( diff --git a/src/Shared/NonCapturingLazyInitializer.cs b/src/Shared/NonCapturingLazyInitializer.cs index a8d623031..ece9b1944 100644 --- a/src/Shared/NonCapturingLazyInitializer.cs +++ b/src/Shared/NonCapturingLazyInitializer.cs @@ -1,7 +1,5 @@ using System.Diagnostics.CodeAnalysis; -#nullable enable - namespace Microsoft.EntityFrameworkCore.Internal; internal static class NonCapturingLazyInitializer diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index ae06d69f7..6965d22cb 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -1,8 +1,6 @@ using System.Runtime.CompilerServices; using System.Text; -#nullable enable - // ReSharper disable once CheckNamespace namespace System; diff --git a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs index 62ba84946..a873c85b4 100644 --- a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs @@ -1,7 +1,6 @@ using System.Collections.Immutable; using System.ComponentModel.DataAnnotations.Schema; using System.Net.NetworkInformation; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL; @@ -101,8 +100,8 @@ public virtual void Can_query_using_any_mapped_data_type() EnumAsVarchar = StringEnumU16.Value4, PhysicalAddressAsMacaddr = PhysicalAddress.Parse("08-00-2B-01-02-03"), NpgsqlPointAsPoint = new NpgsqlPoint(5.2, 3.3), - StringAsJsonb = @"{""a"": ""b""}", - StringAsJson = @"{""a"": ""b""}", + StringAsJsonb = """{"a": "b"}""", + StringAsJson = """{"a": "b"}""", DictionaryAsHstore = new Dictionary { { "a", "b" } }, ImmutableDictionaryAsHstore = ImmutableDictionary.Empty.Add("c", "d"), NpgsqlRangeAsRange = new NpgsqlRange(4, true, 8, false), @@ -238,7 +237,7 @@ public virtual void Can_query_using_any_mapped_data_type() // Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.Point == param29)); // ReSharper disable once ConvertToConstant.Local - var param30 = @"{""a"": ""b""}"; + var param30 = """{"a": "b"}"""; Assert.Same(entity, context.Set().Single(e => e.Int == 999 && e.StringAsJsonb == param30)); // operator does not exist: json = json (https://stackoverflow.com/questions/32843213/operator-does-not-exist-json-json) @@ -467,7 +466,8 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types() var parameters = DumpParameters(); Assert.Equal( - @"@p0='77' + """ +@p0='77' @p1='True' @p2='80' (DbType = Int16) @p3='0x56' (Nullable = false) @@ -500,8 +500,8 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types() @p28=''a' & 'b'' (Nullable = false) (DbType = Object) @p29=''a' 'b'' (Nullable = false) (DbType = Object) @p30='79' -@p31='{""a"": ""b""}' (Nullable = false) (DbType = Object) -@p32='{""a"": ""b""}' (Nullable = false) (DbType = Object) +@p31='{"a": "b"}' (Nullable = false) (DbType = Object) +@p32='{"a": "b"}' (Nullable = false) (DbType = Object) @p33='Gumball Rules!' (Nullable = false) @p34='Gumball Rules OK' (Nullable = false) @p35='11:15:12' (DbType = Object) @@ -511,7 +511,8 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types() @p39='4294967295' @p40='-1' @p41='2147483648' (DbType = Object) -@p42='-1'", +@p42='-1' +""", parameters, ignoreLineEndingDifferences: true); } @@ -564,8 +565,8 @@ private static void AssertMappedDataTypes(MappedDataTypes entity, int id) Assert.Equal(PhysicalAddress.Parse("08-00-2B-01-02-03"), entity.PhysicalAddressAsMacaddr); Assert.Equal(new NpgsqlPoint(5.2, 3.3), entity.NpgsqlPointAsPoint); - Assert.Equal(@"{""a"": ""b""}", entity.StringAsJsonb); - Assert.Equal(@"{""a"": ""b""}", entity.StringAsJson); + Assert.Equal("""{"a": "b"}""", entity.StringAsJsonb); + Assert.Equal("""{"a": "b"}""", entity.StringAsJson); Assert.Equal(new Dictionary { { "a", "b" } }, entity.DictionaryAsHstore); Assert.Equal(new NpgsqlRange(4, true, 8, false), entity.NpgsqlRangeAsRange); @@ -619,8 +620,8 @@ private static MappedDataTypes CreateMappedDataTypes(int id) EnumAsVarchar = StringEnumU16.Value4, PhysicalAddressAsMacaddr = PhysicalAddress.Parse("08-00-2B-01-02-03"), NpgsqlPointAsPoint = new NpgsqlPoint(5.2, 3.3), - StringAsJsonb = @"{""a"": ""b""}", - StringAsJson = @"{""a"": ""b""}", + StringAsJsonb = """{"a": "b"}""", + StringAsJson = """{"a": "b"}""", DictionaryAsHstore = new Dictionary { { "a", "b" } }, NpgsqlRangeAsRange = new NpgsqlRange(4, true, 8, false), IntArrayAsIntArray = [2, 3], diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs index ecab1b210..4b8c44424 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHFiltersInheritanceBulkUpdatesNpgsqlTest.cs @@ -2,16 +2,12 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.BulkUpdates; -public class TPHFiltersInheritanceBulkUpdatesNpgsqlTest : FiltersInheritanceBulkUpdatesRelationalTestBase< - TPHFiltersInheritanceBulkUpdatesNpgsqlFixture> +public class TPHFiltersInheritanceBulkUpdatesNpgsqlTest( + TPHFiltersInheritanceBulkUpdatesNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : FiltersInheritanceBulkUpdatesRelationalTestBase< + TPHFiltersInheritanceBulkUpdatesNpgsqlFixture>(fixture, testOutputHelper) { - public TPHFiltersInheritanceBulkUpdatesNpgsqlTest( - TPHFiltersInheritanceBulkUpdatesNpgsqlFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture, testOutputHelper) - { - } - public override async Task Delete_where_hierarchy(bool async) { await base.Delete_where_hierarchy(async); diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlFixture.cs index dd076dd8f..dee6e67aa 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/TPHInheritanceBulkUpdatesNpgsqlFixture.cs @@ -13,6 +13,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con { base.OnModelCreating(modelBuilder, context); - modelBuilder.Entity().HasNoKey().ToSqlQuery(@"SELECT * FROM ""Animals"""); + modelBuilder.Entity().HasNoKey().ToSqlQuery(""" + SELECT * FROM "Animals" + """); } } diff --git a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs index 2cc8f767c..0e1b399c3 100644 --- a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs +++ b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs @@ -63,11 +63,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .Property(e => e.P4) - .HasComputedColumnSql(@"""P1"" + ""P2""", stored: true); + .HasComputedColumnSql(""" + "P1" + "P2" + """, stored: true); modelBuilder.Entity() .Property(e => e.P5) - .HasComputedColumnSql(@"""P1"" + ""P3""", stored: true); + .HasComputedColumnSql(""" + "P1" + "P3" + """, stored: true); } } @@ -109,7 +113,9 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity() .Property(entity => entity.CalculatedFlagEnum) - .HasComputedColumnSql(@"""FlagEnum"" | ""OptionalFlagEnum""", stored: true); + .HasComputedColumnSql(""" + "FlagEnum" | "OptionalFlagEnum" + """, stored: true); } [ConditionalFact] diff --git a/test/EFCore.PG.FunctionalTests/ConcurrencyDetectorDisabledNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConcurrencyDetectorDisabledNpgsqlTest.cs index d8c24046a..d31212647 100644 --- a/test/EFCore.PG.FunctionalTests/ConcurrencyDetectorDisabledNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConcurrencyDetectorDisabledNpgsqlTest.cs @@ -14,8 +14,12 @@ public ConcurrencyDetectorDisabledNpgsqlTest(ConcurrencyDetectorNpgsqlFixture fi public override Task FromSql(bool async) => ConcurrencyDetectorTest( async c => async - ? await c.Products.FromSqlRaw(@"select * from ""Products""").ToListAsync() - : c.Products.FromSqlRaw(@"select * from ""Products""").ToList()); + ? await c.Products.FromSqlRaw(""" + select * from "Products" + """).ToListAsync() + : c.Products.FromSqlRaw(""" + select * from "Products" + """).ToList()); protected override async Task ConcurrencyDetectorTest(Func> test) { diff --git a/test/EFCore.PG.FunctionalTests/ConcurrencyDetectorEnabledNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/ConcurrencyDetectorEnabledNpgsqlTest.cs index 8a0f4d5ff..6cb940edd 100644 --- a/test/EFCore.PG.FunctionalTests/ConcurrencyDetectorEnabledNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConcurrencyDetectorEnabledNpgsqlTest.cs @@ -14,8 +14,12 @@ public ConcurrencyDetectorEnabledNpgsqlTest(ConcurrencyDetectorNpgsqlFixture fix public override Task FromSql(bool async) => ConcurrencyDetectorTest( async c => async - ? await c.Products.FromSqlRaw(@"select * from ""Products""").ToListAsync() - : c.Products.FromSqlRaw(@"select * from ""Products""").ToList()); + ? await c.Products.FromSqlRaw(""" + select * from "Products" + """).ToListAsync() + : c.Products.FromSqlRaw(""" + select * from "Products" + """).ToList()); protected override async Task ConcurrencyDetectorTest(Func> test) { diff --git a/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs b/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs index 640b3c356..f488081c6 100644 --- a/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs +++ b/test/EFCore.PG.FunctionalTests/ExecutionStrategyTest.cs @@ -80,7 +80,7 @@ private void Test_commit_failure(bool realFailure, Action(); - connection.CommitFailures.Enqueue(new bool?[] { realFailure }); + connection.CommitFailures.Enqueue([realFailure]); Fixture.TestSqlLoggerFactory.Clear(); context.Products.Add(new Product()); @@ -194,7 +194,7 @@ private async Task Test_commit_failure_async( { var connection = (TestNpgsqlConnection)context.GetService(); - connection.CommitFailures.Enqueue(new bool?[] { realFailure }); + connection.CommitFailures.Enqueue([realFailure]); Fixture.TestSqlLoggerFactory.Clear(); context.Products.Add(new Product()); @@ -236,7 +236,7 @@ public void Handles_commit_failure_multiple_SaveChanges(bool realFailure) using (var context2 = CreateContext()) { - connection.CommitFailures.Enqueue(new bool?[] { realFailure }); + connection.CommitFailures.Enqueue([realFailure]); context1.Products.Add(new Product()); context2.Products.Add(new Product()); @@ -276,7 +276,7 @@ public async Task Retries_SaveChanges_on_execution_failure( { var connection = (TestNpgsqlConnection)context.GetService(); - connection.ExecutionFailures.Enqueue(new bool?[] { null, realFailure }); + connection.ExecutionFailures.Enqueue([null, realFailure]); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); @@ -385,7 +385,7 @@ public async Task Retries_query_on_execution_failure(bool externalStrategy, bool { var connection = (TestNpgsqlConnection)context.GetService(); - connection.ExecutionFailures.Enqueue(new bool?[] { true }); + connection.ExecutionFailures.Enqueue([true]); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); @@ -441,7 +441,7 @@ public async Task Retries_FromSqlRaw_on_execution_failure(bool externalStrategy, { var connection = (TestNpgsqlConnection)context.GetService(); - connection.ExecutionFailures.Enqueue(new bool?[] { true }); + connection.ExecutionFailures.Enqueue([true]); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); @@ -453,14 +453,16 @@ public async Task Retries_FromSqlRaw_on_execution_failure(bool externalStrategy, list = await new TestNpgsqlRetryingExecutionStrategy(context) .ExecuteAsync( context, (c, ct) => c.Set().FromSqlRaw( - @"SELECT ""Id"", ""Name"" - FROM ""Products""").ToListAsync(ct), null); + """ + SELECT "Id", "Name" FROM "Products" + """).ToListAsync(ct), null); } else { list = await context.Set().FromSqlRaw( - @"SELECT ""Id"", ""Name"" - FROM ""Products""").ToListAsync(); + """ + SELECT "Id", "Name" FROM "Products" + """).ToListAsync(); } } else @@ -470,14 +472,17 @@ public async Task Retries_FromSqlRaw_on_execution_failure(bool externalStrategy, list = new TestNpgsqlRetryingExecutionStrategy(context) .Execute( context, c => c.Set().FromSqlRaw( - @"SELECT ""Id"", ""Name"" - FROM ""Products""").ToList(), null); + """ + SELECT "Id", "Name" + FROM "Products" + """).ToList(), null); } else { list = context.Set().FromSqlRaw( - @"SELECT ""Id"", ""Name"" - FROM ""Products""").ToList(); + """ + SELECT "Id", "Name" FROM "Products" + """).ToList(); } } @@ -496,7 +501,7 @@ public async Task Retries_OpenConnection_on_execution_failure(bool externalStrat await using var context = CreateContext(); var connection = (TestNpgsqlConnection)context.GetService(); - connection.OpenFailures.Enqueue(new bool?[] { true }); + connection.OpenFailures.Enqueue([true]); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); @@ -549,7 +554,7 @@ public async Task Retries_BeginTransaction_on_execution_failure(bool async) await using var context = CreateContext(); var connection = (TestNpgsqlConnection)context.GetService(); - connection.OpenFailures.Enqueue(new bool?[] { true }); + connection.OpenFailures.Enqueue([true]); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); @@ -584,8 +589,8 @@ public void Verification_is_retried_using_same_retry_limit() { var connection = (TestNpgsqlConnection)context.GetService(); - connection.ExecutionFailures.Enqueue(new bool?[] { true, null, true, true }); - connection.CommitFailures.Enqueue(new bool?[] { true, true, true, true }); + connection.ExecutionFailures.Enqueue([true, null, true, true]); + connection.CommitFailures.Enqueue([true, true, true, true]); context.Products.Add(new Product()); Assert.Throws( diff --git a/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs b/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs index f155b67ba..abbe8f3f5 100644 --- a/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs +++ b/test/EFCore.PG.FunctionalTests/ExistingConnectionTest.cs @@ -85,7 +85,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b => { b.HasKey(c => c.CustomerId); - RelationalEntityTypeBuilderExtensions.ToTable((EntityTypeBuilder)b, "Customers"); + ((EntityTypeBuilder)b).ToTable("Customers"); }); } diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index 6806f8fa0..575433a57 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -1,7 +1,6 @@ #nullable enable using System.Collections; - using System.Globalization; using System.Numerics; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; diff --git a/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs index 31735e9ff..1e44c27db 100644 --- a/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/MaterializationInterceptionNpgsqlTest.cs @@ -1,7 +1,5 @@ #nullable enable -using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL; diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 1f11df6bf..38743f56e 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -62,7 +62,7 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'dbo2') THEN COMMENT ON COLUMN dbo2."People"."EmployerId" IS 'Employer ID comment'; """, // - @"CREATE INDEX ""IX_People_EmployerId"" ON dbo2.""People"" (""EmployerId"");"); + """CREATE INDEX "IX_People_EmployerId" ON dbo2."People" ("EmployerId");"""); } public override async Task Create_table_no_key() @@ -531,7 +531,7 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'TestTableSchema') THEN END $EF$; """, // - @"ALTER TABLE ""TestTable"" SET SCHEMA ""TestTableSchema"";"); + """ALTER TABLE "TestTable" SET SCHEMA "TestTableSchema";"""); } #endregion @@ -1194,7 +1194,9 @@ await Test( e.Property("Y"); }), builder => builder.Entity("People").Property("Sum") - .HasComputedColumnSql(@"""X"" + ""Y""", stored: true), + .HasComputedColumnSql(""" + "X" + "Y" + """, stored: true), builder => builder.Entity("People").Property("Sum"), model => { @@ -1740,7 +1742,9 @@ await Test( "People", b => { b.Property("Name"); - b.Property("Name2").HasComputedColumnSql(@"""Name""", stored: true); + b.Property("Name2").HasComputedColumnSql(""" + "Name" + """, stored: true); }), _ => { }, builder => builder.Entity("People").Property("Name2") @@ -1748,7 +1752,9 @@ await Test( model => { var computedColumn = Assert.Single(Assert.Single(model.Tables).Columns, c => c.Name == "Name2"); - Assert.Equal(@"""Name""", computedColumn.ComputedColumnSql); + Assert.Equal(""" + "Name" + """, computedColumn.ComputedColumnSql); Assert.Equal(NonDefaultCollation, computedColumn.Collation); }); @@ -2031,12 +2037,14 @@ await Test( _ => { }, builder => builder.Entity("People").HasIndex("Name") .IncludeProperties("FirstName", "LastName") - .HasFilter(@"""Name"" IS NOT NULL"), + .HasFilter(""" + "Name" IS NOT NULL + """), model => { var table = Assert.Single(model.Tables); var index = Assert.Single(table.Indexes); - Assert.Equal(@"(""Name"" IS NOT NULL)", index.Filter); + Assert.Equal("""("Name" IS NOT NULL)""", index.Filter); Assert.Single(index.Columns); Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); @@ -2122,13 +2130,15 @@ await Test( builder => builder.Entity("People").HasIndex("Name") .IsUnique() .IncludeProperties("FirstName", "LastName") - .HasFilter(@"""Name"" IS NOT NULL"), + .HasFilter(""" + "Name" IS NOT NULL + """), model => { var table = Assert.Single(model.Tables); var index = Assert.Single(table.Indexes); Assert.True(index.IsUnique); - Assert.Equal(@"(""Name"" IS NOT NULL)", index.Filter); + Assert.Equal("""("Name" IS NOT NULL)""", index.Filter); Assert.Single(index.Columns); Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); @@ -2858,12 +2868,14 @@ await Test( }); AssertSql( - @"DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'some_schema') THEN - CREATE SCHEMA some_schema; - END IF; -END $EF$;", + """ + DO $EF$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'some_schema') THEN + CREATE SCHEMA some_schema; + END IF; + END $EF$; + """, // @"CREATE EXTENSION IF NOT EXISTS citext SCHEMA some_schema;"); } @@ -3176,7 +3188,7 @@ await Test( var column = Assert.Single(table.Columns, c => c.Name == "TsVector"); Assert.Equal("tsvector", column.StoreType); Assert.Equal( - @"to_tsvector('english'::regconfig, ((""Title"" || ' '::text) || COALESCE(""Description"", ''::text)))", + """to_tsvector('english'::regconfig, (("Title" || ' '::text) || COALESCE("Description", ''::text)))""", column.ComputedColumnSql); }); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs index ac5fec041..0b3dc8292 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/NpgsqlMigrationsSqlGeneratorTest.cs @@ -23,8 +23,10 @@ public virtual void CreateDatabaseOperation() Generate(new NpgsqlCreateDatabaseOperation { Name = "Northwind" }); AssertSql( - @"CREATE DATABASE ""Northwind""; -"); + """ +CREATE DATABASE "Northwind"; + +"""); } [ConditionalFact] diff --git a/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderTestBase.cs b/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderTestBase.cs index 95f42a658..afce39d7a 100644 --- a/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderTestBase.cs +++ b/test/EFCore.PG.FunctionalTests/ModelBuilding/NpgsqlModelBuilderTestBase.cs @@ -1,9 +1,8 @@ +#nullable enable + using Microsoft.EntityFrameworkCore.ModelBuilding; -using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; -#nullable enable - namespace Npgsql.EntityFrameworkCore.PostgreSQL.ModelBuilding; public class NpgsqlModelBuilderTestBase : RelationalModelBuilderTest diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs index 2c7bc8061..edf3087bd 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs @@ -452,37 +452,37 @@ public void GenerateCreateScript_works() using var context = new BloggingContext("Data Source=foo"); var script = context.Database.GenerateCreateScript(); Assert.Equal( - @"CREATE TABLE ""Blogs"" (" + """CREATE TABLE "Blogs" (""" + _eol - + @" ""Key1"" text NOT NULL," + + """ "Key1" text NOT NULL,""" + _eol - + @" ""Key2"" bytea NOT NULL," + + """ "Key2" bytea NOT NULL,""" + _eol - + @" ""Cheese"" text," + + """ "Cheese" text,""" + _eol - + @" ""ErMilan"" integer NOT NULL," + + """ "ErMilan" integer NOT NULL,""" + _eol - + @" ""George"" boolean NOT NULL," + + """ "George" boolean NOT NULL,""" + _eol - + @" ""TheGu"" uuid NOT NULL," + + """ "TheGu" uuid NOT NULL,""" + _eol - + @" ""NotFigTime"" timestamp with time zone NOT NULL," + + """ "NotFigTime" timestamp with time zone NOT NULL,""" + _eol - + @" ""ToEat"" smallint NOT NULL," + + """ "ToEat" smallint NOT NULL,""" + _eol - + @" ""OrNothing"" double precision NOT NULL," + + """ "OrNothing" double precision NOT NULL,""" + _eol - + @" ""Fuse"" smallint NOT NULL," + + """ "Fuse" smallint NOT NULL,""" + _eol - + @" ""WayRound"" bigint NOT NULL," + + """ "WayRound" bigint NOT NULL,""" + _eol - + @" ""On"" real NOT NULL," + + """ "On" real NOT NULL,""" + _eol - + @" ""AndChew"" bytea," + + """ "AndChew" bytea,""" + _eol - + @" ""AndRow"" bytea," + + """ "AndRow" bytea,""" + _eol - + @" CONSTRAINT ""PK_Blogs"" PRIMARY KEY (""Key1"", ""Key2"")" + + """ CONSTRAINT "PK_Blogs" PRIMARY KEY ("Key1", "Key2")""" + _eol + ");" + _eol diff --git a/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs index bf18e9670..fd76749d3 100644 --- a/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/FuzzyStringMatchQueryNpgsqlTest.cs @@ -31,7 +31,7 @@ public void FuzzyStringMatchSoundex() .Select(x => EF.Functions.FuzzyStringMatchSoundex(x.Text)) .ToArray(); - AssertContainsSql(@"soundex(f.""Text"")"); + AssertContainsSql("""soundex(f."Text")"""); } [Fact] @@ -42,7 +42,7 @@ public void FuzzyStringMatchDifference() .Select(x => EF.Functions.FuzzyStringMatchDifference(x.Text, "target")) .ToArray(); - AssertContainsSql(@"difference(f.""Text"", 'target')"); + AssertContainsSql("""difference(f."Text", 'target')"""); } [Fact] @@ -53,7 +53,7 @@ public void FuzzyStringMatchLevenshtein() .Select(x => EF.Functions.FuzzyStringMatchLevenshtein(x.Text, "target")) .ToArray(); - AssertContainsSql(@"levenshtein(f.""Text"", 'target')"); + AssertContainsSql("""levenshtein(f."Text", 'target')"""); } [Fact] @@ -64,7 +64,7 @@ public void FuzzyStringMatchLevenshtein_With_Costs() .Select(x => EF.Functions.FuzzyStringMatchLevenshtein(x.Text, "target", 1, 2, 3)) .ToArray(); - AssertContainsSql(@"levenshtein(f.""Text"", 'target', 1, 2, 3)"); + AssertContainsSql("""levenshtein(f."Text", 'target', 1, 2, 3)"""); } [Fact] @@ -75,7 +75,7 @@ public void FuzzyStringMatchLevenshteinLessEqual() .Select(x => EF.Functions.FuzzyStringMatchLevenshteinLessEqual(x.Text, "target", 5)) .ToArray(); - AssertContainsSql(@"levenshtein_less_equal(f.""Text"", 'target', 5)"); + AssertContainsSql("""levenshtein_less_equal(f."Text", 'target', 5)"""); } [Fact] @@ -86,7 +86,7 @@ public void FuzzyStringMatchLevenshteinLessEqual_With_Costs() .Select(x => EF.Functions.FuzzyStringMatchLevenshteinLessEqual(x.Text, "target", 1, 2, 3, 5)) .ToArray(); - AssertContainsSql(@"levenshtein_less_equal(f.""Text"", 'target', 1, 2, 3, 5)"); + AssertContainsSql("""levenshtein_less_equal(f."Text", 'target', 1, 2, 3, 5)"""); } [Fact] @@ -97,7 +97,7 @@ public void FuzzyStringMatchMetaphone() .Select(x => EF.Functions.FuzzyStringMatchMetaphone(x.Text, 6)) .ToArray(); - AssertContainsSql(@"metaphone(f.""Text"", 6)"); + AssertContainsSql("""metaphone(f."Text", 6)"""); } [Fact] @@ -108,7 +108,7 @@ public void FuzzyStringMatchDoubleMetaphone() .Select(x => EF.Functions.FuzzyStringMatchDoubleMetaphone(x.Text)) .ToArray(); - AssertContainsSql(@"dmetaphone(f.""Text"")"); + AssertContainsSql("""dmetaphone(f."Text")"""); } [Fact] @@ -119,7 +119,7 @@ public void FuzzyStringMatchDoubleMetaphoneAlt() .Select(x => EF.Functions.FuzzyStringMatchDoubleMetaphoneAlt(x.Text)) .ToArray(); - AssertContainsSql(@"dmetaphone_alt(f.""Text"")"); + AssertContainsSql("""dmetaphone_alt(f."Text")"""); } #endregion diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarFromSqlQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarFromSqlQueryNpgsqlTest.cs index 59e8a92b2..b8a140c27 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarFromSqlQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarFromSqlQueryNpgsqlTest.cs @@ -14,7 +14,9 @@ public override void From_sql_queryable_simple_columns_out_of_order() base.From_sql_queryable_simple_columns_out_of_order(); Assert.Equal( - @"SELECT ""Id"", ""Name"", ""IsAutomatic"", ""AmmunitionType"", ""OwnerFullName"", ""SynergyWithId"" FROM ""Weapons"" ORDER BY ""Name""", + """ +SELECT "Id", "Name", "IsAutomatic", "AmmunitionType", "OwnerFullName", "SynergyWithId" FROM "Weapons" ORDER BY "Name" +""", Sql); } diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs index 3e26de810..a46fd3c96 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonDomQueryTest.cs @@ -55,8 +55,10 @@ public void Literal_document() ctx.JsonbEntities.Where( e => e.CustomerDocument == JsonDocument.Parse( - @" -{ ""Name"": ""Test customer"", ""Age"": 80 }", default))); + """ + + { "Name": "Test customer", "Age": 80 } + """, default))); AssertSql( """ @@ -428,7 +430,7 @@ LIMIT 2 public void JsonContains_with_json_element() { using var ctx = CreateContext(); - var element = JsonDocument.Parse(@"{""Name"": ""Joe"", ""Age"": 25}").RootElement; + var element = JsonDocument.Parse("""{"Name": "Joe", "Age": 25}""").RootElement; var count = ctx.JsonbEntities.Count( e => EF.Functions.JsonContains(e.CustomerElement, element)); @@ -450,7 +452,7 @@ public void JsonContains_with_string() using var ctx = CreateContext(); var count = ctx.JsonbEntities.Count( e => - EF.Functions.JsonContains(e.CustomerElement, @"{""Name"": ""Joe"", ""Age"": 25}")); + EF.Functions.JsonContains(e.CustomerElement, """{"Name": "Joe", "Age": 25}""")); Assert.Equal(1, count); AssertSql( @@ -465,7 +467,7 @@ SELECT count(*)::int public void JsonContained_with_json_element() { using var ctx = CreateContext(); - var element = JsonDocument.Parse(@"{""Name"": ""Joe"", ""Age"": 25}").RootElement; + var element = JsonDocument.Parse("""{"Name": "Joe", "Age": 25}""").RootElement; var count = ctx.JsonbEntities.Count( e => EF.Functions.JsonContained(element, e.CustomerElement)); @@ -487,7 +489,7 @@ public void JsonContained_with_string() using var ctx = CreateContext(); var count = ctx.JsonbEntities.Count( e => - EF.Functions.JsonContained(@"{""Name"": ""Joe"", ""Age"": 25}", e.CustomerElement)); + EF.Functions.JsonContained("""{"Name": "Joe", "Age": 25}""", e.CustomerElement)); // Assert.Equal(1, count); AssertSql( @@ -732,7 +734,9 @@ static JsonDocument CreateCustomer2() """); static JsonDocument CreateCustomer3() - => JsonDocument.Parse(@"""foo"""); + => JsonDocument.Parse(""" + "foo" + """); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs index 55668f31f..bb108fc6e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs @@ -499,7 +499,7 @@ LIMIT 2 public void JsonContains_with_json_element() { using var ctx = CreateContext(); - var element = JsonDocument.Parse(@"{""Name"": ""Joe"", ""Age"": 25}").RootElement; + var element = JsonDocument.Parse("""{"Name": "Joe", "Age": 25}""").RootElement; var count = ctx.JsonbEntities.Count( e => EF.Functions.JsonContains(e.Customer, element)); @@ -521,7 +521,7 @@ public void JsonContains_with_string_literal() using var ctx = CreateContext(); var count = ctx.JsonbEntities.Count( e => - EF.Functions.JsonContains(e.Customer, @"{""Name"": ""Joe"", ""Age"": 25}")); + EF.Functions.JsonContains(e.Customer, """{"Name": "Joe", "Age": 25}""")); Assert.Equal(1, count); AssertSql( @@ -536,7 +536,7 @@ SELECT count(*)::int public void JsonContains_with_string_parameter() { using var ctx = CreateContext(); - var someJson = @"{""Name"": ""Joe"", ""Age"": 25}"; + var someJson = """{"Name": "Joe", "Age": 25}"""; var count = ctx.JsonbEntities.Count( e => EF.Functions.JsonContains(e.Customer, someJson)); @@ -556,7 +556,7 @@ WHERE j."Customer" @> @__someJson_1 public void JsonContained_with_json_element() { using var ctx = CreateContext(); - var element = JsonDocument.Parse(@"{""Name"": ""Joe"", ""Age"": 25}").RootElement; + var element = JsonDocument.Parse("""{"Name": "Joe", "Age": 25}""").RootElement; var count = ctx.JsonbEntities.Count( e => EF.Functions.JsonContained(element, e.Customer)); @@ -578,7 +578,7 @@ public void JsonContained_with_string_literal() using var ctx = CreateContext(); var count = ctx.JsonbEntities.Count( e => - EF.Functions.JsonContained(@"{""Name"": ""Joe"", ""Age"": 25}", e.Customer)); + EF.Functions.JsonContained("""{"Name": "Joe", "Age": 25}""", e.Customer)); Assert.Equal(1, count); AssertSql( @@ -593,7 +593,7 @@ SELECT count(*)::int public void JsonContained_with_string_parameter() { using var ctx = CreateContext(); - var someJson = @"{""Name"": ""Joe"", ""Age"": 25}"; + var someJson = """{"Name": "Joe", "Age": 25}"""; var count = ctx.JsonbEntities.Count( e => EF.Functions.JsonContained(someJson, e.Customer)); diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs index 5504d001c..3608a7dfd 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonStringQueryTest.cs @@ -52,7 +52,7 @@ public void Literal() Assert.Empty( ctx.JsonEntities.Where( - e => e.CustomerJsonb == @"{""Name"":""Test customer"",""Age"":80,""IsVip"":false,""Statistics"":null,""Orders"":null}")); + e => e.CustomerJsonb == """{"Name":"Test customer","Age":80,"IsVip":false,"Statistics":null,"Orders":null}""")); AssertSql( """ @@ -99,7 +99,7 @@ public void JsonContains_with_json_element() { using var ctx = CreateContext(); - var element = JsonDocument.Parse(@"{""Name"": ""Joe"", ""Age"": 25}").RootElement; + var element = JsonDocument.Parse("""{"Name": "Joe", "Age": 25}""").RootElement; var count = ctx.JsonEntities.Count(e => EF.Functions.JsonContains(e.CustomerJsonb, element)); Assert.Equal(1, count); @@ -119,7 +119,7 @@ public void JsonContains_with_string() { using var ctx = CreateContext(); - var count = ctx.JsonEntities.Count(e => EF.Functions.JsonContains(e.CustomerJsonb, @"{""Name"": ""Joe"", ""Age"": 25}")); + var count = ctx.JsonEntities.Count(e => EF.Functions.JsonContains(e.CustomerJsonb, """{"Name": "Joe", "Age": 25}""")); Assert.Equal(1, count); @@ -137,7 +137,11 @@ public void JsonContains_with_string_column() using var ctx = CreateContext(); var count = ctx.JsonEntities.Count( - e => EF.Functions.JsonContains(e.CustomerJsonb, @"{""Name"": """ + e.SomeString + @""", ""Age"": 25}")); + e => EF.Functions.JsonContains(e.CustomerJsonb, """ + {"Name": " + """ + e.SomeString + """ + ", "Age": 25} + """)); Assert.Equal(1, count); @@ -154,7 +158,7 @@ public void JsonContained_with_json_element() { using var ctx = CreateContext(); - var element = JsonDocument.Parse(@"{""Name"": ""Joe"", ""Age"": 25}").RootElement; + var element = JsonDocument.Parse("""{"Name": "Joe", "Age": 25}""").RootElement; var count = ctx.JsonEntities.Count(e => EF.Functions.JsonContained(element, e.CustomerJsonb)); Assert.Equal(1, count); @@ -174,7 +178,7 @@ public void JsonContained_with_string() { using var ctx = CreateContext(); - var count = ctx.JsonEntities.Count(e => EF.Functions.JsonContained(@"{""Name"": ""Joe"", ""Age"": 25}", e.CustomerJsonb)); + var count = ctx.JsonEntities.Count(e => EF.Functions.JsonContained("""{"Name": "Joe", "Age": 25}""", e.CustomerJsonb)); Assert.Equal(1, count); @@ -253,60 +257,64 @@ public class JsonStringQueryContext(DbContextOptions options) : PoolableDbContex public static async Task SeedAsync(JsonStringQueryContext context) { - const string customer1 = @" + const string customer1 = """ + { - ""Name"": ""Joe"", - ""Age"": 25, - ""IsVip"": false, - ""Statistics"": + "Name": "Joe", + "Age": 25, + "IsVip": false, + "Statistics": { - ""Visits"": 4, - ""Purchases"": 3, - ""Nested"": + "Visits": 4, + "Purchases": 3, + "Nested": { - ""SomeProperty"": 10, - ""IntArray"": [3, 4] + "SomeProperty": 10, + "IntArray": [3, 4] } }, - ""Orders"": + "Orders": [ { - ""Price"": 99.5, - ""ShippingAddress"": ""Some address 1"", - ""ShippingDate"": ""2019-10-01"" + "Price": 99.5, + "ShippingAddress": "Some address 1", + "ShippingDate": "2019-10-01" }, { - ""Price"": 23, - ""ShippingAddress"": ""Some address 2"", - ""ShippingDate"": ""2019-10-10"" + "Price": 23, + "ShippingAddress": "Some address 2", + "ShippingDate": "2019-10-10" } ] - }"; + } +"""; + + const string customer2 = """ - const string customer2 = @" { - ""Name"": ""Moe"", - ""Age"": 35, - ""IsVip"": true, - ""Statistics"": + "Name": "Moe", + "Age": 35, + "IsVip": true, + "Statistics": { - ""Visits"": 20, - ""Purchases"": 25, - ""Nested"": + "Visits": 20, + "Purchases": 25, + "Nested": { - ""SomeProperty"": 20, - ""IntArray"": [5, 6] + "SomeProperty": 20, + "IntArray": [5, 6] } }, - ""Orders"": + "Orders": [ { - ""Price"": 5, - ""ShippingAddress"": ""Moe's address"", - ""ShippingDate"": ""2019-11-03"" + "Price": 5, + "ShippingAddress": "Moe's address", + "ShippingDate": "2019-11-03" } ] - }"; + } +"""; const string array = "[1, 2, 3]"; diff --git a/test/EFCore.PG.FunctionalTests/Query/LegacyNpgsqlNodaTimeTypeMappingTest.cs b/test/EFCore.PG.FunctionalTests/Query/LegacyNpgsqlNodaTimeTypeMappingTest.cs index 1d1d41641..e2a5bb1f0 100644 --- a/test/EFCore.PG.FunctionalTests/Query/LegacyNpgsqlNodaTimeTypeMappingTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/LegacyNpgsqlNodaTimeTypeMappingTest.cs @@ -55,7 +55,7 @@ public void GenerateSqlLiteral_returns_instant_range_in_legacy_mode() var value = new NpgsqlRange( new LocalDateTime(2020, 1, 1, 12, 0, 0).InUtc().ToInstant(), new LocalDateTime(2020, 1, 2, 12, 0, 0).InUtc().ToInstant()); - Assert.Equal(@"'[""2020-01-01T12:00:00Z"",""2020-01-02T12:00:00Z""]'::tsrange", mapping.GenerateSqlLiteral(value)); + Assert.Equal("""'["2020-01-01T12:00:00Z","2020-01-02T12:00:00Z"]'::tsrange""", mapping.GenerateSqlLiteral(value)); } #region Support @@ -66,11 +66,10 @@ public void GenerateSqlLiteral_returns_instant_range_in_legacy_mode() new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), []), new RelationalTypeMappingSourceDependencies( - new IRelationalTypeMappingSourcePlugin[] - { - new NpgsqlNodaTimeTypeMappingSourcePlugin( + [ + new NpgsqlNodaTimeTypeMappingSourcePlugin( new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies())) - }), + ]), new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new NpgsqlSingletonOptions() ); diff --git a/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs index 399c8cd84..b4ffa354e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NonSharedPrimitiveCollectionsQueryNpgsqlTest.cs @@ -1,5 +1,4 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; -using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs index 48f0fa784..c8fd85fe5 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs @@ -534,7 +534,7 @@ public virtual async Task GroupBy_JsonObjectAgg(bool async) var london = results.Single(r => r.City == "London"); Assert.Equal( - @"{ ""Around the Horn"" : ""Thomas Hardy"", ""B's Beverages"" : ""Victoria Ashworth"", ""Consolidated Holdings"" : ""Elizabeth Brown"", ""Eastern Connection"" : ""Ann Devon"", ""North/South"" : ""Simon Crowther"", ""Seven Seas Imports"" : ""Hari Kumar"" }", + """{ "Around the Horn" : "Thomas Hardy", "B's Beverages" : "Victoria Ashworth", "Consolidated Holdings" : "Elizabeth Brown", "Eastern Connection" : "Ann Devon", "North/South" : "Simon Crowther", "Seven Seas Imports" : "Hari Kumar" }""", london.Companies); AssertSql( diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs index d6db7b54d..cc8aa9153 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs @@ -298,9 +298,8 @@ public async Task Array_Any_Like(bool async) var result = async ? await query.ToListAsync() : query.ToList(); Assert.Equal( - new[] - { - "ANATR", + [ + "ANATR", "BERGS", "BOLID", "CACTU", @@ -322,7 +321,7 @@ public async Task Array_Any_Like(bool async) "TORTU", "TRADH", "WANDK" - }, result.Select(e => e.CustomerID)); + ], result.Select(e => e.CustomerID)); AssertSql( """ @@ -367,9 +366,8 @@ public async Task Array_Any_ILike(bool async) var result = async ? await query.ToListAsync() : query.ToList(); Assert.Equal( - new[] - { - "ANATR", + [ + "ANATR", "BERGS", "BOLID", "CACTU", @@ -391,7 +389,7 @@ public async Task Array_Any_ILike(bool async) "TORTU", "TRADH", "WANDK" - }, result.Select(e => e.CustomerID)); + ], result.Select(e => e.CustomerID)); AssertSql( """ diff --git a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs index 20d115583..8508607b7 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs @@ -22,10 +22,10 @@ public SpatialQueryNpgsqlGeographyTest(SpatialQueryNpgsqlGeographyFixture fixtur protected override bool AssertDistances => false; - public static IEnumerable IsAsyncDataAndUseSpheroid = new[] - { - [false, false], [false, true], [true, false], new object[] { true, true } - }; + public static IEnumerable IsAsyncDataAndUseSpheroid = + [ + [false, false], [false, true], [true, false], [true, true] + ]; public override async Task Area(bool async) { diff --git a/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs index 2c10adc6f..d761b5586 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SqlExecutorNpgsqlTest.cs @@ -15,11 +15,11 @@ protected override DbParameter CreateDbParameter(string name, object value) => new NpgsqlParameter { ParameterName = name, Value = value }; protected override string TenMostExpensiveProductsSproc - => @"SELECT * FROM ""Ten Most Expensive Products""()"; + => """SELECT * FROM "Ten Most Expensive Products"()"""; protected override string CustomerOrderHistorySproc - => @"SELECT * FROM ""CustOrderHist""(@CustomerID)"; + => """SELECT * FROM "CustOrderHist"(@CustomerID)"""; protected override string CustomerOrderHistoryWithGeneratedParameterSproc - => @"SELECT * FROM ""CustOrderHist""({0})"; + => """SELECT * FROM "CustOrderHist"({0})"""; } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPHInheritanceQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPHInheritanceQueryNpgsqlFixture.cs index 7957eb5a1..eedc4427e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPHInheritanceQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPHInheritanceQueryNpgsqlFixture.cs @@ -12,6 +12,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con { base.OnModelCreating(modelBuilder, context); - modelBuilder.Entity().HasNoKey().ToSqlQuery(@"SELECT * FROM ""Animals"""); + modelBuilder.Entity().HasNoKey().ToSqlQuery(""" + SELECT * FROM "Animals" + """); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs index c78819df2..12f3668e0 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TrigramsQueryNpgsqlTest.cs @@ -31,7 +31,7 @@ public void TrigramsShow() .Select(x => EF.Functions.TrigramsShow(x.Text)) .ToArray(); - AssertContainsSql(@"show_trgm(t.""Text"")"); + AssertContainsSql("""show_trgm(t."Text")"""); } [Fact] @@ -42,7 +42,7 @@ public void TrigramsSimilarity() .Select(x => EF.Functions.TrigramsSimilarity(x.Text, "target")) .ToArray(); - AssertContainsSql(@"similarity(t.""Text"", 'target')"); + AssertContainsSql("""similarity(t."Text", 'target')"""); } [Fact] @@ -53,7 +53,7 @@ public void TrigramsWordSimilarity() .Select(x => EF.Functions.TrigramsWordSimilarity(x.Text, "target")) .ToArray(); - AssertContainsSql(@"word_similarity(t.""Text"", 'target')"); + AssertContainsSql("""word_similarity(t."Text", 'target')"""); } [ConditionalFact] @@ -65,7 +65,7 @@ public void TrigramsStrictWordSimilarity() .Select(x => EF.Functions.TrigramsStrictWordSimilarity(x.Text, "target")) .ToArray(); - AssertContainsSql(@"strict_word_similarity(t.""Text"", 'target')"); + AssertContainsSql("""strict_word_similarity(t."Text", 'target')"""); } [Fact] @@ -76,7 +76,7 @@ public void TrigramsAreSimilar() .Select(x => EF.Functions.TrigramsAreSimilar(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" % 'target'"); + AssertContainsSql("""t."Text" % 'target'"""); } [Fact] @@ -87,7 +87,7 @@ public void TrigramsAreWordSimilar() .Select(x => EF.Functions.TrigramsAreWordSimilar(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" <% 'target'"); + AssertContainsSql("""t."Text" <% 'target'"""); } [Fact] @@ -98,7 +98,7 @@ public void TrigramsAreNotWordSimilar() .Select(x => EF.Functions.TrigramsAreNotWordSimilar(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" %> 'target'"); + AssertContainsSql("""t."Text" %> 'target'"""); } [ConditionalFact] @@ -110,7 +110,7 @@ public void TrigramsAreStrictWordSimilar() .Select(x => EF.Functions.TrigramsAreStrictWordSimilar(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" <<% 'target'"); + AssertContainsSql("""t."Text" <<% 'target'"""); } [ConditionalFact] @@ -122,7 +122,7 @@ public void TrigramsAreNotStrictWordSimilar() .Select(x => EF.Functions.TrigramsAreNotStrictWordSimilar(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" %>> 'target'"); + AssertContainsSql("""t."Text" %>> 'target'"""); } [Fact] @@ -133,7 +133,7 @@ public void TrigramsSimilarityDistance() .Select(x => EF.Functions.TrigramsSimilarityDistance(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" <-> 'target'"); + AssertContainsSql("""t."Text" <-> 'target'"""); } [Fact] @@ -144,7 +144,7 @@ public void TrigramsWordSimilarityDistance() .Select(x => EF.Functions.TrigramsWordSimilarityDistance(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" <<-> 'target'"); + AssertContainsSql("""t."Text" <<-> 'target'"""); } [Fact] @@ -155,7 +155,7 @@ public void TrigramsWordSimilarityDistanceInverted() .Select(x => EF.Functions.TrigramsWordSimilarityDistanceInverted(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" <->> 'target'"); + AssertContainsSql("""t."Text" <->> 'target'"""); } [ConditionalFact] @@ -167,7 +167,7 @@ public void TrigramsStrictWordSimilarityDistance() .Select(x => EF.Functions.TrigramsStrictWordSimilarityDistance(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" <<<-> 'target'"); + AssertContainsSql("""t."Text" <<<-> 'target'"""); } [ConditionalFact] @@ -179,7 +179,7 @@ public void TrigramsStrictWordSimilarityDistanceInverted() .Select(x => EF.Functions.TrigramsStrictWordSimilarityDistanceInverted(x.Text, "target")) .ToArray(); - AssertContainsSql(@"t.""Text"" <->>> 'target'"); + AssertContainsSql("""t."Text" <->>> 'target'"""); } [Fact] // #1659 diff --git a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs index 5696d082b..eb0179a68 100644 --- a/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Scaffolding/NpgsqlDatabaseModelFactoryTest.cs @@ -42,8 +42,8 @@ MAXVALUE 8 MINVALUE -3 CYCLE; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var defaultSequence = dbModel.Sequences.First(ds => ds.Name == "DefaultFacetsSequence"); @@ -81,8 +81,8 @@ public void Sequence_min_max_start_values_are_null_if_default() CREATE SEQUENCE "IntSequence" AS int; CREATE SEQUENCE "BigIntSequence" AS bigint; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { Assert.All( @@ -107,8 +107,8 @@ public void Filter_sequences_based_on_schema() CREATE SEQUENCE "Sequence"; CREATE SEQUENCE db2."Sequence" """, - Enumerable.Empty(), - new[] { "db2" }, + [], + ["db2"], dbModel => { var sequence = Assert.Single(dbModel.Sequences); @@ -130,8 +130,8 @@ CREATE SEQUENCE db2."Sequence" public void Set_default_schema() => Test( "SELECT 1", - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { Assert.Equal("public", dbModel.DefaultSchema); @@ -145,8 +145,8 @@ public void Create_tables() CREATE TABLE "Everest" (id int); CREATE TABLE "Denali" (id int); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { Assert.Collection( @@ -178,8 +178,8 @@ public void Filter_schemas() CREATE TABLE db2."K2" (Id int, A varchar, UNIQUE (A)); CREATE TABLE "Kilimanjaro" (Id int, B varchar, UNIQUE (B)); """, - Enumerable.Empty(), - new[] { "db2" }, + [], + ["db2"], dbModel => { var table = Assert.Single(dbModel.Tables); @@ -201,8 +201,8 @@ public void Filter_tables() CREATE TABLE "K2" (Id int, A varchar, UNIQUE (A)); CREATE TABLE "Kilimanjaro" (Id int, B varchar, UNIQUE (B), FOREIGN KEY (B) REFERENCES "K2" (A)); """, - new[] { "K2" }, - Enumerable.Empty(), + ["K2"], + [], dbModel => { var table = Assert.Single(dbModel.Tables); @@ -224,8 +224,10 @@ public void Filter_tables_with_qualified_name() CREATE TABLE "K.2" (Id int, A varchar, UNIQUE (A)); CREATE TABLE "Kilimanjaro" (Id int, B varchar, UNIQUE (B)); """, - new[] { @"""K.2""" }, - Enumerable.Empty(), + [""" + "K.2" + """], + [], dbModel => { var table = Assert.Single(dbModel.Tables); @@ -248,8 +250,8 @@ public void Filter_tables_with_schema_qualified_name1() CREATE TABLE db2."K2" (Id int, A varchar, UNIQUE (A)); CREATE TABLE "Kilimanjaro" (Id int, B varchar, UNIQUE (B)); """, - new[] { "public.K2" }, - Enumerable.Empty(), + ["public.K2"], + [], dbModel => { var table = Assert.Single(dbModel.Tables); @@ -273,8 +275,10 @@ public void Filter_tables_with_schema_qualified_name2() CREATE TABLE "db.2"."K.2" (Id int, A varchar, UNIQUE (A)); CREATE TABLE "db.2"."Kilimanjaro" (Id int, B varchar, UNIQUE (B)); """, - new[] { @"""db.2"".""K.2""" }, - Enumerable.Empty(), + [""" + "db.2"."K.2" + """], + [], dbModel => { var table = Assert.Single(dbModel.Tables); @@ -298,8 +302,10 @@ public void Filter_tables_with_schema_qualified_name3() CREATE TABLE "db2"."K.2" (Id int, A varchar, UNIQUE (A)); CREATE TABLE "Kilimanjaro" (Id int, B varchar, UNIQUE (B)); """, - new[] { @"public.""K.2""" }, - Enumerable.Empty(), + [""" + public."K.2" + """], + [], dbModel => { var table = Assert.Single(dbModel.Tables); @@ -323,8 +329,10 @@ public void Filter_tables_with_schema_qualified_name4() CREATE TABLE "db.2"."K2" (Id int, A varchar, UNIQUE (A)); CREATE TABLE "db.2"."Kilimanjaro" (Id int, B varchar, UNIQUE (B)); """, - new[] { @"""db.2"".K2" }, - Enumerable.Empty(), + [""" + "db.2".K2 + """], + [], dbModel => { var table = Assert.Single(dbModel.Tables); @@ -380,15 +388,24 @@ public void Complex_filtering_validation() FOREIGN KEY ("ForeignKeyId1", "ForeignKeyId2") REFERENCES "db2"."PrincipalTable"("UC1", "UC2") ON DELETE CASCADE ); """, - new[] - { - @"""db.2"".""QuotedTableName""", - @"""db.2"".SimpleTableName", - @"public.""Table.With.Dot""", - @"public.""SimpleTableName""", - @"""JustTableName""" - }, - new[] { "db2" }, + [ + """ + "db.2"."QuotedTableName" + """, + """ + "db.2".SimpleTableName + """, + """ + public."Table.With.Dot" + """, + """ + public."SimpleTableName" + """, + """ + "JustTableName" + """ + ], + ["db2"], dbModel => { var sequence = Assert.Single(dbModel.Sequences); @@ -455,8 +472,8 @@ public void Create_columns() "Name" text NOT NULL ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -472,7 +489,9 @@ public void Create_columns() Assert.Single(table.Columns.Where(c => c.Name == "Id")); Assert.Single(table.Columns.Where(c => c.Name == "Name")); }, - @"DROP TABLE ""Blogs"""); + """ + DROP TABLE "Blogs" + """); [Fact] public void Create_view_columns() @@ -480,8 +499,8 @@ public void Create_view_columns() """ CREATE VIEW "BlogsView" AS SELECT 100::int AS "Id", ''::text AS "Name"; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = Assert.IsType(dbModel.Tables.Single()); @@ -498,7 +517,7 @@ public void Create_view_columns() Assert.Single(table.Columns.Where(c => c.Name == "Id")); Assert.Single(table.Columns.Where(c => c.Name == "Name")); }, - @"DROP VIEW ""BlogsView"";"); + """DROP VIEW "BlogsView";"""); [Fact] public void Create_materialized_view_columns() @@ -506,8 +525,8 @@ public void Create_materialized_view_columns() """ CREATE MATERIALIZED VIEW "BlogsView" AS SELECT 100::int AS "Id", ''::text AS "Name"; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -524,7 +543,7 @@ public void Create_materialized_view_columns() Assert.Single(table.Columns.Where(c => c.Name == "Id")); Assert.Single(table.Columns.Where(c => c.Name == "Name")); }, - @"DROP MATERIALIZED VIEW ""BlogsView"";"); + """DROP MATERIALIZED VIEW "BlogsView";"""); [Fact] public void Create_primary_key() @@ -532,8 +551,8 @@ public void Create_primary_key() """ CREATE TABLE "PrimaryKeyTable" ("Id" int PRIMARY KEY); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var pk = dbModel.Tables.Single().PrimaryKey; @@ -543,7 +562,9 @@ public void Create_primary_key() Assert.StartsWith("PrimaryKeyTable_pkey", pk.Name); Assert.Equal(["Id"], pk.Columns.Select(ic => ic.Name).ToList()); }, - @"DROP TABLE ""PrimaryKeyTable"""); + """ + DROP TABLE "PrimaryKeyTable" + """); [Fact] public void Create_unique_constraints() @@ -560,8 +581,8 @@ public void Create_unique_constraints() CREATE INDEX "IX_INDEX" on "UniqueConstraint" ("IndexProperty"); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -578,7 +599,9 @@ public void Create_unique_constraints() Assert.Equal("UniqueConstraint", secondConstraint.Table.Name); Assert.Equal(["Unq1", "Unq2"], secondConstraint.Columns.Select(ic => ic.Name).ToList()); }, - @"DROP TABLE ""UniqueConstraint"""); + """ + DROP TABLE "UniqueConstraint" + """); [Fact] public void Create_indexes() @@ -595,8 +618,8 @@ public void Create_indexes() CREATE INDEX "IX_NAME" on "IndexTable" ("Name"); CREATE INDEX "IX_INDEX" on "IndexTable" ("IndexProperty"); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -613,7 +636,9 @@ public void Create_indexes() Assert.Single(table.Indexes.Where(c => c.Name == "IX_NAME")); Assert.Single(table.Indexes.Where(c => c.Name == "IX_INDEX")); }, - @"DROP TABLE ""IndexTable"""); + """ + DROP TABLE "IndexTable" + """); [Fact] public void Create_foreign_keys() @@ -634,8 +659,8 @@ FOREIGN KEY ("ForeignKeyId") REFERENCES "PrincipalTable"("Id") ON DELETE CASCADE FOREIGN KEY ("Id") REFERENCES "PrincipalTable"("Id") ON DELETE NO ACTION ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var firstFk = Assert.Single(dbModel.Tables.Single(t => t.Name == "FirstDependent").ForeignKeys); @@ -688,8 +713,8 @@ CREATE TABLE domains ( char_domain public.char_domain NULL ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var textDomainColumn = Assert.Single(dbModel.Tables.Single().Columns.Where(c => c.Name == "text_domain")); @@ -721,8 +746,8 @@ public void Decimal_numeric_types_have_precision_scale() "numeric18Column" numeric(18) NOT NULL ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -731,7 +756,9 @@ public void Decimal_numeric_types_have_precision_scale() Assert.Equal("numeric(15,2)", columns.Single(c => c.Name == "numeric152Column").StoreType); Assert.Equal("numeric(18,0)", columns.Single(c => c.Name == "numeric18Column").StoreType); }, - @"DROP TABLE ""NumericColumns"""); + """ + DROP TABLE "NumericColumns" + """); [Fact] public void Specific_max_length_are_add_to_store_type() @@ -747,8 +774,8 @@ public void Specific_max_length_are_add_to_store_type() "varbit123ArrayColumn" varbit(123)[] NULL ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -760,7 +787,9 @@ public void Specific_max_length_are_add_to_store_type() Assert.Equal("character varying(66)[]", columns.Single(c => c.Name == "varchar66ArrayColumn").StoreType); Assert.Equal("bit varying(123)[]", columns.Single(c => c.Name == "varbit123ArrayColumn").StoreType); }, - @"DROP TABLE ""LengthColumns"""); + """ + DROP TABLE "LengthColumns" + """); [Fact] public void Datetime_types_have_precision_if_non_null_scale() @@ -775,8 +804,8 @@ public void Datetime_types_have_precision_if_non_null_scale() "interval5Column" interval(5) NULL ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -787,7 +816,9 @@ public void Datetime_types_have_precision_if_non_null_scale() Assert.Equal("timestamp(4) with time zone", columns.Single(c => c.Name == "timestamptz4Column").StoreType); Assert.Equal("interval(5)", columns.Single(c => c.Name == "interval5Column").StoreType); }, - @"DROP TABLE ""LengthColumns"""); + """ + DROP TABLE "LengthColumns" + """); [Fact] public void Store_types_without_any_facets() @@ -820,8 +851,8 @@ public void Store_types_without_any_facets() "textArrayColumn" text[] ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single(t => t.Name == "NoFacetTypes").Columns; @@ -849,7 +880,9 @@ public void Store_types_without_any_facets() Assert.Equal("xid", columns.Single(c => c.Name == "xidColumn").StoreType); Assert.Equal("text[]", columns.Single(c => c.Name == "textArrayColumn").StoreType); }, - @"DROP TABLE ""NoFacetTypes"""); + """ + DROP TABLE "NoFacetTypes" + """); [Fact] public void Default_values_are_stored() @@ -860,8 +893,8 @@ public void Default_values_are_stored() "FixedDefaultValue" timestamp NOT NULL DEFAULT ('1999-01-08') ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -869,7 +902,9 @@ public void Default_values_are_stored() "'1999-01-08 00:00:00'::timestamp without time zone", columns.Single(c => c.Name == "FixedDefaultValue").DefaultValueSql); }, - @"DROP TABLE ""DefaultValues"""); + """ + DROP TABLE "DefaultValues" + """); [ConditionalFact] [MinimumPostgresVersion(12, 0)] @@ -883,8 +918,8 @@ public void Computed_values_are_stored() "SumOfAAndB" int GENERATED ALWAYS AS ("A" + "B") STORED ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -893,10 +928,12 @@ public void Computed_values_are_stored() // columns. var column = columns.Single(c => c.Name == "SumOfAAndB"); Assert.Null(column.DefaultValueSql); - Assert.Equal(@"(""A"" + ""B"")", column.ComputedColumnSql); + Assert.Equal("""("A" + "B")""", column.ComputedColumnSql); Assert.True(column.IsStored); }, - @"DROP TABLE ""ComputedValues"""); + """ + DROP TABLE "ComputedValues" + """); [Fact] public void Default_value_matching_clr_default_is_not_stored() @@ -927,8 +964,8 @@ public void Default_value_matching_clr_default_is_not_stored() "IgnoredDefault34" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -952,8 +989,8 @@ public void ValueGenerated_is_set_for_default_and_serial_column() "FixedDefaultValue" timestamp NOT NULL DEFAULT ('1999-01-08') ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -962,7 +999,9 @@ public void ValueGenerated_is_set_for_default_and_serial_column() Assert.Null(columns.Single(c => c.Name == "NoValueGenerationColumn").ValueGenerated); Assert.Null(columns.Single(c => c.Name == "FixedDefaultValue").ValueGenerated); }, - @"DROP TABLE ""ValueGeneratedProperties"""); + """ + DROP TABLE "ValueGeneratedProperties" + """); [ConditionalFact] [MinimumPostgresVersion(10, 0)] @@ -974,8 +1013,8 @@ public void ValueGenerated_is_set_for_identity_column() "Id2" INT GENERATED BY DEFAULT AS IDENTITY ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -983,7 +1022,9 @@ public void ValueGenerated_is_set_for_identity_column() Assert.Equal(ValueGenerated.OnAdd, columns.Single(c => c.Name == "Id1").ValueGenerated); Assert.Equal(ValueGenerated.OnAdd, columns.Single(c => c.Name == "Id2").ValueGenerated); }, - @"DROP TABLE ""ValueGeneratedProperties"""); + """ + DROP TABLE "ValueGeneratedProperties" + """); [ConditionalFact] [MinimumPostgresVersion(12, 0)] @@ -997,15 +1038,17 @@ public void ValueGenerated_is_set_for_computed_column() "SumOfAAndB" int GENERATED ALWAYS AS ("A" + "B") STORED ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; Assert.Null(columns.Single(c => c.Name == "SumOfAAndB").ValueGenerated); }, - @"DROP TABLE ""ValueGeneratedProperties"""); + """ + DROP TABLE "ValueGeneratedProperties" + """); [Fact] public void Column_nullability_is_set() @@ -1017,8 +1060,8 @@ public void Column_nullability_is_set() "NonNullableInt" int NOT NULL ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -1026,7 +1069,9 @@ public void Column_nullability_is_set() Assert.True(columns.Single(c => c.Name == "NullableInt").IsNullable); Assert.False(columns.Single(c => c.Name == "NonNullableInt").IsNullable); }, - @"DROP TABLE ""NullableColumns"""); + """ + DROP TABLE "NullableColumns" + """); [Fact] public void Column_nullability_is_set_with_domain() @@ -1040,8 +1085,8 @@ public void Column_nullability_is_set_with_domain() "NonNullString" non_nullable_int NOT NULL ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -1063,10 +1108,12 @@ CREATE TABLE "SystemColumnsTable" "Id" int NOT NULL PRIMARY KEY ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => Assert.Single(dbModel.Tables.Single().Columns), - @"DROP TABLE ""SystemColumnsTable"""); + """ + DROP TABLE "SystemColumnsTable" + """); #endregion @@ -1082,8 +1129,8 @@ public void Create_composite_primary_key() PRIMARY KEY ("Id2", "Id1") ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var pk = dbModel.Tables.Single().PrimaryKey; @@ -1092,7 +1139,9 @@ PRIMARY KEY ("Id2", "Id1") Assert.Equal("CompositePrimaryKeyTable", pk.Table.Name); Assert.Equal(["Id2", "Id1"], pk.Columns.Select(ic => ic.Name).ToList()); }, - @"DROP TABLE ""CompositePrimaryKeyTable"""); + """ + DROP TABLE "CompositePrimaryKeyTable" + """); [Fact] public void Set_primary_key_name_from_index() @@ -1104,8 +1153,8 @@ public void Set_primary_key_name_from_index() CONSTRAINT "MyPK" PRIMARY KEY ( "Id2" ) ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var pk = dbModel.Tables.Single().PrimaryKey; @@ -1115,7 +1164,9 @@ public void Set_primary_key_name_from_index() Assert.StartsWith("MyPK", pk.Name); Assert.Equal(["Id2"], pk.Columns.Select(ic => ic.Name).ToList()); }, - @"DROP TABLE ""PrimaryKeyName"""); + """ + DROP TABLE "PrimaryKeyName" + """); #endregion @@ -1131,8 +1182,8 @@ public void Create_composite_unique_constraint() CONSTRAINT "UX" UNIQUE ("Id2", "Id1") ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var uniqueConstraint = Assert.Single(dbModel.Tables.Single().UniqueConstraints); @@ -1143,7 +1194,9 @@ public void Create_composite_unique_constraint() Assert.Equal("UX", uniqueConstraint.Name); Assert.Equal(["Id2", "Id1"], uniqueConstraint.Columns.Select(ic => ic.Name).ToList()); }, - @"DROP TABLE ""CompositeUniqueConstraintTable"""); + """ + DROP TABLE "CompositeUniqueConstraintTable" + """); [Fact] public void Set_unique_constraint_name_from_index() @@ -1155,8 +1208,8 @@ public void Set_unique_constraint_name_from_index() CONSTRAINT "MyUC" UNIQUE ( "Id2" ) ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1169,7 +1222,9 @@ public void Set_unique_constraint_name_from_index() Assert.Equal(["Id2"], uniqueConstraint.Columns.Select(ic => ic.Name).ToList()); Assert.Empty(table.Indexes); }, - @"DROP TABLE ""UniqueConstraintName"""); + """ + DROP TABLE "UniqueConstraintName" + """); #endregion @@ -1186,8 +1241,8 @@ public void Create_composite_index() CREATE INDEX "IX_COMPOSITE" ON "CompositeIndexTable" ( "Id2", "Id1" ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var index = Assert.Single(dbModel.Tables.Single().Indexes); @@ -1198,7 +1253,9 @@ public void Create_composite_index() Assert.Equal("IX_COMPOSITE", index.Name); Assert.Equal(["Id2", "Id1"], index.Columns.Select(ic => ic.Name).ToList()); }, - @"DROP TABLE ""CompositeIndexTable"""); + """ + DROP TABLE "CompositeIndexTable" + """); [Fact] public void Set_unique_true_for_unique_index() @@ -1211,8 +1268,8 @@ public void Set_unique_true_for_unique_index() CREATE UNIQUE INDEX "IX_UNIQUE" ON "UniqueIndexTable" ( "Id2" ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var index = Assert.Single(dbModel.Tables.Single().Indexes); @@ -1225,7 +1282,9 @@ public void Set_unique_true_for_unique_index() Assert.Null(index.Filter); Assert.Equal(["Id2"], index.Columns.Select(ic => ic.Name).ToList()); }, - @"DROP TABLE ""UniqueIndexTable"""); + """ + DROP TABLE "UniqueIndexTable" + """); [Fact] public void Set_filter_for_filtered_index() @@ -1238,8 +1297,8 @@ public void Set_filter_for_filtered_index() CREATE UNIQUE INDEX "IX_UNIQUE" ON "FilteredIndexTable" ( "Id2" ) WHERE "Id2" > 10; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var index = Assert.Single(dbModel.Tables.Single().Indexes); @@ -1248,10 +1307,12 @@ public void Set_filter_for_filtered_index() Assert.Equal("public", index.Table.Schema); Assert.Equal("FilteredIndexTable", index.Table.Name); Assert.Equal("IX_UNIQUE", index.Name); - Assert.Equal(@"(""Id2"" > 10)", index.Filter); + Assert.Equal("""("Id2" > 10)""", index.Filter); Assert.Equal(["Id2"], index.Columns.Select(ic => ic.Name).ToList()); }, - @"DROP TABLE ""FilteredIndexTable"""); + """ + DROP TABLE "FilteredIndexTable" + """); #endregion @@ -1274,8 +1335,8 @@ PRIMARY KEY ("Id1", "Id2") FOREIGN KEY ("ForeignKeyId1", "ForeignKeyId2") REFERENCES "PrincipalTable"("Id1", "Id2") ON DELETE CASCADE ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var fk = Assert.Single(dbModel.Tables.Single(t => t.Name == "DependentTable").ForeignKeys); @@ -1314,8 +1375,8 @@ FOREIGN KEY ("ForeignKeyId1") REFERENCES "PrincipalTable"("Id") ON DELETE CASCAD FOREIGN KEY ("ForeignKeyId2") REFERENCES "AnotherPrincipalTable"("Id") ON DELETE CASCADE ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var foreignKeys = dbModel.Tables.Single(t => t.Name == "DependentTable").ForeignKeys; @@ -1365,8 +1426,8 @@ public void Create_foreign_key_referencing_unique_constraint() FOREIGN KEY ("ForeignKeyId") REFERENCES "PrincipalTable"("Id2") ON DELETE CASCADE ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var fk = Assert.Single(dbModel.Tables.Single(t => t.Name == "DependentTable").ForeignKeys); @@ -1399,8 +1460,8 @@ public void Set_name_for_foreign_key() CONSTRAINT "MYFK" FOREIGN KEY ("ForeignKeyId") REFERENCES "PrincipalTable"("Id") ON DELETE CASCADE ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var fk = Assert.Single(dbModel.Tables.Single(t => t.Name == "DependentTable").ForeignKeys); @@ -1443,8 +1504,8 @@ FOREIGN KEY ("ForeignKeyRestrictId") REFERENCES "PrincipalTable"("Id") ON DELETE FOREIGN KEY ("ForeignKeySetDefaultId") REFERENCES "PrincipalTable"("Id") ON DELETE SET DEFAULT ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(t => t.Name == "DependentTable"); @@ -1487,8 +1548,8 @@ public void Warn_missing_schema() """ CREATE TABLE "Blank" ("Id" int) """, - Enumerable.Empty(), - new[] { "MySchema" }, + [], + ["MySchema"], dbModel => { Assert.Empty(dbModel.Tables); @@ -1499,7 +1560,9 @@ public void Warn_missing_schema() Assert.Equal( NpgsqlResources.LogMissingSchema(new TestLogger()).GenerateMessage("MySchema"), Message); }, - @"DROP TABLE ""Blank"""); + """ + DROP TABLE "Blank" + """); [Fact] public void Warn_missing_table() @@ -1507,8 +1570,8 @@ public void Warn_missing_table() """ CREATE TABLE "Blank" ("Id" int) """, - new[] { "MyTable" }, - Enumerable.Empty(), + ["MyTable"], + [], dbModel => { Assert.Empty(dbModel.Tables); @@ -1519,7 +1582,9 @@ public void Warn_missing_table() Assert.Equal( NpgsqlResources.LogMissingTable(new TestLogger()).GenerateMessage("MyTable"), Message); }, - @"DROP TABLE ""Blank"""); + """ + DROP TABLE "Blank" + """); [Fact] public void Warn_missing_principal_table_for_foreign_key() @@ -1535,8 +1600,8 @@ public void Warn_missing_principal_table_for_foreign_key() CONSTRAINT "MYFK" FOREIGN KEY ("ForeignKeyId") REFERENCES "PrincipalTable"("Id") ON DELETE CASCADE ); """, - new[] { "DependentTable" }, - Enumerable.Empty(), + ["DependentTable"], + [], _ => { var (_, Id, Message, _, _) = Assert.Single(Fixture.ListLoggerFactory.Log.Where(t => t.Level == LogLevel.Warning)); @@ -1565,8 +1630,8 @@ public void SequenceSerial() CREATE TABLE my_schema.serial_sequence_in_schema (Id serial PRIMARY KEY); CREATE TABLE my_schema."SerialSequenceInSchema" ("Id" serial PRIMARY KEY); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { // Sequences which belong to a serial column should not get reverse engineered as separate sequences @@ -1595,12 +1660,12 @@ public void SequenceNonSerial() CREATE SEQUENCE "SomeSequence"; CREATE TABLE "NonSerialSequence" ("Id" integer PRIMARY KEY DEFAULT nextval('"SomeSequence"')) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var column = dbModel.Tables.Single().Columns.Single(); - Assert.Equal(@"nextval('""SomeSequence""'::regclass)", column.DefaultValueSql); + Assert.Equal("""nextval('"SomeSequence"'::regclass)""", column.DefaultValueSql); // Npgsql has special detection for serial columns (scaffolding them with ValueGenerated.OnAdd // and removing the default), but not for non-serial sequence-driven columns, which are scaffolded // with a DefaultValue. This is consistent with the SqlServer scaffolding behavior. @@ -1624,8 +1689,8 @@ CREATE TABLE identity ( b int GENERATED BY DEFAULT AS IDENTITY ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var idIdentityAlways = dbModel.Tables.Single().Columns.Single(c => c.Name == "id"); @@ -1663,8 +1728,8 @@ with_options int GENERATED BY DEFAULT AS IDENTITY (START WITH 5 INCREMENT BY 2 M smallint_without_options smallint GENERATED BY DEFAULT AS IDENTITY ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var withOptions = dbModel.Tables.Single().Columns.Single(c => c.Name == "with_options"); @@ -1715,8 +1780,8 @@ CREATE TABLE columns_with_collation ( non_default_collation TEXT COLLATE "POSIX" ); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var columns = dbModel.Tables.Single().Columns; @@ -1730,8 +1795,8 @@ non_default_collation TEXT COLLATE "POSIX" public void Default_database_collation_is_not_scaffolded() => Test( @"-- Empty database", - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => Assert.Null(dbModel.Collation), @""); @@ -1743,8 +1808,8 @@ public void Index_method() CREATE INDEX ix_a ON "IndexMethod" USING hash (a); CREATE INDEX ix_b ON "IndexMethod" (b); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1762,7 +1827,9 @@ public void Index_method() Assert.Null(noMethodIndex.FindAnnotation(NpgsqlAnnotationNames.IndexMethod)); //Assert.Equal("btree", noMethodIndex.FindAnnotation(NpgsqlAnnotationNames.IndexMethod).Value); }, - @"DROP TABLE ""IndexMethod"""); + """ + DROP TABLE "IndexMethod" + """); [Fact] public void Index_operators() @@ -1772,8 +1839,8 @@ public void Index_operators() CREATE INDEX ix_with ON "IndexOperators" (a, b varchar_pattern_ops); CREATE INDEX ix_without ON "IndexOperators" (a, b); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1784,7 +1851,9 @@ public void Index_operators() var indexWithout = table.Indexes.Single(i => i.Name == "ix_without"); Assert.Null(indexWithout.FindAnnotation(NpgsqlAnnotationNames.IndexOperators)); }, - @"DROP TABLE ""IndexOperators"""); + """ + DROP TABLE "IndexOperators" + """); [Fact] public void Index_collation() @@ -1794,8 +1863,8 @@ public void Index_collation() CREATE INDEX ix_with ON "IndexCollation" (a, b COLLATE "POSIX"); CREATE INDEX ix_without ON "IndexCollation" (a, b); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1806,7 +1875,9 @@ public void Index_collation() var indexWithout = table.Indexes.Single(i => i.Name == "ix_without"); Assert.Null(indexWithout.FindAnnotation(RelationalAnnotationNames.Collation)); }, - @"DROP TABLE ""IndexCollation"""); + """ + DROP TABLE "IndexCollation" + """); [Theory] [InlineData("gin", new bool[0])] @@ -1825,8 +1896,8 @@ public void Index_IsDescending(string method, bool[] expected) CREATE INDEX ix_btree ON "IndexSortOrder" USING btree (a ASC, b DESC); CREATE INDEX ix_without ON "IndexSortOrder" (a, b); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1836,9 +1907,11 @@ public void Index_IsDescending(string method, bool[] expected) Assert.Equal(expected, indexWith.IsDescending); var indexWithout = table.Indexes.Single(i => i.Name == "ix_without"); - Assert.Equal(new[] { false, false }, indexWithout.IsDescending); + Assert.Equal([false, false], indexWithout.IsDescending); }, - @"DROP TABLE ""IndexSortOrder"""); + """ + DROP TABLE "IndexSortOrder" + """); [Fact] public void Index_null_sort_order() @@ -1848,8 +1921,8 @@ public void Index_null_sort_order() CREATE INDEX ix_with ON "IndexNullSortOrder" (a NULLS FIRST, b DESC NULLS LAST); CREATE INDEX ix_without ON "IndexNullSortOrder" (a, b); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1862,7 +1935,9 @@ public void Index_null_sort_order() var indexWithout = table.Indexes.Single(i => i.Name == "ix_without"); Assert.Null(indexWithout.FindAnnotation(NpgsqlAnnotationNames.IndexNullSortOrder)); }, - @"DROP TABLE ""IndexNullSortOrder"""); + """ + DROP TABLE "IndexNullSortOrder" + """); [ConditionalFact] [MinimumPostgresVersion(11, 0)] @@ -1873,8 +1948,8 @@ public void Index_covering() CREATE INDEX ix_with ON "IndexCovering" (a) INCLUDE (b, c); CREATE INDEX ix_without ON "IndexCovering" (a, b, c); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1889,7 +1964,9 @@ public void Index_covering() Assert.Equal(new[] { "a", "b", "c" }, indexWithout.Columns.Select(i => i.Name).ToArray()); Assert.Null(indexWithout.FindAnnotation(NpgsqlAnnotationNames.IndexInclude)); }, - @"DROP TABLE ""IndexCovering"""); + """ + DROP TABLE "IndexCovering" + """); [ConditionalFact] [MinimumPostgresVersion(15, 0)] @@ -1900,8 +1977,8 @@ public void Index_are_nulls_distinct() CREATE INDEX "IX_NullsDistinct" ON "IndexNullsDistinct" (a); CREATE INDEX "IX_NullsNotDistinct" ON "IndexNullsDistinct" (a) NULLS NOT DISTINCT; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1913,7 +1990,9 @@ public void Index_are_nulls_distinct() false, Assert.Single(table.Indexes, i => i.Name == "IX_NullsNotDistinct")[NpgsqlAnnotationNames.NullsDistinct]); }, - @"DROP TABLE ""IndexNullsDistinct"""); + """ + DROP TABLE "IndexNullsDistinct" + """); [Fact] public void Comments() @@ -1923,8 +2002,8 @@ public void Comments() COMMENT ON TABLE comment IS 'table comment'; COMMENT ON COLUMN comment.a IS 'column comment' """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var table = dbModel.Tables.Single(); @@ -1942,8 +2021,8 @@ public void Sequence_types() CREATE SEQUENCE "IntSequence" AS int; CREATE SEQUENCE "BigIntSequence" AS bigint; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var smallSequence = dbModel.Sequences.Single(s => s.Name == "SmallIntSequence"); @@ -1967,8 +2046,8 @@ public void Dropped_columns() ALTER TABLE foo DROP COLUMN id; ALTER TABLE foo ADD COLUMN id2 int PRIMARY KEY; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { Assert.Single(dbModel.Tables.Single().Columns); @@ -1983,8 +2062,8 @@ public void Postgres_extensions() CREATE EXTENSION hstore; CREATE EXTENSION pgcrypto SCHEMA db2; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var extensions = dbModel.GetPostgresExtensions(); @@ -2011,8 +2090,8 @@ public void Enums() CREATE TYPE db2.mood AS ENUM ('excited', 'depressed'); CREATE TABLE foo (mood mood UNIQUE); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var enums = dbModel.GetPostgresEnums(); @@ -2020,11 +2099,11 @@ public void Enums() var mood = enums.Single(e => e.Schema is null); Assert.Equal("mood", mood.Name); - Assert.Equal(new[] { "happy", "sad" }, mood.Labels); + Assert.Equal(["happy", "sad"], mood.Labels); var mood2 = enums.Single(e => e.Schema == "db2"); Assert.Equal("mood", mood2.Name); - Assert.Equal(new[] { "excited", "depressed" }, mood2.Labels); + Assert.Equal(["excited", "depressed"], mood2.Labels); var table = Assert.Single(dbModel.Tables); Assert.NotNull(table); @@ -2049,8 +2128,8 @@ public void Bug453() CREATE TABLE foo (mood mood, some_num int UNIQUE); CREATE TABLE bar (foreign_key int REFERENCES foo(some_num)); """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], // Enum columns are left out of the model for now (a warning is logged). dbModel => Assert.Single(dbModel.Tables.Single(t => t.Name == "foo").Columns), """ @@ -2093,8 +2172,8 @@ CREATE TABLE column_types ( line line ) """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => { var options = new NpgsqlSingletonOptions(); @@ -2129,8 +2208,8 @@ public void System_tables_are_ignored() DROP EXTENSION IF EXISTS postgis; CREATE EXTENSION postgis; """, - Enumerable.Empty(), - Enumerable.Empty(), + [], + [], dbModel => Assert.Empty(dbModel.Tables), "DROP EXTENSION postgis"); @@ -2184,7 +2263,9 @@ public override async Task InitializeAsync() { await base.InitializeAsync(); await TestStore.ExecuteNonQueryAsync("CREATE SCHEMA IF NOT EXISTS db2"); - await TestStore.ExecuteNonQueryAsync(@"CREATE SCHEMA IF NOT EXISTS ""db.2"""); + await TestStore.ExecuteNonQueryAsync(""" + CREATE SCHEMA IF NOT EXISTS "db.2" + """); } protected override bool ShouldLogCategory(string logCategory) diff --git a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs index 55f9f0bd9..32e5a6c3a 100644 --- a/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs +++ b/test/EFCore.PG.FunctionalTests/TestModels/Array/ArrayQueryData.cs @@ -22,8 +22,8 @@ public IQueryable Set() } public static IReadOnlyList CreateArrayEntities() - => new ArrayEntity[] - { + => + [ new() { Id = 1, @@ -49,7 +49,7 @@ public static IReadOnlyList CreateArrayEntities() ValueConvertedListOfEnum = [SomeEnum.Eight, SomeEnum.Nine], ArrayOfStringConvertedToDelimitedString = ["3", "4"], ListOfStringConvertedToDelimitedString = ["3", "4"], - IList = new[] { 8, 9 }, + IList = [8, 9], Byte = 10 }, new() @@ -77,11 +77,11 @@ public static IReadOnlyList CreateArrayEntities() ValueConvertedListOfEnum = [SomeEnum.Nine, SomeEnum.Ten], ArrayOfStringConvertedToDelimitedString = ["5", "6", "7", "8"], ListOfStringConvertedToDelimitedString = ["5", "6", "7", "8"], - IList = new[] { 9, 10 }, + IList = [9, 10], Byte = 20 } - }; + ]; public static IReadOnlyList CreateContainerEntities() - => new[] { new ArrayContainerEntity { Id = 1, ArrayEntities = CreateArrayEntities().ToList() } }; + => [new ArrayContainerEntity { Id = 1, ArrayEntities = CreateArrayEntities().ToList() }]; } diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs index d75de6be2..5698435e0 100644 --- a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlDatabaseCleaner.cs @@ -53,8 +53,7 @@ public override void Clean(DatabaseFacade facade) private void DropExtensions(NpgsqlConnection conn) { - const string getExtensions = @" -SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name <> 'plpgsql'"; + const string getExtensions = "SELECT name FROM pg_available_extensions WHERE installed_version IS NOT NULL AND name <> 'plpgsql'"; List extensions; using (var cmd = new NpgsqlCommand(getExtensions, conn)) @@ -76,11 +75,12 @@ private void DropExtensions(NpgsqlConnection conn) /// private void DropTypes(NpgsqlConnection conn) { - const string getUserDefinedRangesEnums = @" + const string getUserDefinedRangesEnums = """ SELECT ns.nspname, typname FROM pg_type JOIN pg_namespace AS ns ON ns.oid = pg_type.typnamespace -WHERE typtype IN ('r', 'e') AND nspname <> 'pg_catalog'"; +WHERE typtype IN ('r', 'e') AND nspname <> 'pg_catalog' +"""; (string Schema, string Name)[] userDefinedTypes; using (var cmd = new NpgsqlCommand(getUserDefinedRangesEnums, conn)) @@ -91,7 +91,7 @@ FROM pg_type if (userDefinedTypes.Any()) { - var dropTypes = string.Concat(userDefinedTypes.Select(t => $@"DROP TYPE ""{t.Schema}"".""{t.Name}"" CASCADE;")); + var dropTypes = string.Concat(userDefinedTypes.Select(t => $"""DROP TYPE "{t.Schema}"."{t.Name}" CASCADE;""")); using var cmd = new NpgsqlCommand(dropTypes, conn); cmd.ExecuteNonQuery(); } @@ -102,8 +102,8 @@ FROM pg_type /// private void DropFunctions(NpgsqlConnection conn) { - const string getUserDefinedFunctions = @" -SELECT 'DROP ROUTINE ""' || nspname || '"".""' || proname || '""(' || oidvectortypes(proargtypes) || ');' FROM pg_proc + const string getUserDefinedFunctions = """ +SELECT 'DROP ROUTINE "' || nspname || '"."' || proname || '"(' || oidvectortypes(proargtypes) || ');' FROM pg_proc JOIN pg_namespace AS ns ON ns.oid = pg_proc.pronamespace WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND @@ -111,7 +111,8 @@ NOT EXISTS ( SELECT * FROM pg_depend AS dep WHERE dep.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_proc') AND dep.objid = pg_proc.oid AND - deptype = 'e');"; + deptype = 'e'); +"""; string dropSql; using (var cmd = new NpgsqlCommand(getUserDefinedFunctions, conn)) @@ -151,7 +152,7 @@ FROM pg_collation coll if (userDefinedTypes.Any()) { - var dropTypes = string.Concat(userDefinedTypes.Select(t => $@"DROP COLLATION ""{t.Schema}"".""{t.Name}"" CASCADE;")); + var dropTypes = string.Concat(userDefinedTypes.Select(t => $"""DROP COLLATION "{t.Schema}"."{t.Name}" CASCADE;""")); using var cmd = new NpgsqlCommand(dropTypes, conn); cmd.ExecuteNonQuery(); } diff --git a/test/EFCore.PG.FunctionalTests/UpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/UpdatesNpgsqlTest.cs index 0c4b0fc3c..b44952233 100644 --- a/test/EFCore.PG.FunctionalTests/UpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/UpdatesNpgsqlTest.cs @@ -69,7 +69,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity() .Property(p => p.Id).HasDefaultValueSql("uuid_generate_v4()"); - modelBuilder.Entity().HasIndex(p => new { p.Name, p.Price }).IsUnique().HasFilter(@"""Name"" IS NOT NULL"); + modelBuilder.Entity().HasIndex(p => new { p.Name, p.Price }).IsUnique().HasFilter(""" + "Name" IS NOT NULL + """); modelBuilder.Entity().Property(r => r.Concurrency).HasColumnType("timestamp without time zone"); } diff --git a/test/EFCore.PG.FunctionalTests/WithConstructorsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/WithConstructorsNpgsqlTest.cs index e1337b284..14ce4b0b4 100644 --- a/test/EFCore.PG.FunctionalTests/WithConstructorsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/WithConstructorsNpgsqlTest.cs @@ -17,7 +17,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con { base.OnModelCreating(modelBuilder, context); - modelBuilder.Entity().HasNoKey().ToSqlQuery(@"SELECT * FROM ""Blog"""); + modelBuilder.Entity().HasNoKey().ToSqlQuery(""" + SELECT * FROM "Blog" + """); } } } diff --git a/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs b/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs index 78e23abfb..91aa83076 100644 --- a/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs +++ b/test/EFCore.PG.Tests/Metadata/NpgsqlMetadataBuilderExtensionsTest.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; @@ -81,7 +80,7 @@ public void Can_access_index() var modelBuilder = CreateBuilder(); var entityTypeBuilder = modelBuilder.Entity(typeof(Splot)); var idProperty = entityTypeBuilder.Property(typeof(int), "Id").Metadata; - var indexBuilder = entityTypeBuilder.HasIndex(new[] { idProperty }); + var indexBuilder = entityTypeBuilder.HasIndex([idProperty]); Assert.NotNull(indexBuilder.HasMethod("gin")); Assert.Equal("gin", indexBuilder.Metadata.GetMethod()); @@ -103,7 +102,7 @@ public void Can_access_relationship() var modelBuilder = CreateBuilder(); var entityTypeBuilder = modelBuilder.Entity(typeof(Splot)); var idProperty = entityTypeBuilder.Property(typeof(int), "Id").Metadata; - var key = entityTypeBuilder.HasKey(new[] { idProperty }).Metadata; + var key = entityTypeBuilder.HasKey([idProperty]).Metadata; var relationshipBuilder = entityTypeBuilder.HasRelationship(entityTypeBuilder.Metadata, key); Assert.NotNull(relationshipBuilder.HasConstraintName("Splew")); diff --git a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs index e5ce3d183..8b72df045 100644 --- a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Diagnostics.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; diff --git a/test/EFCore.PG.Tests/Scaffolding/NpgsqlCodeGeneratorTest.cs b/test/EFCore.PG.Tests/Scaffolding/NpgsqlCodeGeneratorTest.cs index 5b2d1348b..1405216a8 100644 --- a/test/EFCore.PG.Tests/Scaffolding/NpgsqlCodeGeneratorTest.cs +++ b/test/EFCore.PG.Tests/Scaffolding/NpgsqlCodeGeneratorTest.cs @@ -12,7 +12,7 @@ public virtual void Use_provider_method_is_generated_correctly() { var codeGenerator = new NpgsqlCodeGenerator( new ProviderCodeGeneratorDependencies( - Enumerable.Empty())); + [])); var result = codeGenerator.GenerateUseProvider("Server=test;Username=test;Password=test;Database=test", providerOptions: null); @@ -28,7 +28,7 @@ public virtual void Use_provider_method_is_generated_correctly_with_options() { var codeGenerator = new NpgsqlCodeGenerator( new ProviderCodeGeneratorDependencies( - Enumerable.Empty())); + [])); var providerOptions = new MethodCallCodeFragment(_setProviderOptionMethodInfo); @@ -53,7 +53,7 @@ public virtual void Use_provider_method_is_generated_correctly_with_NetTopologyS { var codeGenerator = new NpgsqlCodeGenerator( new ProviderCodeGeneratorDependencies( - new[] { new NpgsqlNetTopologySuiteCodeGeneratorPlugin() })); + [new NpgsqlNetTopologySuiteCodeGeneratorPlugin()])); var result = ((IProviderConfigurationCodeGenerator)codeGenerator).GenerateUseProvider("Data Source=Test"); @@ -76,7 +76,7 @@ public virtual void Use_provider_method_is_generated_correctly_with_NodaTime() { var codeGenerator = new NpgsqlCodeGenerator( new ProviderCodeGeneratorDependencies( - new[] { new NpgsqlNodaTimeCodeGeneratorPlugin() })); + [new NpgsqlNodaTimeCodeGeneratorPlugin()])); var result = ((IProviderConfigurationCodeGenerator)codeGenerator).GenerateUseProvider("Data Source=Test"); diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlNodaTimeTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlNodaTimeTypeMappingTest.cs index 4fe8cc00a..1545c1348 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlNodaTimeTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlNodaTimeTypeMappingTest.cs @@ -105,7 +105,7 @@ public void GenerateSqlLiteral_returns_tsrange_literal() Assert.Equal("timestamp without time zone", mapping.SubtypeMapping.StoreType); var value = new NpgsqlRange(new LocalDateTime(2020, 1, 1, 12, 0, 0), new LocalDateTime(2020, 1, 2, 12, 0, 0)); - Assert.Equal(@"'[""2020-01-01T12:00:00"",""2020-01-02T12:00:00""]'::tsrange", mapping.GenerateSqlLiteral(value)); + Assert.Equal("""'["2020-01-01T12:00:00","2020-01-02T12:00:00"]'::tsrange""", mapping.GenerateSqlLiteral(value)); } [Fact] @@ -170,7 +170,7 @@ public void GenerateCodeLiteral_returns_ZonedDateTime_literal() var zonedDateTime = (new LocalDateTime(2018, 4, 20, 10, 31, 33, 666) + Period.FromTicks(6660)) .InZone(DateTimeZone.ForOffset(Offset.FromHours(2)), Resolvers.LenientResolver); Assert.Equal( - @"new NodaTime.ZonedDateTime(NodaTime.Instant.FromUnixTimeTicks(15242130936666660L), NodaTime.TimeZones.TzdbDateTimeZoneSource.Default.ForId(""UTC+02""))", + """new NodaTime.ZonedDateTime(NodaTime.Instant.FromUnixTimeTicks(15242130936666660L), NodaTime.TimeZones.TzdbDateTimeZoneSource.Default.ForId("UTC+02"))""", CodeLiteral(zonedDateTime)); } @@ -369,7 +369,7 @@ public void GenerateSqlLiteral_returns_tstzrange_Instant_literal() var value = new NpgsqlRange( new LocalDateTime(2020, 1, 1, 12, 0, 0).InUtc().ToInstant(), new LocalDateTime(2020, 1, 2, 12, 0, 0).InUtc().ToInstant()); - Assert.Equal(@"'[""2020-01-01T12:00:00Z"",""2020-01-02T12:00:00Z""]'::tstzrange", mapping.GenerateSqlLiteral(value)); + Assert.Equal("""'["2020-01-01T12:00:00Z","2020-01-02T12:00:00Z"]'::tstzrange""", mapping.GenerateSqlLiteral(value)); } [Fact] @@ -382,7 +382,7 @@ public void GenerateSqlLiteral_returns_tstzrange_ZonedDateTime_literal() var value = new NpgsqlRange( new LocalDateTime(2020, 1, 1, 12, 0, 0).InUtc(), new LocalDateTime(2020, 1, 2, 12, 0, 0).InUtc()); - Assert.Equal(@"'[""2020-01-01T12:00:00Z"",""2020-01-02T12:00:00Z""]'::tstzrange", mapping.GenerateSqlLiteral(value)); + Assert.Equal("""'["2020-01-01T12:00:00Z","2020-01-02T12:00:00Z"]'::tstzrange""", mapping.GenerateSqlLiteral(value)); } [Fact] @@ -395,7 +395,7 @@ public void GenerateSqlLiteral_returns_tstzrange_OffsetDateTime_literal() var value = new NpgsqlRange( new LocalDateTime(2020, 1, 1, 12, 0, 0).WithOffset(Offset.Zero), new LocalDateTime(2020, 1, 2, 12, 0, 0).WithOffset(Offset.Zero)); - Assert.Equal(@"'[""2020-01-01T12:00:00Z"",""2020-01-02T12:00:00Z""]'::tstzrange", mapping.GenerateSqlLiteral(value)); + Assert.Equal("""'["2020-01-01T12:00:00Z","2020-01-02T12:00:00Z"]'::tstzrange""", mapping.GenerateSqlLiteral(value)); } [Fact] @@ -688,7 +688,7 @@ public void OffsetTime_json(string timeString, string json) [Fact] public void Duration_is_properly_mapped() => Assert.All( - new[] { GetMapping(typeof(Duration)), GetMapping(typeof(Duration), "interval") }, + [GetMapping(typeof(Duration)), GetMapping(typeof(Duration), "interval")], m => { Assert.Equal("interval", m.StoreType); @@ -698,7 +698,7 @@ public void Duration_is_properly_mapped() [Fact] public void Period_is_properly_mapped() => Assert.All( - new[] { GetMapping(typeof(Period)), GetMapping(typeof(Period), "interval") }, + [GetMapping(typeof(Period)), GetMapping(typeof(Period), "interval")], m => { Assert.Equal("interval", m.StoreType); @@ -841,11 +841,10 @@ public void GenerateCodeLiteral_returns_DateTimezone_literal() new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), []), new RelationalTypeMappingSourceDependencies( - new IRelationalTypeMappingSourcePlugin[] - { - new NpgsqlNodaTimeTypeMappingSourcePlugin( + [ + new NpgsqlNodaTimeTypeMappingSourcePlugin( new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies())) - }), + ]), new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), new NpgsqlSingletonOptions() ); diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs index c735a165c..e95bbdd19 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -310,11 +310,10 @@ private NpgsqlTypeMappingSource CreateTypeMappingSource(Version postgresVersion new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), []), new RelationalTypeMappingSourceDependencies( - new IRelationalTypeMappingSourcePlugin[] - { - new NpgsqlNetTopologySuiteTypeMappingSourcePlugin(new NpgsqlNetTopologySuiteSingletonOptions()), + [ + new NpgsqlNetTopologySuiteTypeMappingSourcePlugin(new NpgsqlNetTopologySuiteSingletonOptions()), new DummyTypeMappingSourcePlugin() - }), + ]), new NpgsqlSqlGenerationHelper(new RelationalSqlGenerationHelperDependencies()), options); } diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs index c2d90bd2a..ea8f3663a 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs @@ -9,7 +9,6 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; -using Npgsql.NameTranslation; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage; @@ -259,7 +258,7 @@ public void GenerateSqlLiteral_returns_macaddr_literal() [Fact] public void GenerateCodeLiteral_returns_macaddr_literal() => Assert.Equal( - @"System.Net.NetworkInformation.PhysicalAddress.Parse(""001122334455"")", + """System.Net.NetworkInformation.PhysicalAddress.Parse("001122334455")""", CodeLiteral(PhysicalAddress.Parse("00-11-22-33-44-55"))); [Fact] @@ -270,7 +269,7 @@ public void GenerateSqlLiteral_returns_macaddr8_literal() [Fact] public void GenerateCodeLiteral_returns_macaddr8_literal() => Assert.Equal( - @"System.Net.NetworkInformation.PhysicalAddress.Parse(""0011223344556677"")", + """System.Net.NetworkInformation.PhysicalAddress.Parse("0011223344556677")""", CodeLiteral(PhysicalAddress.Parse("00-11-22-33-44-55-66-77"))); [Fact] @@ -279,7 +278,7 @@ public void GenerateSqlLiteral_returns_inet_literal() [Fact] public void GenerateCodeLiteral_returns_inet_literal() - => Assert.Equal(@"System.Net.IPAddress.Parse(""192.168.1.1"")", CodeLiteral(IPAddress.Parse("192.168.1.1"))); + => Assert.Equal("""System.Net.IPAddress.Parse("192.168.1.1")""", CodeLiteral(IPAddress.Parse("192.168.1.1"))); [Fact] public void GenerateSqlLiteral_returns_cidr_literal() @@ -288,7 +287,7 @@ public void GenerateSqlLiteral_returns_cidr_literal() [Fact] public void GenerateCodeLiteral_returns_cidr_literal() => Assert.Equal( - @"new NpgsqlTypes.NpgsqlCidr(System.Net.IPAddress.Parse(""192.168.1.0""), (byte)24)", + """new NpgsqlTypes.NpgsqlCidr(System.Net.IPAddress.Parse("192.168.1.0"), (byte)24)""", CodeLiteral(new NpgsqlCidr(IPAddress.Parse("192.168.1.0"), 24))); #endregion Networking @@ -402,19 +401,19 @@ public void GenerateSqlLiteral_returns_bool_literal() [Fact] public void GenerateSqlLiteral_returns_varbit_literal() - => Assert.Equal("B'10'", GetMapping("varbit").GenerateSqlLiteral(new BitArray(new[] { true, false }))); + => Assert.Equal("B'10'", GetMapping("varbit").GenerateSqlLiteral(new BitArray([true, false]))); [Fact] public void GenerateCodeLiteral_returns_varbit_literal() - => Assert.Equal("new System.Collections.BitArray(new bool[] { true, false })", CodeLiteral(new BitArray(new[] { true, false }))); + => Assert.Equal("new System.Collections.BitArray(new bool[] { true, false })", CodeLiteral(new BitArray([true, false]))); [Fact] public void GenerateSqlLiteral_returns_bit_literal() - => Assert.Equal("B'10'", GetMapping("bit").GenerateSqlLiteral(new BitArray(new[] { true, false }))); + => Assert.Equal("B'10'", GetMapping("bit").GenerateSqlLiteral(new BitArray([true, false]))); [Fact] public void GenerateCodeLiteral_returns_bit_literal() - => Assert.Equal("new System.Collections.BitArray(new bool[] { true, false })", CodeLiteral(new BitArray(new[] { true, false }))); + => Assert.Equal("new System.Collections.BitArray(new bool[] { true, false })", CodeLiteral(new BitArray([true, false]))); [Fact(Skip = "https://github.com/dotnet/efcore/pull/30939")] public void ValueComparer_hstore_array() @@ -438,7 +437,7 @@ public void GenerateSqlLiteral_returns_bytea_literal() [Fact] public void GenerateSqlLiteral_returns_hstore_literal() => Assert.Equal( - @"HSTORE '""k1""=>""v1"",""k2""=>""v2""'", + """HSTORE '"k1"=>"v1","k2"=>"v2"'""", GetMapping("hstore").GenerateSqlLiteral(new Dictionary { { "k1", "v1" }, { "k2", "v2" } })); [Fact] @@ -454,7 +453,7 @@ public void GenerateSqlLiteral_returns_BigInteger_literal() [Fact] public void GenerateCodeLiteral_returns_BigInteger_literal() => Assert.Equal( - @"BigInteger.Parse(""18446744073709551615"", NumberFormatInfo.InvariantInfo)", + """BigInteger.Parse("18446744073709551615", NumberFormatInfo.InvariantInfo)""", CodeLiteral(new BigInteger(ulong.MaxValue))); [Fact] @@ -501,13 +500,17 @@ public void GenerateSqlLiteral_returns_enum_literal() [Fact] public void GenerateSqlLiteral_returns_enum_uppercase_literal() { - var mapping = new NpgsqlEnumTypeMapping(@"""DummyEnum""", "DummyEnum", typeof(DummyEnum), new Dictionary + var mapping = new NpgsqlEnumTypeMapping(""" + "DummyEnum" + """, "DummyEnum", typeof(DummyEnum), new Dictionary { [DummyEnum.Happy] = "happy", [DummyEnum.Sad] = "sad" }); - Assert.Equal(@"'sad'::""DummyEnum""", mapping.GenerateSqlLiteral(DummyEnum.Sad)); + Assert.Equal(""" + 'sad'::"DummyEnum" + """, mapping.GenerateSqlLiteral(DummyEnum.Sad)); } private enum DummyEnum @@ -713,7 +716,7 @@ public void GenerateSqlLiteral_returns_tsrange_literal() Assert.Equal("timestamp without time zone", mapping.SubtypeMapping.StoreType); var value = new NpgsqlRange(new DateTime(2020, 1, 1, 12, 0, 0), new DateTime(2020, 1, 2, 12, 0, 0)); - Assert.Equal(@"'[""2020-01-01T12:00:00"",""2020-01-02T12:00:00""]'::tsrange", mapping.GenerateSqlLiteral(value)); + Assert.Equal("""'["2020-01-01T12:00:00","2020-01-02T12:00:00"]'::tsrange""", mapping.GenerateSqlLiteral(value)); } [Fact] @@ -724,7 +727,7 @@ public void GenerateSqlLiteral_returns_tstzrange_literal() var value = new NpgsqlRange( new DateTime(2020, 1, 1, 12, 0, 0, DateTimeKind.Utc), new DateTime(2020, 1, 2, 12, 0, 0, DateTimeKind.Utc)); - Assert.Equal(@"'[""2020-01-01T12:00:00Z"",""2020-01-02T12:00:00Z""]'::tstzrange", mapping.GenerateSqlLiteral(value)); + Assert.Equal("""'["2020-01-01T12:00:00Z","2020-01-02T12:00:00Z"]'::tstzrange""", mapping.GenerateSqlLiteral(value)); } [Fact] @@ -908,12 +911,12 @@ public void GenerateSqlLiteral_returns_json_element_literal() public void GenerateCodeLiteral_returns_json_document_literal() => Assert.Equal( """System.Text.Json.JsonDocument.Parse("{\"Name\":\"Joe\",\"Age\":25}", new System.Text.Json.JsonDocumentOptions())""", - CodeLiteral(JsonDocument.Parse(@"{""Name"":""Joe"",""Age"":25}"))); + CodeLiteral(JsonDocument.Parse("""{"Name":"Joe","Age":25}"""))); [Fact] public void GenerateCodeLiteral_returns_json_element_literal() // TODO: https://github.com/dotnet/efcore/issues/32192 - => Assert.Throws(() => CodeLiteral(JsonDocument.Parse(@"{""Name"":""Joe"",""Age"":25}").RootElement)); + => Assert.Throws(() => CodeLiteral(JsonDocument.Parse("""{"Name":"Joe","Age":25}""").RootElement)); // Assert.Equal( // """System.Text.Json.JsonDocument.Parse("{\"Name\":\"Joe\",\"Age\":25}", new System.Text.Json.JsonDocumentOptions()).RootElement""", From e31994185bee5ff05d06676c872911eeb11f8ae0 Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Tue, 3 Sep 2024 01:49:29 +0800 Subject: [PATCH 052/107] Don't use same table in join when it is also primary target table (#3263) --- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 2 +- .../NonSharedModelBulkUpdatesNpgsqlTest.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 40bbb33e8..61583bb4e 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -341,7 +341,7 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression) var table = selectExpression.Tables[i]; var joinExpression = table as JoinExpressionBase; - if (ReferenceEquals(updateExpression.Table, joinExpression?.Table ?? table)) + if (updateExpression.Table.Alias == (joinExpression?.Table.Alias ?? table.Alias)) { LiftPredicate(table); continue; diff --git a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs index af6d6cfa2..d32b3c4f7 100644 --- a/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BulkUpdates/NonSharedModelBulkUpdatesNpgsqlTest.cs @@ -50,12 +50,17 @@ DELETE FROM "Owner" AS o """); } - // TODO: #3253 public override async Task Replace_ColumnExpression_in_column_setter(bool async) { - var exception = await Assert.ThrowsAsync(() => base.Replace_ColumnExpression_in_column_setter(async)); + await base.Replace_ColumnExpression_in_column_setter(async); - Assert.Equal("42712", exception.SqlState); + AssertSql( + """ +UPDATE "OwnedCollection" AS o0 +SET "Value" = 'SomeValue' +FROM "Owner" AS o +WHERE o."Id" = o0."OwnerId" +"""); } public override async Task Delete_aggregate_root_when_table_sharing_with_non_owned_throws(bool async) From 382450ce181cdacde23a68fa567af38c68c96dea Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 13 Sep 2024 19:40:56 +0200 Subject: [PATCH 053/107] Bump Npgsql version to 8.0.4 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index fd299fb25..ac56cad17 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ [9.0.0-rc.1.24451.1] 9.0.0-rc.1.24431.7 - 8.0.3 + 8.0.4 From a20457942603c1516ecb7b9fb33d4a9349c7e33c Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 13 Sep 2024 19:42:18 +0200 Subject: [PATCH 054/107] Bump dotnet SDK to 9.0.100-rc.1.24452.12 --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- global.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c3dfafe4..62d3c4038 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: pull_request: env: - dotnet_sdk_version: '9.0.100-rc.1.24451.4' + dotnet_sdk_version: '9.0.100-rc.1.24452.12' postgis_version: 3 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 31920ab09..d8101b9f1 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ on: - cron: '30 22 * * 6' env: - dotnet_sdk_version: '9.0.100-rc.1.24451.4' + dotnet_sdk_version: '9.0.100-rc.1.24452.12' jobs: analyze: diff --git a/global.json b/global.json index 8fcf651cb..22cf64c12 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100-rc.1.24451.4", + "version": "9.0.100-rc.1.24452.12", "rollForward": "latestMajor", "allowPrerelease": true } From b818e82971220fa1ce5347d067027065d4d82cb7 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 13 Sep 2024 19:53:16 +0200 Subject: [PATCH 055/107] Bump version to 9.0.0-rc.2 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 503810c8e..cd7fb9c90 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0-rc.1 + 9.0.0-rc.2 latest true latest From b11d2790843ec5bb09ee447b302f43a5b9439972 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 13 Sep 2024 22:04:23 +0200 Subject: [PATCH 056/107] Sync to EF Core 9.0.0-rc.2.24460.3 (#3275) Closes #3223 --- Directory.Packages.props | 4 +- .../Internal/NpgsqlHistoryRepository.cs | 124 +++++++++++------- .../Migrations/Internal/NpgsqlMigrator.cs | 5 +- .../ComputedColumnTest.cs | 7 +- .../MigrationsInfrastructureNpgsqlTest.cs | 19 --- .../NpgsqlValueGenerationScenariosTest.cs | 26 ++-- .../Query/CompatibilityQueryNpgsqlTest.cs | 4 +- .../Query/JsonQueryNpgsqlTest.cs | 67 ++++++---- .../Query/QueryBugTest.cs | 2 +- .../SequenceEndToEndTest.cs | 7 +- .../SpatialNpgsqlTest.cs | 5 +- .../Update/JsonUpdateNpgsqlTest.cs | 30 ++--- 12 files changed, 161 insertions(+), 139 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ac56cad17..91a34ec82 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - [9.0.0-rc.1.24451.1] - 9.0.0-rc.1.24431.7 + [9.0.0-rc.2.24460.3] + 9.0.0-rc.2.24456.9 8.0.4 diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index 78fde2980..b075723ba 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -6,7 +6,7 @@ /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class NpgsqlHistoryRepository : HistoryRepository +public class NpgsqlHistoryRepository : HistoryRepository, IHistoryRepository { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -19,43 +19,13 @@ public NpgsqlHistoryRepository(HistoryRepositoryDependencies dependencies) { } - // TODO: We override Exists() as a workaround for https://github.com/dotnet/efcore/issues/34569; this should be fixed on the EF side - // before EF 9.0 is released - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public override bool Exists() - => Dependencies.DatabaseCreator.Exists() - && InterpretExistsResult( - Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalar( - new RelationalCommandParameterObject( - Dependencies.Connection, - null, - null, - Dependencies.CurrentContext.Context, - Dependencies.CommandLogger, CommandSource.Migrations))); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override async Task ExistsAsync(CancellationToken cancellationToken = default) - => await Dependencies.DatabaseCreator.ExistsAsync(cancellationToken).ConfigureAwait(false) - && InterpretExistsResult( - await Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalarAsync( - new RelationalCommandParameterObject( - Dependencies.Connection, - null, - null, - Dependencies.CurrentContext.Context, - Dependencies.CommandLogger, CommandSource.Migrations), - cancellationToken).ConfigureAwait(false)); + public override LockReleaseBehavior LockReleaseBehavior => LockReleaseBehavior.Transaction; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -63,16 +33,15 @@ await Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalarAsync( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override IDisposable GetDatabaseLock() + public override IMigrationsDatabaseLock AcquireDatabaseLock() { - // TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a - // transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. + Dependencies.MigrationsLogger.AcquiringMigrationLock(); - // Dependencies.RawSqlCommandBuilder - // .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") - // .ExecuteNonQuery(CreateRelationalCommandParameters()); + Dependencies.RawSqlCommandBuilder + .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") + .ExecuteNonQuery(CreateRelationalCommandParameters()); - return new DummyDisposable(); + return new NpgsqlMigrationDatabaseLock(this); } /// @@ -81,19 +50,24 @@ public override IDisposable GetDatabaseLock() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override Task GetDatabaseLockAsync(CancellationToken cancellationToken = default) + public override async Task AcquireDatabaseLockAsync(CancellationToken cancellationToken = default) { - // TODO: There are issues with the current lock implementation in EF - most importantly, the lock isn't acquired within a - // transaction so we can't use e.g. LOCK TABLE. This should be fixed for rc.1, see #34439. - - // await Dependencies.RawSqlCommandBuilder - // .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") - // .ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken) - // .ConfigureAwait(false); + await Dependencies.RawSqlCommandBuilder + .Build($"LOCK TABLE {Dependencies.SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} IN ACCESS EXCLUSIVE MODE") + .ExecuteNonQueryAsync(CreateRelationalCommandParameters(), cancellationToken) + .ConfigureAwait(false); - return Task.FromResult(new DummyDisposable()); + return new NpgsqlMigrationDatabaseLock(this); } + private RelationalCommandParameterObject CreateRelationalCommandParameters() + => new( + Dependencies.Connection, + null, + null, + Dependencies.CurrentContext.Context, + Dependencies.CommandLogger, CommandSource.Migrations); + /// /// 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 @@ -127,6 +101,48 @@ SELECT 1 FROM pg_catalog.pg_class c protected override bool InterpretExistsResult(object? value) => (bool?)value == true; + bool IHistoryRepository.CreateIfNotExists() + { + // In PG, doing CREATE TABLE IF NOT EXISTS concurrently can result in a unique constraint violation + // (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). We catch this and report that the table wasn't + // created. + try + { + return Dependencies.MigrationCommandExecutor.ExecuteNonQuery( + GetCreateIfNotExistsCommands(), Dependencies.Connection, new MigrationExecutionState(), commitTransaction: true) + != 0; + } + catch (PostgresException e) when (e.SqlState == "23505") + { + return false; + } + } + + async Task IHistoryRepository.CreateIfNotExistsAsync(CancellationToken cancellationToken) + { + // In PG, doing CREATE TABLE IF NOT EXISTS concurrently can result in a unique constraint violation + // (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). We catch this and report that the table wasn't + // created. + try + { + return (await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync( + GetCreateIfNotExistsCommands(), Dependencies.Connection, new MigrationExecutionState(), commitTransaction: true, + cancellationToken: cancellationToken).ConfigureAwait(false)) + != 0; + } + catch (PostgresException e) when (e.SqlState == "23505") + { + return false; + } + } + + private IReadOnlyList GetCreateIfNotExistsCommands() + => Dependencies.MigrationsSqlGenerator.Generate([new SqlOperation + { + Sql = GetCreateIfNotExistsScript(), + SuppressTransaction = true + }]); + /// /// 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 @@ -136,7 +152,7 @@ protected override bool InterpretExistsResult(object? value) public override string GetCreateIfNotExistsScript() { var script = GetCreateScript(); - return script.Insert(script.IndexOf("CREATE TABLE", StringComparison.Ordinal) + 12, " IF NOT EXISTS"); + return script.Replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS"); } /// @@ -178,8 +194,16 @@ public override string GetEndIfScript() END $EF$; """; - private sealed class DummyDisposable : IDisposable, IAsyncDisposable + private sealed class NpgsqlMigrationDatabaseLock(IHistoryRepository historyRepository) : IMigrationsDatabaseLock { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public IHistoryRepository HistoryRepository => historyRepository; + public void Dispose() { } diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs index cf8d222c5..0d32f58c0 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs @@ -39,10 +39,11 @@ public NpgsqlMigrator( IDatabaseProvider databaseProvider, IMigrationsModelDiffer migrationsModelDiffer, IDesignTimeModel designTimeModel, - IDbContextOptions contextOptions) + IDbContextOptions contextOptions, + IExecutionStrategy executionStrategy) : base(migrationsAssembly, historyRepository, databaseCreator, migrationsSqlGenerator, rawSqlCommandBuilder, migrationCommandExecutor, connection, sqlGenerationHelper, currentContext, modelRuntimeInitializer, logger, - commandLogger, databaseProvider, migrationsModelDiffer, designTimeModel, contextOptions) + commandLogger, databaseProvider, migrationsModelDiffer, designTimeModel, contextOptions, executionStrategy) { _historyRepository = historyRepository; _connection = connection; diff --git a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs index 0e1b399c3..5c3c86cab 100644 --- a/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs +++ b/test/EFCore.PG.FunctionalTests/ComputedColumnTest.cs @@ -139,9 +139,6 @@ public void Can_use_computed_columns_with_nullable_enum() public async Task InitializeAsync() => TestStore = await NpgsqlTestStore.CreateInitializedAsync("ComputedColumnTest"); - public Task DisposeAsync() - { - TestStore.Dispose(); - return Task.CompletedTask; - } + public async Task DisposeAsync() + => await TestStore.DisposeAsync(); } diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs index ba84d9a0a..c87d9507a 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs @@ -7,25 +7,6 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations public class MigrationsInfrastructureNpgsqlTest(MigrationsInfrastructureNpgsqlTest.MigrationsInfrastructureNpgsqlFixture fixture) : MigrationsInfrastructureTestBase(fixture) { - // TODO: The following test the migration lock, which isn't yet implemented - waiting for EF-side fixes in rc.2 - #region Unskip for 9.0.0-rc.2 - - public override void Can_apply_one_migration_in_parallel() - { - } - - public override Task Can_apply_one_migration_in_parallel_async() - => Task.CompletedTask; - - public override void Can_apply_second_migration_in_parallel() - { - } - - public override Task Can_apply_second_migration_in_parallel_async() - => Task.CompletedTask; - - #endregion Unskip for 9.0.0-rc.2 - public override void Can_get_active_provider() { base.Can_get_active_provider(); diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs index 1cdb9aed7..bf6b1ca0d 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlValueGenerationScenariosTest.cs @@ -10,7 +10,7 @@ public class NpgsqlValueGenerationScenariosTest [Fact] public async Task Insert_with_sequence_id() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextSequence(testStore.Name)) { @@ -35,7 +35,7 @@ public class BlogContextSequence(string databaseName) : ContextBase(databaseName [Fact] public async Task Insert_with_sequence_HiLo() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextHiLo(testStore.Name)) { @@ -68,7 +68,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) [Fact] public async Task Insert_with_default_value_from_sequence() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextDefaultValue(testStore.Name)) { @@ -146,7 +146,7 @@ public class BlogWithStringKey [Fact] public async Task Insert_with_key_default_value_from_sequence() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextKeyColumnWithDefaultValue(testStore.Name)) { @@ -187,7 +187,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) [ConditionalFact] public async Task Insert_uint_to_Identity_column_using_value_converter() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextUIntToIdentityUsingValueConverter(testStore.Name)) { context.Database.EnsureCreatedResiliently(); @@ -231,7 +231,7 @@ public class BlogWithUIntKey [ConditionalFact] public async Task Insert_string_to_Identity_column_using_value_converter() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextStringToIdentityUsingValueConverter(testStore.Name)) { context.Database.EnsureCreatedResiliently(); @@ -276,7 +276,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) [Fact] public async Task Insert_with_explicit_non_default_keys() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNoKeyGeneration(testStore.Name)) { @@ -312,7 +312,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) [Fact] public async Task Insert_with_explicit_with_default_keys() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNoKeyGenerationNullableKey(testStore.Name)) { @@ -348,7 +348,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) [Fact] public async Task Insert_with_non_key_default_value() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNonKeyDefaultValue(testStore.Name)) { @@ -401,7 +401,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) [Fact] public async Task Insert_with_non_key_default_value_readonly() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); using (var context = new BlogContextNonKeyReadOnlyDefaultValue(testStore.Name)) { @@ -455,7 +455,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) [Fact] public async Task Insert_with_serial_non_id() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); int afterSave; @@ -493,7 +493,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) [Fact] public async Task Insert_with_client_generated_GUID_key() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); Guid afterSave; using (var context = new BlogContext(testStore.Name)) @@ -518,7 +518,7 @@ public class BlogContext(string databaseName) : ContextBase(databaseName); [Fact] public async Task Insert_with_server_generated_GUID_key() { - using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); + await using var testStore = await NpgsqlTestStore.CreateInitializedAsync(DatabaseName); Guid afterSave; using (var context = new BlogContextServerGuidKey(testStore.Name)) diff --git a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs index ebf72bbb9..0e20d87fc 100644 --- a/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/CompatibilityQueryNpgsqlTest.cs @@ -84,8 +84,8 @@ public virtual void Dispose() { } - public virtual Task DisposeAsync() - => _testStore.DisposeAsync(); + public virtual async Task DisposeAsync() + => await _testStore.DisposeAsync(); } public class CompatibilityTestEntity diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs index e4706b39b..d1e3305b6 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonQueryNpgsqlTest.cs @@ -1152,6 +1152,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBra "Date" timestamp without time zone, "Enum" integer, "Fraction" numeric(18,2), + "Id" integer, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o WHERE o."Enum" = -3 @@ -1194,6 +1195,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBra "Date" timestamp without time zone, "Enum" integer, "Fraction" numeric(18,2), + "Id" integer, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o ORDER BY o."Date" DESC NULLS LAST @@ -1215,12 +1217,13 @@ public override async Task Json_collection_Distinct_Count_with_predicate(bool as WHERE ( SELECT count(*)::int FROM ( - SELECT DISTINCT j."Id", o."Date", o."Enum", o."Enums", o."Fraction", o."NullableEnum", o."NullableEnums", o."OwnedCollectionLeaf" AS c, o."OwnedReferenceLeaf" AS c0 + SELECT DISTINCT j."Id", o."Date", o."Enum", o."Enums", o."Fraction", o."Id" AS "Id0", o."NullableEnum", o."NullableEnums", o."OwnedCollectionLeaf" AS c, o."OwnedReferenceLeaf" AS c0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( "Date" timestamp without time zone, "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), + "Id" integer, "NullableEnum" integer, "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, @@ -1249,6 +1252,7 @@ FROM ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), + "Id" integer, "NullableEnum" integer, "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, @@ -1266,6 +1270,7 @@ public override async Task Json_collection_in_projection_with_composition_count( SELECT ( SELECT count(*)::int FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -1287,6 +1292,7 @@ public override async Task Json_collection_in_projection_with_anonymous_projecti SELECT j."Id", o."Name", o."Number", o.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Number" integer )) WITH ORDINALITY AS o ON TRUE @@ -1305,6 +1311,7 @@ public override async Task Json_collection_in_projection_with_composition_where_ LEFT JOIN LATERAL ( SELECT o."Name", o."Number", o.ordinality FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Number" integer )) WITH ORDINALITY AS o @@ -1325,6 +1332,7 @@ public override async Task Json_collection_in_projection_with_composition_where_ LEFT JOIN LATERAL ( SELECT o."Names", o."Numbers", o.ordinality FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -1342,11 +1350,12 @@ public override async Task Json_collection_filter_in_projection(bool async) AssertSql( """ -SELECT j."Id", o0."Id", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0, o0.ordinality +SELECT j."Id", o0."Id", o0."Id0", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0, o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( - SELECT j."Id", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0, o.ordinality + SELECT j."Id", o."Id" AS "Id0", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0, o.ordinality FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -1366,18 +1375,19 @@ public override async Task Json_nested_collection_filter_in_projection(bool asyn AssertSql( """ -SELECT j."Id", s.ordinality, s."Id", s."Date", s."Enum", s."Enums", s."Fraction", s."NullableEnum", s."NullableEnums", s.c, s.c0, s.ordinality0 +SELECT j."Id", s.ordinality, s."Id", s."Date", s."Enum", s."Enums", s."Fraction", s."Id0", s."NullableEnum", s."NullableEnums", s.c, s.c0, s.ordinality0 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( - SELECT o.ordinality, o1."Id", o1."Date", o1."Enum", o1."Enums", o1."Fraction", o1."NullableEnum", o1."NullableEnums", o1.c, o1.c0, o1.ordinality AS ordinality0 + SELECT o.ordinality, o1."Id", o1."Date", o1."Enum", o1."Enums", o1."Fraction", o1."Id0", o1."NullableEnum", o1."NullableEnums", o1.c, o1.c0, o1.ordinality AS ordinality0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedCollectionBranch" jsonb)) WITH ORDINALITY AS o LEFT JOIN LATERAL ( - SELECT j."Id", o0."Date", o0."Enum", o0."Enums", o0."Fraction", o0."NullableEnum", o0."NullableEnums", o0."OwnedCollectionLeaf" AS c, o0."OwnedReferenceLeaf" AS c0, o0.ordinality + SELECT j."Id", o0."Date", o0."Enum", o0."Enums", o0."Fraction", o0."Id" AS "Id0", o0."NullableEnum", o0."NullableEnums", o0."OwnedCollectionLeaf" AS c, o0."OwnedReferenceLeaf" AS c0, o0.ordinality FROM ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( "Date" timestamp without time zone, "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), + "Id" integer, "NullableEnum" integer, "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, @@ -1406,6 +1416,7 @@ LEFT JOIN LATERAL ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), + "Id" integer, "OwnedCollectionLeaf" jsonb, "OwnedReferenceLeaf" jsonb )) WITH ORDINALITY AS o0 ON TRUE @@ -1420,11 +1431,12 @@ public override async Task Json_collection_skip_take_in_projection(bool async) AssertSql( """ -SELECT j."Id", o0."Id", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0, o0.ordinality +SELECT j."Id", o0."Id", o0."Id0", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0, o0.ordinality FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( - SELECT j."Id", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0, o.ordinality, o."Name" AS c1 + SELECT j."Id", o."Id" AS "Id0", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0, o.ordinality, o."Name" AS c1 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -1450,6 +1462,7 @@ public override async Task Json_collection_skip_take_in_projection_project_into_ LEFT JOIN LATERAL ( SELECT o."Name" AS c, o."Names" AS c0, o."Number" AS c1, o."Numbers" AS c2, o."OwnedCollectionBranch" AS c3, j."Id", o."OwnedReferenceBranch" AS c4, o.ordinality FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -1475,6 +1488,7 @@ public override async Task Json_collection_skip_take_in_projection_with_json_ref LEFT JOIN LATERAL ( SELECT o."OwnedReferenceBranch" AS c, j."Id", o.ordinality, o."Name" AS c0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Number" integer, "OwnedReferenceBranch" jsonb @@ -1492,11 +1506,12 @@ public override async Task Json_collection_distinct_in_projection(bool async) AssertSql( """ -SELECT j."Id", o0."Id", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0 +SELECT j."Id", o0."Id", o0."Id0", o0."Name", o0."Names", o0."Number", o0."Numbers", o0.c, o0.c0 FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( - SELECT DISTINCT j."Id", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0 + SELECT DISTINCT j."Id", o."Id" AS "Id0", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch" AS c, o."OwnedReferenceBranch" AS c0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -1505,7 +1520,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( "OwnedReferenceBranch" jsonb )) WITH ORDINALITY AS o ) AS o0 ON TRUE -ORDER BY j."Id" NULLS FIRST, o0."Name" NULLS FIRST, o0."Names" NULLS FIRST, o0."Number" NULLS FIRST +ORDER BY j."Id" NULLS FIRST, o0."Id0" NULLS FIRST, o0."Name" NULLS FIRST, o0."Names" NULLS FIRST, o0."Number" NULLS FIRST """); } @@ -1539,7 +1554,7 @@ public override async Task Json_multiple_collection_projections(bool async) AssertSql( """ -SELECT j."Id", o4."Id", o4."SomethingSomething", o4.ordinality, o1."Id", o1."Name", o1."Names", o1."Number", o1."Numbers", o1.c, o1.c0, s.ordinality, s."Id", s."Date", s."Enum", s."Enums", s."Fraction", s."NullableEnum", s."NullableEnums", s.c, s.c0, s.ordinality0, j0."Id", j0."Name", j0."ParentId" +SELECT j."Id", o4."Id", o4."SomethingSomething", o4.ordinality, o1."Id", o1."Id0", o1."Name", o1."Names", o1."Number", o1."Numbers", o1.c, o1.c0, s.ordinality, s."Id", s."Date", s."Enum", s."Enums", s."Fraction", s."Id0", s."NullableEnum", s."NullableEnums", s.c, s.c0, s.ordinality0, j0."Id", j0."Name", j0."ParentId" FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( SELECT j."Id", o."SomethingSomething", o.ordinality @@ -1547,8 +1562,9 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" #> '{OwnedReferenceBra WHERE o."SomethingSomething" <> 'Baz' OR o."SomethingSomething" IS NULL ) AS o4 ON TRUE LEFT JOIN LATERAL ( - SELECT DISTINCT j."Id", o0."Name", o0."Names", o0."Number", o0."Numbers", o0."OwnedCollectionBranch" AS c, o0."OwnedReferenceBranch" AS c0 + SELECT DISTINCT j."Id", o0."Id" AS "Id0", o0."Name", o0."Names", o0."Number", o0."Numbers", o0."OwnedCollectionBranch" AS c, o0."OwnedReferenceBranch" AS c0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -1558,15 +1574,16 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( )) WITH ORDINALITY AS o0 ) AS o1 ON TRUE LEFT JOIN LATERAL ( - SELECT o2.ordinality, o5."Id", o5."Date", o5."Enum", o5."Enums", o5."Fraction", o5."NullableEnum", o5."NullableEnums", o5.c, o5.c0, o5.ordinality AS ordinality0 + SELECT o2.ordinality, o5."Id", o5."Date", o5."Enum", o5."Enums", o5."Fraction", o5."Id0", o5."NullableEnum", o5."NullableEnums", o5.c, o5.c0, o5.ordinality AS ordinality0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ("OwnedCollectionBranch" jsonb)) WITH ORDINALITY AS o2 LEFT JOIN LATERAL ( - SELECT j."Id", o3."Date", o3."Enum", o3."Enums", o3."Fraction", o3."NullableEnum", o3."NullableEnums", o3."OwnedCollectionLeaf" AS c, o3."OwnedReferenceLeaf" AS c0, o3.ordinality + SELECT j."Id", o3."Date", o3."Enum", o3."Enums", o3."Fraction", o3."Id" AS "Id0", o3."NullableEnum", o3."NullableEnums", o3."OwnedCollectionLeaf" AS c, o3."OwnedReferenceLeaf" AS c0, o3.ordinality FROM ROWS FROM (jsonb_to_recordset(o2."OwnedCollectionBranch") AS ( "Date" timestamp without time zone, "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), + "Id" integer, "NullableEnum" integer, "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, @@ -1576,7 +1593,7 @@ FROM ROWS FROM (jsonb_to_recordset(o2."OwnedCollectionBranch") AS ( ) AS o5 ON TRUE ) AS s ON TRUE LEFT JOIN "JsonEntitiesBasicForCollection" AS j0 ON j."Id" = j0."ParentId" -ORDER BY j."Id" NULLS FIRST, o4.ordinality NULLS FIRST, o1."Name" NULLS FIRST, o1."Names" NULLS FIRST, o1."Number" NULLS FIRST, o1."Numbers" NULLS FIRST, s.ordinality NULLS FIRST, s.ordinality0 NULLS FIRST +ORDER BY j."Id" NULLS FIRST, o4.ordinality NULLS FIRST, o1."Id0" NULLS FIRST, o1."Name" NULLS FIRST, o1."Names" NULLS FIRST, o1."Number" NULLS FIRST, o1."Numbers" NULLS FIRST, s.ordinality NULLS FIRST, s.ordinality0 NULLS FIRST """); } @@ -1586,15 +1603,16 @@ public override async Task Json_branch_collection_distinct_and_other_collection( AssertSql( """ -SELECT j."Id", o0."Id", o0."Date", o0."Enum", o0."Enums", o0."Fraction", o0."NullableEnum", o0."NullableEnums", o0.c, o0.c0, j0."Id", j0."Name", j0."ParentId" +SELECT j."Id", o0."Id", o0."Date", o0."Enum", o0."Enums", o0."Fraction", o0."Id0", o0."NullableEnum", o0."NullableEnums", o0.c, o0.c0, j0."Id", j0."Name", j0."ParentId" FROM "JsonEntitiesBasic" AS j LEFT JOIN LATERAL ( - SELECT DISTINCT j."Id", o."Date", o."Enum", o."Enums", o."Fraction", o."NullableEnum", o."NullableEnums", o."OwnedCollectionLeaf" AS c, o."OwnedReferenceLeaf" AS c0 + SELECT DISTINCT j."Id", o."Date", o."Enum", o."Enums", o."Fraction", o."Id" AS "Id0", o."NullableEnum", o."NullableEnums", o."OwnedCollectionLeaf" AS c, o."OwnedReferenceLeaf" AS c0 FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( "Date" timestamp without time zone, "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), + "Id" integer, "NullableEnum" integer, "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, @@ -1602,7 +1620,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBra )) WITH ORDINALITY AS o ) AS o0 ON TRUE LEFT JOIN "JsonEntitiesBasicForCollection" AS j0 ON j."Id" = j0."ParentId" -ORDER BY j."Id" NULLS FIRST, o0."Date" NULLS FIRST, o0."Enum" NULLS FIRST, o0."Enums" NULLS FIRST, o0."Fraction" NULLS FIRST, o0."NullableEnum" NULLS FIRST, o0."NullableEnums" NULLS FIRST +ORDER BY j."Id" NULLS FIRST, o0."Date" NULLS FIRST, o0."Enum" NULLS FIRST, o0."Enums" NULLS FIRST, o0."Fraction" NULLS FIRST, o0."Id0" NULLS FIRST, o0."NullableEnum" NULLS FIRST, o0."NullableEnums" NULLS FIRST """); } @@ -1629,9 +1647,10 @@ public override async Task Json_collection_SelectMany(bool async) AssertSql( """ -SELECT j."Id", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch", o."OwnedReferenceBranch" +SELECT j."Id", o."Id", o."Name", o."Names", o."Number", o."Numbers", o."OwnedCollectionBranch", o."OwnedReferenceBranch" FROM "JsonEntitiesBasic" AS j JOIN LATERAL ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -1648,13 +1667,14 @@ public override async Task Json_nested_collection_SelectMany(bool async) AssertSql( """ -SELECT j."Id", o."Date", o."Enum", o."Enums", o."Fraction", o."NullableEnum", o."NullableEnums", o."OwnedCollectionLeaf", o."OwnedReferenceLeaf" +SELECT j."Id", o."Date", o."Enum", o."Enums", o."Fraction", o."Id", o."NullableEnum", o."NullableEnums", o."OwnedCollectionLeaf", o."OwnedReferenceLeaf" FROM "JsonEntitiesBasic" AS j JOIN LATERAL ROWS FROM (jsonb_to_recordset(j."OwnedReferenceRoot" -> 'OwnedCollectionBranch') AS ( "Date" timestamp without time zone, "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), + "Id" integer, "NullableEnum" integer, "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, @@ -1738,6 +1758,7 @@ FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot" #> ARRAY[@__prm_0,'Ow "Enum" integer, "Enums" integer[], "Fraction" numeric(18,2), + "Id" integer, "NullableEnum" integer, "NullableEnums" integer[], "OwnedCollectionLeaf" jsonb, @@ -1811,6 +1832,7 @@ public override async Task Json_collection_Select_entity_with_initializer_Elemen LEFT JOIN LATERAL ( SELECT j."Id", 1 AS c FROM ROWS FROM (jsonb_to_recordset(j."OwnedCollectionRoot") AS ( + "Id" integer, "Name" text, "Names" text[], "Number" integer, @@ -3231,7 +3253,8 @@ LEFT JOIN LATERAL ROWS FROM (jsonb_to_recordset(o."OwnedCollectionBranch") AS ( "Date" timestamp without time zone, "Enum" integer, "Enums" integer[], - "Fraction" numeric(18,2) + "Fraction" numeric(18,2), + "Id" integer )) WITH ORDINALITY AS o0 ON TRUE ) AS s ON TRUE ORDER BY j."Id" NULLS FIRST, s.ordinality NULLS FIRST diff --git a/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs b/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs index 26bb1e04d..3dd78fa0d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/QueryBugTest.cs @@ -22,7 +22,7 @@ public QueryBugsTest(NpgsqlFixture fixture, ITestOutputHelper testOutputHelper) [Fact] public async Task Bug920() { - using var _ = await CreateDatabase920Async(); + await using var _ = await CreateDatabase920Async(); using var context = new Bug920Context(_options); context.Entities.Add(new Bug920Entity { Enum = Bug920Enum.Two }); context.SaveChanges(); diff --git a/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs b/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs index 21a0e97fe..715fa78fa 100644 --- a/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs +++ b/test/EFCore.PG.FunctionalTests/SequenceEndToEndTest.cs @@ -394,9 +394,6 @@ private class Unicon public async Task InitializeAsync() => TestStore = await NpgsqlTestStore.CreateInitializedAsync("SequenceEndToEndTest"); - public Task DisposeAsync() - { - TestStore.Dispose(); - return Task.CompletedTask; - } + public async Task DisposeAsync() + => await TestStore.DisposeAsync(); } diff --git a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs index fa81d3a87..dc949e01d 100644 --- a/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/SpatialNpgsqlTest.cs @@ -10,7 +10,6 @@ protected override void UseTransaction(DatabaseFacade facade, IDbContextTransact // This test requires DbConnection to be used with the test store, but SpatialNpgsqlFixture must set useConnectionString to true // in order to properly set up the NetTopologySuite internally with the data source. - public override void Mutation_of_tracked_values_does_not_mutate_values_in_store() - { - } + public override Task Mutation_of_tracked_values_does_not_mutate_values_in_store() + => Assert.ThrowsAsync(() => base.Mutation_of_tracked_values_does_not_mutate_values_in_store()); } diff --git a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs index 4db135758..e3c8a983a 100644 --- a/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Update/JsonUpdateNpgsqlTest.cs @@ -18,7 +18,7 @@ public override async Task Add_element_to_json_collection_branch() AssertSql( """ -@p0='[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":10.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (DbType = Object) +@p0='[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":10.1,"Id":89,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"Id":90,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"Id":77,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedCollectionBranch}', @p0) @@ -58,7 +58,7 @@ public override async Task Add_element_to_json_collection_on_derived() AssertSql( """ -@p0='[{"Date":"2221-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":221.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2222-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":222.1,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (DbType = Object) +@p0='[{"Date":"2221-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":221.1,"Id":104,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2222-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":222.1,"Id":105,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"d2_r_c1"},{"SomethingSomething":"d2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"d2_r_r"}},{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"Id":77,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}]' (Nullable = false) (DbType = Object) @p1='2' UPDATE "JsonEntitiesInheritance" SET "CollectionOnDerived" = @p0 @@ -79,7 +79,7 @@ public override async Task Add_element_to_json_collection_root() AssertSql( """ -@p0='[{"Name":"e1_c1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"e1_c2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Name":"new Name","Names":null,"Number":142,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}]' (Nullable = false) (DbType = Object) +@p0='[{"Id":0,"Name":"e1_c1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"Id":92,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"Id":93,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"Id":91,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Id":0,"Name":"e1_c2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"Id":95,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"Id":96,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"Id":94,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Id":0,"Name":"new Name","Names":null,"Number":142,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"Id":7,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 @@ -99,7 +99,7 @@ public override async Task Add_element_to_json_collection_root_null_navigations( AssertSql( """ -@p0='[{"Name":"e1_c1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"e1_c2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Name":"new Name","Names":null,"Number":142,"Numbers":null,"OwnedCollectionBranch":null,"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":null,"OwnedReferenceLeaf":null}}]' (Nullable = false) (DbType = Object) +@p0='[{"Id":0,"Name":"e1_c1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"Id":92,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"Id":93,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"Id":91,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Id":0,"Name":"e1_c2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"Id":95,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"Id":96,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"Id":94,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}},{"Id":0,"Name":"new Name","Names":null,"Number":142,"Numbers":null,"OwnedCollectionBranch":null,"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"Id":7,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":null,"OwnedReferenceLeaf":null}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 @@ -119,7 +119,7 @@ public override async Task Add_entity_with_json() AssertSql( """ -@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (DbType = Object) +@p0='{"Id":0,"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"Id":7,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (DbType = Object) @p1='[]' (Nullable = false) (DbType = Object) @p2='2' @p3=NULL (DbType = Int32) @@ -141,7 +141,7 @@ public override async Task Add_entity_with_json_null_navigations() AssertSql( """ -@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":null,"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":null}}' (Nullable = false) (DbType = Object) +@p0='{"Id":0,"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":null,"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"Id":7,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":null}}' (Nullable = false) (DbType = Object) @p1='2' @p2=NULL (DbType = Int32) @p3='NewEntity' @@ -182,7 +182,7 @@ public override async Task Add_json_reference_root() AssertSql( """ -@p0='{"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (DbType = Object) +@p0='{"Id":0,"Name":"RootName","Names":null,"Number":42,"Numbers":null,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2010-10-10T00:00:00","Enum":-3,"Enums":null,"Fraction":42.42,"Id":7,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":[{"SomethingSomething":"ss1"},{"SomethingSomething":"ss2"}],"OwnedReferenceLeaf":{"SomethingSomething":"ss3"}}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = @p0 @@ -360,8 +360,8 @@ public override async Task Edit_element_in_json_multiple_levels_partial_update() AssertSql( """ -@p0='[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"...and another"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"yet another change"},{"SomethingSomething":"and another"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}]' (Nullable = false) (DbType = Object) -@p1='{"Name":"edit","Names":["e1_r1","e1_r2"],"Number":10,"Numbers":[-2147483648,-1,0,1,2147483647],"OwnedCollectionBranch":[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":10.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}}],"OwnedReferenceBranch":{"Date":"2111-11-11T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":10.0,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}}' (Nullable = false) (DbType = Object) +@p0='[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"Id":92,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"...and another"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"Id":93,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"yet another change"},{"SomethingSomething":"and another"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}]' (Nullable = false) (DbType = Object) +@p1='{"Id":0,"Name":"edit","Names":["e1_r1","e1_r2"],"Number":10,"Numbers":[-2147483648,-1,0,1,2147483647],"OwnedCollectionBranch":[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":10.1,"Id":89,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"Id":90,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}}],"OwnedReferenceBranch":{"Date":"2111-11-11T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":10.0,"Id":88,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}}' (Nullable = false) (DbType = Object) @p2='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = jsonb_set("OwnedCollectionRoot", '{0,OwnedCollectionBranch}', @p0), "OwnedReferenceRoot" = @p1 @@ -381,7 +381,7 @@ public override async Task Edit_element_in_json_branch_collection_and_add_elemen AssertSql( """ -@p0='[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":4321.3,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2222-11-11T00:00:00","Enum":-3,"Enums":null,"Fraction":45.32,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":null,"OwnedReferenceLeaf":{"SomethingSomething":"cc"}}]' (Nullable = false) (DbType = Object) +@p0='[{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":4321.3,"Id":89,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c1_c1"},{"SomethingSomething":"e1_r_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c1_r"}},{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"Id":90,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_c2_c1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_c2_r"}},{"Date":"2222-11-11T00:00:00","Enum":-3,"Enums":null,"Fraction":45.32,"Id":77,"NullableEnum":null,"NullableEnums":null,"OwnedCollectionLeaf":null,"OwnedReferenceLeaf":{"SomethingSomething":"cc"}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedCollectionBranch}', @p0) @@ -421,7 +421,7 @@ public override async Task Edit_two_elements_in_the_same_json_collection_at_the_ AssertSql( """ -@p0='[{"Name":"edit1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Name":"edit2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}}]' (Nullable = false) (DbType = Object) +@p0='[{"Id":0,"Name":"edit1","Names":["e1_c11","e1_c12"],"Number":11,"Numbers":[-1000,0,1000],"OwnedCollectionBranch":[{"Date":"2111-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":11.1,"Id":92,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c1_c1"},{"SomethingSomething":"e1_c1_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c1_r"}},{"Date":"2112-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":11.2,"Id":93,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_c2_c1"},{"SomethingSomething":"e1_c1_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_c2_r"}}],"OwnedReferenceBranch":{"Date":"2110-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":11.0,"Id":91,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c1_r_c1"},{"SomethingSomething":"e1_c1_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c1_r_r"}}},{"Id":0,"Name":"edit2","Names":["e1_c21","e1_c22"],"Number":12,"Numbers":[-1001,0,1001],"OwnedCollectionBranch":[{"Date":"2121-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":12.1,"Id":95,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c1_c1"},{"SomethingSomething":"e1_c2_c1_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c1_r"}},{"Date":"2122-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":12.2,"Id":96,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_c2_c1"},{"SomethingSomething":"e1_c2_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_c2_r"}}],"OwnedReferenceBranch":{"Date":"2120-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":12.0,"Id":94,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_c2_r_c1"},{"SomethingSomething":"e1_c2_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_c2_r_r"}}}]' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedCollectionRoot" = @p0 @@ -441,7 +441,7 @@ public override async Task Edit_collection_element_and_reference_at_once() AssertSql( """ -@p0='{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"edit1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit2"}}' (Nullable = false) (DbType = Object) +@p0='{"Date":"2102-01-01T00:00:00","Enum":-3,"Enums":[-1,-1,2],"Fraction":10.2,"Id":90,"NullableEnum":2,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"edit1"},{"SomethingSomething":"e1_r_c2_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit2"}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedCollectionBranch,1}', @p0) @@ -1118,7 +1118,7 @@ public override async Task Edit_a_scalar_property_and_reference_navigation_on_th AssertSql( """ -@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (DbType = Object) +@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"Id":88,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedReferenceBranch}', @p0) @@ -1138,7 +1138,7 @@ public override async Task Edit_a_scalar_property_and_collection_navigation_on_t AssertSql( """ -@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"edit"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}' (Nullable = false) (DbType = Object) +@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"Id":88,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"edit"}],"OwnedReferenceLeaf":{"SomethingSomething":"e1_r_r_r"}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedReferenceBranch}', @p0) @@ -1158,7 +1158,7 @@ public override async Task Edit_a_scalar_property_and_another_property_behind_re AssertSql( """ -@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (DbType = Object) +@p0='{"Date":"2100-01-01T00:00:00","Enum":-1,"Enums":[-1,-1,2],"Fraction":523.532,"Id":88,"NullableEnum":null,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"edit"}}' (Nullable = false) (DbType = Object) @p1='1' UPDATE "JsonEntitiesBasic" SET "OwnedReferenceRoot" = jsonb_set("OwnedReferenceRoot", '{OwnedReferenceBranch}', @p0) From f7d79101d46552569450f08df10fe27023ce2d41 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 14 Sep 2024 09:33:51 +0200 Subject: [PATCH 057/107] Negated syntax for regex and ILIKE (#3276) Closes #2589 --- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 50 ++++++++++++------ .../NorthwindDbFunctionsQueryNpgsqlTest.cs | 2 +- .../NorthwindFunctionsQueryNpgsqlTest.cs | 32 ++++++++++++ .../NorthwindMiscellaneousQueryNpgsqlTest.cs | 51 +++++++++++++++++++ 4 files changed, 119 insertions(+), 16 deletions(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 61583bb4e..3418d5045 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -614,11 +614,29 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio Visit(sqlUnaryExpression.Operand); return sqlUnaryExpression; - // Not operation on full-text queries + // NOT operation on full-text queries case ExpressionType.Not when sqlUnaryExpression.Operand.TypeMapping.ClrType == typeof(NpgsqlTsQuery): Sql.Append("!!"); Visit(sqlUnaryExpression.Operand); return sqlUnaryExpression; + + // NOT over expression types which have fancy embedded negation + case ExpressionType.Not + when sqlUnaryExpression.Type == typeof(bool): + { + switch (sqlUnaryExpression.Operand) + { + case PgRegexMatchExpression regexMatch: + VisitRegexMatch(regexMatch, negated: true); + return sqlUnaryExpression; + + case PgILikeExpression iLike: + VisitILike(iLike, negated: true); + return sqlUnaryExpression; + } + + break; + } } return base.VisitSqlUnary(sqlUnaryExpression); @@ -907,16 +925,12 @@ protected virtual Expression VisitArraySlice(PgArraySliceExpression expression) } /// - /// Visits the children of a . + /// Produces SQL for PostgreSQL regex matching. /// - /// The expression. - /// - /// An . - /// /// /// See: http://www.postgresql.org/docs/current/static/functions-matching.html /// - protected virtual Expression VisitRegexMatch(PgRegexMatchExpression expression) + protected virtual Expression VisitRegexMatch(PgRegexMatchExpression expression, bool negated = false) { var options = expression.Options; @@ -924,12 +938,12 @@ protected virtual Expression VisitRegexMatch(PgRegexMatchExpression expression) if (options.HasFlag(RegexOptions.IgnoreCase)) { - Sql.Append(" ~* "); + Sql.Append(negated ? " !~* " : " ~* "); options &= ~RegexOptions.IgnoreCase; } else { - Sql.Append(" ~ "); + Sql.Append(negated ? " !~ " : " ~ "); } // PG regexps are single-line by default @@ -1012,16 +1026,22 @@ protected virtual Expression VisitRowValue(PgRowValueExpression rowValueExpressi } /// - /// Visits the children of an . + /// 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. /// - /// The expression. - /// - /// An . - /// - protected virtual Expression VisitILike(PgILikeExpression likeExpression) + protected virtual Expression VisitILike(PgILikeExpression likeExpression, bool negated = false) { Visit(likeExpression.Match); + + if (negated) + { + Sql.Append(" NOT"); + } + Sql.Append(" ILIKE "); + Visit(likeExpression.Pattern); if (likeExpression.EscapeChar is not null) diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs index 0b13fc070..425b5782e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindDbFunctionsQueryNpgsqlTest.cs @@ -117,7 +117,7 @@ public void String_ILike_negated() """ SELECT count(*)::int FROM "Customers" AS c -WHERE NOT (c."ContactName" ILIKE '%M%') OR c."ContactName" IS NULL +WHERE c."ContactName" NOT ILIKE '%M%' OR c."ContactName" IS NULL """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs index c8fd85fe5..f522b5ac4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs @@ -131,6 +131,22 @@ await AssertQuery( """); } + [Theory] + [MemberData(nameof(IsAsyncData))] + public async Task Regex_IsMatch_negated(bool async) + { + await AssertQuery( + async, + cs => cs.Set().Where(c => !Regex.IsMatch(c.CompanyName, "^A"))); + + AssertSql( + """ +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" +FROM "Customers" AS c +WHERE c."CompanyName" !~ '(?p)^A' +"""); + } + [Theory] [MemberData(nameof(IsAsyncData))] public async Task Regex_IsMatchOptionsNone(bool async) @@ -163,6 +179,22 @@ await AssertQuery( """); } + [Theory] + [MemberData(nameof(IsAsyncData))] + public async Task Regex_IsMatch_with_IgnoreCase_negated(bool async) + { + await AssertQuery( + async, + cs => cs.Set().Where(c => !Regex.IsMatch(c.CompanyName, "^a", RegexOptions.IgnoreCase))); + + AssertSql( + """ +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" +FROM "Customers" AS c +WHERE c."CompanyName" !~* '(?p)^a' +"""); + } + [Theory] [MemberData(nameof(IsAsyncData))] public async Task Regex_IsMatch_with_Multiline(bool async) diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs index cc8aa9153..521f2ff6c 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindMiscellaneousQueryNpgsqlTest.cs @@ -355,6 +355,28 @@ public async Task Array_All_Like(bool async) """); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Array_All_Like_negated(bool async) + { + await using var context = CreateContext(); + + var collection = new[] { "A%", "B%", "C%" }; + var query = context.Set().Where(c => !collection.All(y => EF.Functions.Like(c.Address, y))); + var result = async ? await query.ToListAsync() : query.ToList(); + + Assert.NotEmpty(result); + + AssertSql( + """ +@__collection_1={ 'A%', 'B%', 'C%' } (DbType = Object) + +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 NOT (c."Address" LIKE ALL (@__collection_1)) +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task Array_Any_ILike(bool async) @@ -401,6 +423,35 @@ public async Task Array_Any_ILike(bool async) """); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Array_Any_ILike_negated(bool async) + { + await using var context = CreateContext(); + + var collection = new[] { "a%", "b%", "c%" }; + var query = context.Set().Where(c => !collection.Any(y => EF.Functions.ILike(c.Address, y))); + var result = async ? await query.ToListAsync() : query.ToList(); + + Assert.Equal( + [ + "ALFKI", + "ANTON", + "AROUT", + "BLAUS", + "BLONP" + ], result.Select(e => e.CustomerID).Order().Take(5)); + + AssertSql( + """ +@__collection_1={ 'a%', 'b%', 'c%' } (DbType = Object) + +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 NOT (c."Address" ILIKE ANY (@__collection_1)) +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task Array_All_ILike(bool async) From 30cebf0db5c48459d25ccf55e38a8ab37f84b898 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 15 Sep 2024 01:34:13 +0200 Subject: [PATCH 058/107] Add ConfigureDataSource() to NpgsqlDbContextOptionsBuilder (#3277) Closes #2542 Closes #2704 --- .../Internal/NpgsqlOptionsExtension.cs | 20 ++ .../NpgsqlDbContextOptionsBuilder.cs | 30 ++- .../Properties/NpgsqlStrings.Designer.cs | 16 +- src/EFCore.PG/Properties/NpgsqlStrings.resx | 7 +- .../Internal/NpgsqlDataSourceManager.cs | 13 +- .../LoggingNpgsqlTest.cs | 2 + .../NpgsqlRelationalConnectionTest.cs | 210 ++++++++++++++---- 7 files changed, 240 insertions(+), 58 deletions(-) diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index 285ed2989..e4abca786 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -36,6 +36,11 @@ public virtual Version PostgresVersion public virtual bool IsPostgresVersionSet => _postgresVersion is not null; + /// + /// A lambda to configure Npgsql options on . + /// + public virtual Action? DataSourceBuilderAction { get; private set; } + /// /// The , or if a connection string or was used /// instead of a . @@ -126,6 +131,21 @@ public NpgsqlOptionsExtension(NpgsqlOptionsExtension copyFrom) public override int? MinBatchSize => base.MinBatchSize ?? 2; + /// + /// Creates a new instance with all options the same as for this instance, but with the given option changed. + /// It is unusual to call this method directly. Instead use . + /// + /// A lambda to configure Npgsql options on . + /// A new instance with the option changed. + public virtual NpgsqlOptionsExtension WithDataSourceConfiguration(Action dataSourceBuilderAction) + { + var clone = (NpgsqlOptionsExtension)Clone(); + + clone.DataSourceBuilderAction = dataSourceBuilderAction; + + return clone; + } + /// /// Creates a new instance with all options the same as for this instance, but with the given option changed. /// It is unusual to call this method directly. Instead use . diff --git a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs index b0a8e9921..12b3fc599 100644 --- a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs +++ b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs @@ -18,6 +18,19 @@ public NpgsqlDbContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder) { } + /// + /// Configures lower-level Npgsql options at the ADO.NET driver level. + /// + /// A lambda to configure Npgsql options on . + /// + /// Changes made by are untracked; When using , EF Core + /// will by default resolve the same internally, disregarding differing configuration across calls + /// to . Either make sure that always sets the same + /// configuration, or pass externally-provided, pre-configured data source instances when configuring the provider. + /// + public virtual NpgsqlDbContextOptionsBuilder ConfigureDataSource(Action dataSourceBuilderAction) + => WithOption(e => e.WithDataSourceConfiguration(dataSourceBuilderAction)); + /// /// Connect to this database for administrative operations (creating/dropping databases). /// @@ -48,6 +61,8 @@ public virtual NpgsqlDbContextOptionsBuilder SetPostgresVersion(int major, int m public virtual NpgsqlDbContextOptionsBuilder UseRedshift(bool useRedshift = true) => WithOption(e => e.WithRedshift(useRedshift)); + #region MapRange + /// /// Maps a user-defined PostgreSQL range type for use. /// @@ -95,6 +110,10 @@ public virtual NpgsqlDbContextOptionsBuilder MapRange( string? subtypeName = null) => WithOption(e => e.WithUserRangeDefinition(rangeName, schemaName, subtypeClrType, subtypeName)); + #endregion MapRange + + #region MapEnum + /// /// Maps a PostgreSQL enum type for use. /// @@ -122,6 +141,8 @@ public virtual NpgsqlDbContextOptionsBuilder MapEnum( INpgsqlNameTranslator? nameTranslator = null) => WithOption(e => e.WithEnumMapping(clrType, enumName, schemaName, nameTranslator)); + #endregion MapEnum + /// /// Appends NULLS FIRST to all ORDER BY clauses. This is important for the tests which were written /// for SQL Server. Note that to fully implement null-first ordering indexes also need to be generated @@ -131,12 +152,13 @@ public virtual NpgsqlDbContextOptionsBuilder MapEnum( internal virtual NpgsqlDbContextOptionsBuilder ReverseNullOrdering(bool reverseNullOrdering = true) => WithOption(e => e.WithReverseNullOrdering(reverseNullOrdering)); - #region Authentication + #region Authentication (obsolete) /// /// Configures the to use the specified . /// /// The callback to use. + [Obsolete("Call ConfigureDataSource() and configure the client certificates on the NpgsqlDataSourceBuilder, or pass an externally-built, pre-configured NpgsqlDataSource to UseNpgsql().")] public virtual NpgsqlDbContextOptionsBuilder ProvideClientCertificatesCallback(ProvideClientCertificatesCallback? callback) => WithOption(e => e.WithProvideClientCertificatesCallback(callback)); @@ -144,6 +166,7 @@ public virtual NpgsqlDbContextOptionsBuilder ProvideClientCertificatesCallback(P /// Configures the to use the specified . /// /// The callback to use. + [Obsolete("Call ConfigureDataSource() and configure remote certificate validation on the NpgsqlDataSourceBuilder, or pass an externally-built, pre-configured NpgsqlDataSource to UseNpgsql().")] public virtual NpgsqlDbContextOptionsBuilder RemoteCertificateValidationCallback(RemoteCertificateValidationCallback? callback) => WithOption(e => e.WithRemoteCertificateValidationCallback(callback)); @@ -151,12 +174,11 @@ public virtual NpgsqlDbContextOptionsBuilder RemoteCertificateValidationCallback /// Configures the to use the specified . /// /// The callback to use. -#pragma warning disable CS0618 // ProvidePasswordCallback is obsolete + [Obsolete("Call ConfigureDataSource() and configure the password callback on the NpgsqlDataSourceBuilder, or pass an externally-built, pre-configured NpgsqlDataSource to UseNpgsql().")] public virtual NpgsqlDbContextOptionsBuilder ProvidePasswordCallback(ProvidePasswordCallback? callback) => WithOption(e => e.WithProvidePasswordCallback(callback)); -#pragma warning restore CS0618 - #endregion Authentication + #endregion Authentication (obsolete) #region Retrying execution strategy diff --git a/src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs b/src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs index 6e39cfd0e..23e9455d7 100644 --- a/src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs +++ b/src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs @@ -20,12 +20,10 @@ private static readonly ResourceManager _resourceManager = new ResourceManager("Npgsql.EntityFrameworkCore.PostgreSQL.Properties.NpgsqlStrings", typeof(NpgsqlStrings).Assembly); /// - /// Using two distinct data sources within a service provider is not supported, and Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or ensure that the same data source is used for all uses of a given service provider passed to '{useInternalServiceProvider}'. + /// ConfigureDataSource() cannot be used when an externally-provided NpgsqlDataSource is passed to UseNpgsql(). Either perform all data source configuration on the external NpgsqlDataSource, or pass a connection string to UseNpgsql() and specify the data source configuration there. /// - public static string TwoDataSourcesInSameServiceProvider(object? useInternalServiceProvider) - => string.Format( - GetString("TwoDataSourcesInSameServiceProvider", nameof(useInternalServiceProvider)), - useInternalServiceProvider); + public static string DataSourceAndConfigNotSupported + => GetString("DataSourceAndConfigNotSupported"); /// /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured with different compression methods. @@ -171,6 +169,14 @@ public static string StoredProcedureReturnValueNotSupported(object? entityType, GetString("StoredProcedureReturnValueNotSupported", nameof(entityType), nameof(sproc)), entityType, sproc); + /// + /// Using two distinct data sources within a service provider is not supported, and Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or ensure that the same data source is used for all uses of a given service provider passed to '{useInternalServiceProvider}'. + /// + public static string TwoDataSourcesInSameServiceProvider(object? useInternalServiceProvider) + => string.Format( + GetString("TwoDataSourcesInSameServiceProvider", nameof(useInternalServiceProvider)), + useInternalServiceProvider); + /// /// An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure()' to the 'UseSqlServer' call. /// diff --git a/src/EFCore.PG/Properties/NpgsqlStrings.resx b/src/EFCore.PG/Properties/NpgsqlStrings.resx index 1759bc4a9..d40cedf4e 100644 --- a/src/EFCore.PG/Properties/NpgsqlStrings.resx +++ b/src/EFCore.PG/Properties/NpgsqlStrings.resx @@ -117,8 +117,8 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Using two distinct data sources within a service provider is not supported, and Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or ensure that the same data source is used for all uses of a given service provider passed to '{useInternalServiceProvider}'. + + ConfigureDataSource() cannot be used when an externally-provided NpgsqlDataSource is passed to UseNpgsql(). Either perform all data source configuration on the external NpgsqlDataSource, or pass a connection string to UseNpgsql() and specify the data source configuration there. '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different compression methods. @@ -247,4 +247,7 @@ The entity type '{entityType}' is mapped to the stored procedure '{sproc}', which is configured with result columns. PostgreSQL stored procedures do not support return values; use output parameters instead. + + Using two distinct data sources within a service provider is not supported, and Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or ensure that the same data source is used for all uses of a given service provider passed to '{useInternalServiceProvider}'. + \ No newline at end of file diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs b/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs index 9859e2cd0..323935116 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs @@ -2,6 +2,7 @@ using System.Data.Common; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; @@ -52,7 +53,12 @@ public NpgsqlDataSourceManager(IEnumerable // If the user has explicitly passed in a data source via UseNpgsql(), use that. // Note that in this case, the data source is scoped (not singleton), and so can change between different // DbContext instances using the same internal service provider. - { DataSource: DbDataSource dataSource } => dataSource, + { DataSource: DbDataSource dataSource } + => npgsqlOptionsExtension.DataSourceBuilderAction is null + ? dataSource + // If the user has explicitly passed in a data source via UseNpgsql(), but also supplied a data source configuration + // lambda, throw - we're unable to apply the configuration lambda to the externally-provided, already-built data source. + : throw new NotSupportedException(NpgsqlStrings.DataSourceAndConfigNotSupported), // If the user has passed in a DbConnection, never use a data source - even if e.g. MapEnum() was called. // This is to avoid blocking and allow continuing using enums in conjunction with DbConnections (which @@ -68,6 +74,7 @@ public NpgsqlDataSourceManager(IEnumerable { ConnectionString: null } or null => null, // The following are features which require an NpgsqlDataSource, since they require configuration on NpgsqlDataSourceBuilder. + { DataSourceBuilderAction: not null } => GetSingletonDataSource(npgsqlOptionsExtension), { EnumDefinitions.Count: > 0 } => GetSingletonDataSource(npgsqlOptionsExtension), _ when _plugins.Any() => GetSingletonDataSource(npgsqlOptionsExtension), @@ -139,6 +146,10 @@ enumDefinition.StoreTypeSchema is null dataSourceBuilder.UseUserCertificateValidationCallback(npgsqlOptionsExtension.RemoteCertificateValidationCallback); } + // Finally, if the user has provided a data source builder configuration action, invoke it. + // Do this last, to allow the user to override anything set above. + npgsqlOptionsExtension.DataSourceBuilderAction?.Invoke(dataSourceBuilder); + return dataSourceBuilder.Build(); } diff --git a/test/EFCore.PG.FunctionalTests/LoggingNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/LoggingNpgsqlTest.cs index 5432f3364..a495a96cb 100644 --- a/test/EFCore.PG.FunctionalTests/LoggingNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/LoggingNpgsqlTest.cs @@ -18,6 +18,7 @@ public void Logs_context_initialization_postgres_version() ExpectedMessage($"PostgresVersion=10.7 {DefaultOptions}"), ActualMessage(s => CreateOptionsBuilder(s, b => ((NpgsqlDbContextOptionsBuilder)b).SetPostgresVersion(Version.Parse("10.7"))))); +#pragma warning disable CS0618 // Authentication APIs on NpgsqlDbContextOptionsBuilder are obsolete [Fact] public void Logs_context_initialization_provide_client_certificates_callback() => Assert.Equal( @@ -42,6 +43,7 @@ public void Logs_context_initialization_remote_certificate_validation_callback() s => CreateOptionsBuilder( s, b => ((NpgsqlDbContextOptionsBuilder)b).RemoteCertificateValidationCallback((_, _, _, _) => true)))); +#pragma warning restore CS0618 // Authentication APIs on NpgsqlDbContextOptionsBuilder are obsolete [Fact] public void Logs_context_initialization_reverse_null_ordering() diff --git a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs index 8b72df045..ee6f3033c 100644 --- a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Diagnostics.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; @@ -11,6 +12,8 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL; +#nullable enable + public class NpgsqlRelationalConnectionTest { [Fact] @@ -30,13 +33,14 @@ public void Uses_DbDataSource_from_DbContextOptions() serviceCollection .AddNpgsqlDataSource("Host=FakeHost") + // ReSharper disable once AccessToDisposedClosure .AddDbContext(o => o.UseNpgsql(dataSource)); using var serviceProvider = serviceCollection.BuildServiceProvider(); using var scope1 = serviceProvider.CreateScope(); var context1 = scope1.ServiceProvider.GetRequiredService(); - var relationalConnection1 = (NpgsqlRelationalConnection)context1.GetService()!; + var relationalConnection1 = (NpgsqlRelationalConnection)context1.GetService(); Assert.Same(dataSource, relationalConnection1.DbDataSource); var connection1 = context1.GetService().Database.GetDbConnection(); @@ -44,7 +48,7 @@ public void Uses_DbDataSource_from_DbContextOptions() using var scope2 = serviceProvider.CreateScope(); var context2 = scope2.ServiceProvider.GetRequiredService(); - var relationalConnection2 = (NpgsqlRelationalConnection)context2.GetService()!; + var relationalConnection2 = (NpgsqlRelationalConnection)context2.GetService(); Assert.Same(dataSource, relationalConnection2.DbDataSource); var connection2 = context2.GetService().Database.GetDbConnection(); @@ -66,7 +70,7 @@ public void Uses_DbDataSource_from_application_service_provider() using var scope1 = serviceProvider.CreateScope(); var context1 = scope1.ServiceProvider.GetRequiredService(); - var relationalConnection1 = (NpgsqlRelationalConnection)context1.GetService()!; + var relationalConnection1 = (NpgsqlRelationalConnection)context1.GetService(); Assert.Same(dataSource, relationalConnection1.DbDataSource); var connection1 = context1.GetService().Database.GetDbConnection(); @@ -74,7 +78,7 @@ public void Uses_DbDataSource_from_application_service_provider() using var scope2 = serviceProvider.CreateScope(); var context2 = scope2.ServiceProvider.GetRequiredService(); - var relationalConnection2 = (NpgsqlRelationalConnection)context2.GetService()!; + var relationalConnection2 = (NpgsqlRelationalConnection)context2.GetService(); Assert.Same(dataSource, relationalConnection2.DbDataSource); var connection2 = context2.GetService().Database.GetDbConnection(); @@ -94,7 +98,7 @@ public void DbDataSource_from_application_service_provider_does_not_used_if_conn using var scope1 = serviceProvider.CreateScope(); var context1 = scope1.ServiceProvider.GetRequiredService(); - var relationalConnection1 = (NpgsqlRelationalConnection)context1.GetService()!; + var relationalConnection1 = (NpgsqlRelationalConnection)context1.GetService(); Assert.Null(relationalConnection1.DbDataSource); var connection1 = context1.GetService().Database.GetDbConnection(); @@ -102,82 +106,183 @@ public void DbDataSource_from_application_service_provider_does_not_used_if_conn } [Fact] - public void Multiple_connection_strings_with_plugin() + public void Data_source_config_with_same_connection_string() + { + // The connection string is the same, so the same data source gets resolved. + // This works well as long as ConfigureDataSource() has the same lambda. + var context1 = new ConfigurableContext( + "Host=FakeHost1", no => no.ConfigureDataSource(dsb => dsb.ConnectionStringBuilder.ApplicationName = "App1")); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1;Application Name=App1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConfigurableContext( + "Host=FakeHost1", no => no.ConfigureDataSource(dsb => dsb.ConnectionStringBuilder.ApplicationName = "App1")); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost1;Application Name=App1", connection1.ConnectionString); + Assert.Same(connection1.DbDataSource, connection2.DbDataSource); + } + + [Fact] + public void Data_source_config_with_different_connection_strings() + { + // When different connection strings are used, different data sources are created internally. + var context1 = new ConfigurableContext( + "Host=FakeHost1", no => no.ConfigureDataSource(dsb => dsb.ConnectionStringBuilder.ApplicationName = "App1")); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1;Application Name=App1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConfigurableContext( + "Host=FakeHost2", no => no.ConfigureDataSource(dsb => dsb.ConnectionStringBuilder.ApplicationName = "App2")); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost2;Application Name=App2", connection2.ConnectionString); + Assert.NotSame(connection1.DbDataSource, connection2.DbDataSource); + } + + [Fact] + public void Data_source_config_with_same_connection_string_and_different_lambda() + { + // Bad case: same connection string but with a different data source config lambda. + // Same data source gets reused, and so the differing data source config gets ignored. + var context1 = new ConfigurableContext( + "Host=FakeHost1", no => no.ConfigureDataSource(dsb => dsb.ConnectionStringBuilder.ApplicationName = "App1")); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1;Application Name=App1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConfigurableContext( + "Host=FakeHost1", no => no.ConfigureDataSource(dsb => dsb.ConnectionStringBuilder.ApplicationName = "App2")); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + // Note the incorrect Application Name below, because the 1st data source was resolved based on the connection string only + Assert.Equal("Host=FakeHost1;Application Name=App1", connection2.ConnectionString); + Assert.Same(connection1.DbDataSource, connection2.DbDataSource); + } + + [Fact] + public void Plugin_config_with_same_connection_string() { - var context1 = new ConnectionStringSwitchingContext("Host=FakeHost1", withNetTopologySuite: true); + // The connection string and plugin config are the same, so the same data source gets resolved. + var context1 = new ConfigurableContext("Host=FakeHost1", no => no.UseNetTopologySuite()); var connection1 = (NpgsqlRelationalConnection)context1.GetService(); Assert.Equal("Host=FakeHost1", connection1.ConnectionString); Assert.NotNull(connection1.DbDataSource); - var context2 = new ConnectionStringSwitchingContext("Host=FakeHost1", withNetTopologySuite: true); + var context2 = new ConfigurableContext("Host=FakeHost1", no => no.UseNetTopologySuite()); var connection2 = (NpgsqlRelationalConnection)context2.GetService(); - Assert.Equal("Host=FakeHost1", connection2.ConnectionString); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); Assert.Same(connection1.DbDataSource, connection2.DbDataSource); + } - var context3 = new ConnectionStringSwitchingContext("Host=FakeHost2", withNetTopologySuite: true); - var connection3 = (NpgsqlRelationalConnection)context3.GetService(); - Assert.Equal("Host=FakeHost2", connection3.ConnectionString); - Assert.NotSame(connection1.DbDataSource, connection3.DbDataSource); + [Fact] + public void Plugin_config_with_different_connection_strings() + { + // When different connection strings are used, different data sources are created internally. + var context1 = new ConfigurableContext("Host=FakeHost1", no => no.UseNetTopologySuite()); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConfigurableContext("Host=FakeHost2", no => no.UseNetTopologySuite()); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost2", connection2.ConnectionString); + Assert.NotSame(connection1.DbDataSource, connection2.DbDataSource); } [Fact] - public void Multiple_connection_strings_with_enum() + public void Plugin_config_with_different_connection_strings_and_different_plugins() { - var context1 = new ConnectionStringSwitchingContext("Host=FakeHost1", withEnum: true); + // Since the plugin configuration is a singleton option, a different service provider gets resolved and we have different data + // sources. + var context1 = new ConfigurableContext("Host=FakeHost1", no => no.UseNetTopologySuite()); var connection1 = (NpgsqlRelationalConnection)context1.GetService(); Assert.Equal("Host=FakeHost1", connection1.ConnectionString); Assert.NotNull(connection1.DbDataSource); - var context2 = new ConnectionStringSwitchingContext("Host=FakeHost1", withEnum: true); + var context2 = new ConfigurableContext("Host=FakeHost1", no => no.UseNodaTime()); var connection2 = (NpgsqlRelationalConnection)context2.GetService(); Assert.Equal("Host=FakeHost1", connection2.ConnectionString); + Assert.NotSame(connection1.DbDataSource, connection2.DbDataSource); + } + + [Fact] + public void Enum_config_with_same_connection_string() + { + // The connection string and plugin config are the same, so the same data source gets resolved. + var context1 = new ConfigurableContext("Host=FakeHost1", no => no.MapEnum("mood")); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConfigurableContext("Host=FakeHost1", no => no.MapEnum("mood")); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); Assert.Same(connection1.DbDataSource, connection2.DbDataSource); + } - var context3 = new ConnectionStringSwitchingContext("Host=FakeHost2", withEnum: true); - var connection3 = (NpgsqlRelationalConnection)context3.GetService(); - Assert.Equal("Host=FakeHost2", connection3.ConnectionString); - Assert.NotSame(connection1.DbDataSource, connection3.DbDataSource); + [Fact] + public void Enum_config_with_different_connection_strings() + { + // When different connection strings are used, different data sources are created internally. + var context1 = new ConfigurableContext("Host=FakeHost1", no => no.MapEnum("mood")); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConfigurableContext("Host=FakeHost2", no => no.MapEnum("mood")); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost2", connection2.ConnectionString); + Assert.NotSame(connection1.DbDataSource, connection2.DbDataSource); + } + + [Fact] + public void Enum_config_with_different_connection_strings_and_different_enums() + { + // Since the enum configuration is a singleton option, a different service provider gets resolved, and we have different data + // sources. + var context1 = new ConfigurableContext("Host=FakeHost1", no => no.MapEnum("mood")); + var connection1 = (NpgsqlRelationalConnection)context1.GetService(); + Assert.Equal("Host=FakeHost1", connection1.ConnectionString); + Assert.NotNull(connection1.DbDataSource); + + var context2 = new ConfigurableContext("Host=FakeHost1", _ => { /* no enums */}); + var connection2 = (NpgsqlRelationalConnection)context2.GetService(); + Assert.Equal("Host=FakeHost1", connection2.ConnectionString); + Assert.NotSame(connection1.DbDataSource, connection2.DbDataSource); + } + + [Fact] + public void Data_source_and_data_source_config_are_incompatible() + { + using var dataSource = NpgsqlDataSource.Create("Host=FakeHost"); + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseNpgsql(dataSource, no => no.ConfigureDataSource(dsb => dsb.ConnectionStringBuilder.ApplicationName = "foo")); + + var context1 = new FakeDbContext(optionsBuilder.Options); + var exception = Assert.Throws(() => context1.GetService()); + Assert.Equal(NpgsqlStrings.DataSourceAndConfigNotSupported, exception.Message); } [Fact] public void Multiple_connection_strings_without_data_source_features() { - var context1 = new ConnectionStringSwitchingContext("Host=FakeHost1"); + var context1 = new ConfigurableContext("Host=FakeHost1"); var connection1 = (NpgsqlRelationalConnection)context1.GetService(); Assert.Equal("Host=FakeHost1", connection1.ConnectionString); Assert.Null(connection1.DbDataSource); - var context2 = new ConnectionStringSwitchingContext("Host=FakeHost1"); + var context2 = new ConfigurableContext("Host=FakeHost1"); var connection2 = (NpgsqlRelationalConnection)context2.GetService(); Assert.Equal("Host=FakeHost1", connection2.ConnectionString); Assert.Null(connection2.DbDataSource); - var context3 = new ConnectionStringSwitchingContext("Host=FakeHost2"); + var context3 = new ConfigurableContext("Host=FakeHost2"); var connection3 = (NpgsqlRelationalConnection)context3.GetService(); Assert.Equal("Host=FakeHost2", connection3.ConnectionString); Assert.Null(connection3.DbDataSource); } - private class ConnectionStringSwitchingContext(string connectionString, bool withNetTopologySuite = false, bool withEnum = false) - : DbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseNpgsql(connectionString, b => - { - if (withNetTopologySuite) - { - b.UseNetTopologySuite(); - } - - if (withEnum) - { - b.MapEnum("mood"); - } - }); - - private enum Mood { Happy, Sad } - } - [Fact] public void Can_create_master_connection_with_connection_string() { @@ -259,7 +364,7 @@ public void CloneWith_with_connection_and_connection_string() Assert.Equal("Host=localhost;Database=DummyDatabase;Application Name=foo", clone.ConnectionString); } - public static NpgsqlRelationalConnection CreateConnection(DbContextOptions options = null, DbDataSource dataSource = null) + public static NpgsqlRelationalConnection CreateConnection(DbContextOptions? options = null, DbDataSource? dataSource = null) { options ??= new DbContextOptionsBuilder() .UseNpgsql(@"Host=localhost;Database=NpgsqlConnectionTest;Username=some_user;Password=some_password") @@ -308,8 +413,7 @@ public static NpgsqlRelationalConnection CreateConnection(DbContextOptions optio private const string ConnectionString = "Fake Connection String"; - private static IDbContextOptions CreateOptions( - RelationalOptionsExtension optionsExtension = null) + private static IDbContextOptions CreateOptions(RelationalOptionsExtension? optionsExtension = null) { var optionsBuilder = new DbContextOptionsBuilder(); @@ -332,4 +436,18 @@ public FakeDbContext(DbContextOptions options) { } } + + private class ConfigurableContext(string connectionString, Action? npgsqlOptionsAction = null) : DbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseNpgsql(connectionString, npgsqlOptionsAction); + } + + private enum Mood + { + // ReSharper disable once UnusedMember.Local + Happy, + // ReSharper disable once UnusedMember.Local + Sad + } } From 8a0fca5b74c32411c6e5f2255b290362cd4ba51b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 21 Sep 2024 21:38:50 +0200 Subject: [PATCH 059/107] Support arbitrary enumerables in NpgsqlArrayConverter (#3290) Closes #3286 --- .../Mapping/NpgsqlArrayTypeMapping.cs | 4 +- .../ValueConversion/NpgsqlArrayConverter.cs | 214 +++++++++++++----- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 20 ++ 3 files changed, 176 insertions(+), 62 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index 6b70853b3..6672f0520 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -222,8 +222,10 @@ public override DbParameter CreateParameter( // In queries which compose non-server-correlated LINQ operators over an array parameter (e.g. Where(b => ids.Skip(1)...) we // get an enumerable parameter value that isn't an array/list - but those aren't supported at the Npgsql ADO level. // Detect this here and evaluate the enumerable to get a fully materialized List. + // Note that when we have a value converter (e.g. for HashSet), we don't want to convert it to a List, since the value converter + // expects the original type. // TODO: Make Npgsql support IList<> instead of only arrays and List<> - if (value is not null && !value.GetType().IsArrayOrGenericList()) + if (value is not null && Converter is null && !value.GetType().IsArrayOrGenericList()) { switch (value) { diff --git a/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs b/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs index f04221b5a..982d58eaa 100644 --- a/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs +++ b/src/EFCore.PG/Storage/ValueConversion/NpgsqlArrayConverter.cs @@ -105,93 +105,185 @@ private static Expression> ArrayConversionExpression(); - var variables = new List(4) - { - output, - lengthVariable, - }; + var variables = new List { output, lengthVariable }; Expression getInputLength; - Func indexer; + Func? indexer; - if (typeof(TInput).IsArray) - { - getInputLength = ArrayLength(input); - indexer = i => ArrayAccess(input, i); - } - else if (typeof(TInput).IsGenericType - && typeof(TInput).GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>))) - { - getInputLength = Property( - input, - typeof(TInput).GetProperty("Count") - // If TInput is an interface (IList), its Count property needs to be found on ICollection - ?? typeof(ICollection<>).MakeGenericType(typeof(TInput).GetGenericArguments()[0]).GetProperty("Count")!); - indexer = i => Property(input, input.Type.FindIndexerProperty()!, i); - } - else + // The conversion is going to depend on what kind of input we have: array, list, collection, or arbitrary IEnumerable. + // For array/list we can get the length and index inside, so we can do an efficient for loop. + // For other ICollections (e.g. HashSet) we can get the length (and so pre-allocate the output), but we can't index; so we + // get an enumerator and use that. + // For arbitrary IEnumerable, we can't get the length so we can't preallocate output arrays; so we to call ToList() on it and then + // process that (note that we could avoid that when the output is a List rather than an array). + var inputInterfaces = input.Type.GetInterfaces(); + switch (input.Type) { - // Input collection isn't typed as an ICollection; it can be *typed* as an IEnumerable, but we only support concrete - // instances being ICollection. Emit code that casts the type at runtime. - var iListType = typeof(IList<>).MakeGenericType(typeof(TInput).GetGenericArguments()[0]); + // Input is typed as an array - we can get its length and index into it + case { IsArray: true }: + getInputLength = ArrayLength(input); + indexer = i => ArrayAccess(input, i); + break; + + // Input is typed as an IList - we can get its length and index into it + case { IsGenericType: true } when inputInterfaces.Append(input.Type) + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)): + { + getInputLength = Property( + input, + input.Type.GetProperty("Count") + // If TInput is an interface (IList), its Count property needs to be found on ICollection + ?? typeof(ICollection<>).MakeGenericType(input.Type.GetGenericArguments()[0]).GetProperty("Count")!); + indexer = i => Property(input, input.Type.FindIndexerProperty()!, i); + break; + } - var convertedInput = Variable(iListType, "convertedInput"); - variables.Add(convertedInput); + // Input is typed as an ICollection - we can get its length, but we can't index into it + case { IsGenericType: true } when inputInterfaces.Append(input.Type) + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICollection<>)): + { + getInputLength = Property( + input, typeof(ICollection<>).MakeGenericType(input.Type.GetGenericArguments()[0]).GetProperty("Count")!); + indexer = null; + break; + } - expressions.Add(Assign(convertedInput, Convert(input, convertedInput.Type))); + // Input is typed as an IEnumerable - we can't get its length, and we can't index into it. + // All we can do is call ToList() on it and then process that. + case { IsGenericType: true } when inputInterfaces.Append(input.Type) + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)): + { + // TODO: In theory, we could add runtime checks for array/list/collection, downcast for those cases and include + // the logic from the other switch cases here. + convertedInput = Variable(typeof(List<>).MakeGenericType(inputElementType), "convertedInput"); + variables.Add(convertedInput); + expressions.Add( + Assign( + convertedInput, + Call(typeof(Enumerable).GetMethod(nameof(Enumerable.ToList))!.MakeGenericMethod(inputElementType), input))); + getInputLength = Property(convertedInput, convertedInput.Type.GetProperty("Count")!); + indexer = i => Property(convertedInput, convertedInput.Type.FindIndexerProperty()!, i); + break; + } - // TODO: Check and properly throw for non-IList, e.g. set - getInputLength = Property( - convertedInput, typeof(ICollection<>).MakeGenericType(typeof(TInput).GetGenericArguments()[0]).GetProperty("Count")!); - indexer = i => Property(convertedInput, iListType.FindIndexerProperty()!, i); + default: + throw new NotSupportedException($"Array value converter input type must be an IEnumerable, but is {typeof(TInput)}"); } expressions.AddRange( [ // Get the length of the input array or list - // var length = input.Length; - Assign(lengthVariable, getInputLength), - - // Allocate an output array or list - // var result = new int[length]; - Assign( - output, typeof(TConcreteOutput).IsArray - ? NewArrayBounds(outputElementType, lengthVariable) - : typeof(TConcreteOutput).GetConstructor([typeof(int)]) is ConstructorInfo ctorWithLength - ? New(ctorWithLength, lengthVariable) - : New(typeof(TConcreteOutput).GetConstructor([])!)), - - // Loop over the elements, applying the element converter on them one by one - // for (var i = 0; i < length; i++) - // { - // result[i] = input[i]; - // } + // var length = input.Length; + Assign(lengthVariable, getInputLength), + + // Allocate an output array or list + // var result = new int[length]; + Assign( + output, typeof(TConcreteOutput).IsArray + ? NewArrayBounds(outputElementType, lengthVariable) + : typeof(TConcreteOutput).GetConstructor([typeof(int)]) is ConstructorInfo ctorWithLength + ? New(ctorWithLength, lengthVariable) + : New(typeof(TConcreteOutput).GetConstructor([])!)) + ]); + + if (indexer is not null) + { + // Good case: the input is an array or list, so we can index into it. Generate code for an efficient for loop, which applies + // the element converter on each element. + // for (var i = 0; i < length; i++) + // { + // result[i] = input[i]; + // } + var counter = Parameter(typeof(int), "i"); + + expressions.Add( ForLoop( - loopVar: loopVariable, + loopVar: counter, initValue: Constant(0), - condition: LessThan(loopVariable, lengthVariable), - increment: AddAssign(loopVariable, Constant(1)), + condition: LessThan(counter, lengthVariable), + increment: AddAssign(counter, Constant(1)), loopContent: typeof(TConcreteOutput).IsArray ? Assign( - ArrayAccess(output, loopVariable), + ArrayAccess(output, counter), elementConversionExpression is null - ? indexer(loopVariable) - : Invoke(elementConversionExpression, indexer(loopVariable))) + ? indexer(counter) + : Invoke(elementConversionExpression, indexer(counter))) : Call( output, typeof(TConcreteOutput).GetMethod("Add", [outputElementType])!, elementConversionExpression is null - ? indexer(loopVariable) - : Invoke(elementConversionExpression, indexer(loopVariable)))), - output - ]); + ? indexer(counter) + : Invoke(elementConversionExpression, indexer(counter))))); + } + else + { + // Bad case: the input is not an array or list, but is a collection (e.g. HashSet), so we can't index into it. + // Generate code for a less efficient enumerator-based iteration. + // enumerator = input.GetEnumerator(); + // counter = 0; + // while (enumerator.MoveNext()) + // { + // output[counter] = enumerator.Current; + // counter++; + // } + var enumerableType = typeof(IEnumerable<>).MakeGenericType(inputElementType); + var enumeratorType = typeof(IEnumerator<>).MakeGenericType(inputElementType); + + var enumeratorVariable = Variable(enumeratorType, "enumerator"); + var counterVariable = Variable(typeof(int), "variable"); + variables.AddRange([enumeratorVariable, counterVariable]); + + expressions.AddRange( + [ + // enumerator = input.GetEnumerator(); + Assign(enumeratorVariable, Call(input, enumerableType.GetMethod(nameof(IEnumerable.GetEnumerator))!)), + + // counter = 0; + Assign(counterVariable, Constant(0)) + ]); + + var breakLabel = Label("LoopBreak"); + + var loop = + Loop( + IfThenElse( + Equal(Call(enumeratorVariable, typeof(IEnumerator).GetMethod(nameof(IEnumerator.MoveNext))!), Constant(true)), + Block( + typeof(TConcreteOutput).IsArray + // output[counter] = enumerator.Current; + ? Assign( + ArrayAccess(output, counterVariable), + elementConversionExpression is null + ? Property(enumeratorVariable, "Current") + : Invoke(elementConversionExpression, Property(enumeratorVariable, "Current"))) + // output.Add(enumerator.Current); + : Call( + output, + typeof(TConcreteOutput).GetMethod("Add", [outputElementType])!, + elementConversionExpression is null + ? Property(enumeratorVariable, "Current") + : Invoke(elementConversionExpression, Property(enumeratorVariable, "Current"))), + + // counter++; + AddAssign(counterVariable, Constant(1))), + Break(breakLabel)), + breakLabel); + + expressions.Add( + TryFinally( + loop, + Call(enumeratorVariable, typeof(IDisposable).GetMethod(nameof(IDisposable.Dispose))!))); + } + + // return output; + expressions.Add(output); return Lambda>( // First, check if the given array value is null and return null immediately if so diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 7802ed3e7..b9a92210d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -508,6 +508,26 @@ WHERE NOT (p."Int" = ANY (@__ints_0) AND p."Int" = ANY (@__ints_0) IS NOT NULL) """); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Parameter_collection_HashSet_with_value_converter_Contains(bool async) + { + HashSet enums = [MyEnum.Value1, MyEnum.Value4]; + + await AssertQuery( + async, + ss => ss.Set().Where(c => enums.Contains(c.Enum))); + + AssertSql( + """ +@__enums_0={ '0', '3' } (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Enum" = ANY (@__enums_0) +"""); + } + public override async Task Parameter_collection_of_ints_Contains_nullable_int(bool async) { await base.Parameter_collection_of_ints_Contains_nullable_int(async); From a894bdc19ac1e4d6a1be3b93bbf2b0881adebe13 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 21 Sep 2024 22:27:01 +0200 Subject: [PATCH 060/107] Redo NpgsqlSetOperationTypingInjector (#3291) Fixes #3283 --- .../NpgsqlQueryTranslationPostprocessor.cs | 2 +- ...ResolutionCompensatingExpressionVisitor.cs | 126 ------------------ .../NpgsqlSetOperationTypingInjector.cs | 80 +++++++++++ .../Query/EntitySplittingQueryNpgsqlTest.cs | 10 +- 4 files changed, 86 insertions(+), 132 deletions(-) delete mode 100644 src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs create mode 100644 src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypingInjector.cs diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs index 0acc8a795..0bb5e96d6 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryTranslationPostprocessor.cs @@ -35,7 +35,7 @@ public override Expression Process(Expression query) var result = base.Process(query); result = new NpgsqlUnnestPostprocessor().Visit(result); - result = new NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor().Visit(result); + result = new NpgsqlSetOperationTypingInjector().Visit(result); return result; } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs deleted file mode 100644 index 9b89c039c..000000000 --- a/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor.cs +++ /dev/null @@ -1,126 +0,0 @@ -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public class NpgsqlSetOperationTypeResolutionCompensatingExpressionVisitor : ExpressionVisitor -{ - private State _state; - - /// - /// 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 Expression VisitExtension(Expression extensionExpression) - => extensionExpression switch - { - ShapedQueryExpression shapedQueryExpression - => shapedQueryExpression.Update( - Visit(shapedQueryExpression.QueryExpression), - Visit(shapedQueryExpression.ShaperExpression)), - SetOperationBase setOperationExpression => VisitSetOperation(setOperationExpression), - SelectExpression selectExpression => VisitSelect(selectExpression), - _ => 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); - } - } - - 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(tables, predicate, groupBy, havingExpression, projections, orderings, offset, limit) - : selectExpression; - } - - private enum State - { - Nothing, - InSingleSetOperation, - InNestedSetOperation, - AlreadyCompensated - } -} diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypingInjector.cs b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypingInjector.cs new file mode 100644 index 000000000..6086bf436 --- /dev/null +++ b/src/EFCore.PG/Query/Internal/NpgsqlSetOperationTypingInjector.cs @@ -0,0 +1,80 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal; + +/// +/// A visitor that injects explicit typing on null projections in set operations, to ensure PostgreSQL gets the typing right. +/// +/// +/// +/// See the +/// PostgreSQL docs on type conversion and set operations. +/// +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +/// +public class NpgsqlSetOperationTypingInjector : ExpressionVisitor +{ + /// + /// 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 Expression VisitExtension(Expression extensionExpression) + => extensionExpression switch + { + ShapedQueryExpression shapedQueryExpression + => shapedQueryExpression.Update( + Visit(shapedQueryExpression.QueryExpression), + Visit(shapedQueryExpression.ShaperExpression)), + + SetOperationBase setOperationExpression => VisitSetOperation(setOperationExpression), + + _ => base.VisitExtension(extensionExpression) + }; + + private Expression VisitSetOperation(SetOperationBase setOperation) + { + var select1 = (SelectExpression)Visit(setOperation.Source1); + var select2 = (SelectExpression)Visit(setOperation.Source2); + + List? rewrittenProjections = null; + + for (var i = 0; i < select1.Projection.Count; i++) + { + var projection = select1.Projection[i]; + var visitedProjection = projection.Expression is SqlConstantExpression { Value : null } + && select2.Projection[i].Expression is SqlConstantExpression { Value : null } + ? projection.Update( + new SqlUnaryExpression( + ExpressionType.Convert, projection.Expression, projection.Expression.Type, projection.Expression.TypeMapping)) + : (ProjectionExpression)Visit(projection); + + if (visitedProjection != projection && rewrittenProjections is null) + { + rewrittenProjections = new List(select1.Projection.Count); + rewrittenProjections.AddRange(select1.Projection.Take(i)); + } + + rewrittenProjections?.Add(visitedProjection); + } + + if (rewrittenProjections is not null) + { + select1 = select1.Update( + select1.Tables, + select1.Predicate, + select1.GroupBy, + select1.Having, + rewrittenProjections, + select1.Orderings, + select1.Offset, + select1.Limit); + } + + return setOperation.Update(select1, select2); + } +} diff --git a/test/EFCore.PG.FunctionalTests/Query/EntitySplittingQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/EntitySplittingQueryNpgsqlTest.cs index aff617fd7..012aed426 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EntitySplittingQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EntitySplittingQueryNpgsqlTest.cs @@ -449,7 +449,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_leaf_with_tabl """ SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", l0."Id", l0."OwnedReference_Id", l0."OwnedReference_OwnedIntValue1", l0."OwnedReference_OwnedIntValue2", o0."OwnedIntValue3", o."OwnedIntValue4", l0."OwnedReference_OwnedStringValue1", l0."OwnedReference_OwnedStringValue2", o0."OwnedStringValue3", o."OwnedStringValue4" FROM ( - SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" + SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b UNION ALL SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator" @@ -578,7 +578,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_base_without_t """ SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", o."BaseEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4" FROM ( - SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" + SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b UNION ALL SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator" @@ -620,7 +620,7 @@ public override async Task Tpc_entity_owning_a_split_reference_on_middle_without """ SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", o."MiddleEntityId", o."Id", o."OwnedIntValue1", o."OwnedIntValue2", o1."OwnedIntValue3", o0."OwnedIntValue4", o."OwnedStringValue1", o."OwnedStringValue2", o1."OwnedStringValue3", o0."OwnedStringValue4" FROM ( - SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" + SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b UNION ALL SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator" @@ -686,7 +686,7 @@ public override async Task Tpc_entity_owning_a_split_collection_on_base(bool asy """ SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", s0."BaseEntityId", s0."Id", s0."OwnedIntValue1", s0."OwnedIntValue2", s0."OwnedIntValue3", s0."OwnedIntValue4", s0."OwnedStringValue1", s0."OwnedStringValue2", s0."OwnedStringValue3", s0."OwnedStringValue4" FROM ( - SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" + SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b UNION ALL SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator" @@ -732,7 +732,7 @@ public override async Task Tpc_entity_owning_a_split_collection_on_middle(bool a """ SELECT u."Id", u."BaseValue", u."MiddleValue", u."SiblingValue", u."LeafValue", u."Discriminator", s0."MiddleEntityId", s0."Id", s0."OwnedIntValue1", s0."OwnedIntValue2", s0."OwnedIntValue3", s0."OwnedIntValue4", s0."OwnedStringValue1", s0."OwnedStringValue2", s0."OwnedStringValue3", s0."OwnedStringValue4" FROM ( - SELECT b."Id", b."BaseValue", NULL::int AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" + SELECT b."Id", b."BaseValue", NULL AS "MiddleValue", NULL::int AS "SiblingValue", NULL::int AS "LeafValue", 'BaseEntity' AS "Discriminator" FROM "BaseEntity" AS b UNION ALL SELECT m."Id", m."BaseValue", m."MiddleValue", NULL AS "SiblingValue", NULL AS "LeafValue", 'MiddleEntity' AS "Discriminator" From 79608d6ab85361f2960fd96cea3adf0467011d39 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 22 Sep 2024 20:59:25 +0200 Subject: [PATCH 061/107] Handle another error code when concurrently creating the migrations history table --- .../Internal/NpgsqlHistoryRepository.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index b075723ba..ddc8d3be7 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -103,16 +103,16 @@ protected override bool InterpretExistsResult(object? value) bool IHistoryRepository.CreateIfNotExists() { - // In PG, doing CREATE TABLE IF NOT EXISTS concurrently can result in a unique constraint violation - // (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). We catch this and report that the table wasn't - // created. + // In PG, doing CREATE TABLE IF NOT EXISTS isn't concurrency-safe, and can result a "duplicate table" error or in a unique + // constraint violation (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). + // We catch this and report that the table wasn't created. try { return Dependencies.MigrationCommandExecutor.ExecuteNonQuery( GetCreateIfNotExistsCommands(), Dependencies.Connection, new MigrationExecutionState(), commitTransaction: true) != 0; } - catch (PostgresException e) when (e.SqlState == "23505") + catch (PostgresException e) when (e.SqlState is "23505" or "42P07") { return false; } @@ -120,9 +120,9 @@ bool IHistoryRepository.CreateIfNotExists() async Task IHistoryRepository.CreateIfNotExistsAsync(CancellationToken cancellationToken) { - // In PG, doing CREATE TABLE IF NOT EXISTS concurrently can result in a unique constraint violation - // (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). We catch this and report that the table wasn't - // created. + // In PG, doing CREATE TABLE IF NOT EXISTS isn't concurrency-safe, and can result a "duplicate table" error or in a unique + // constraint violation (duplicate key value violates unique constraint "pg_type_typname_nsp_index"). + // We catch this and report that the table wasn't created. try { return (await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync( @@ -130,7 +130,7 @@ async Task IHistoryRepository.CreateIfNotExistsAsync(CancellationToken can cancellationToken: cancellationToken).ConfigureAwait(false)) != 0; } - catch (PostgresException e) when (e.SqlState == "23505") + catch (PostgresException e) when (e.SqlState is "23505" or "42P07") { return false; } From 87a1a7e3f6e495f57a9138f2911edf482b9d2409 Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Mon, 23 Sep 2024 15:19:19 +0800 Subject: [PATCH 062/107] Implement Precompiled Query Tests (#3289) --- .../Expressions/Internal/PgAnyExpression.cs | 2 +- .../Internal/NpgsqlQueryCompilationContext.cs | 23 +- .../NpgsqlQueryCompilationContextFactory.cs | 11 + .../NpgsqlSqlTranslatingExpressionVisitor.cs | 36 +- .../NpgsqlComplianceTest.cs | 5 - .../Query/AdHocPrecompiledQueryNpgsqlTest.cs | 91 + .../Query/PrecompiledQueryNpgsqlTest.cs | 2013 +++++++++++++++++ ...compiledSqlPregenerationQueryNpgsqlTest.cs | 246 ++ .../NpgsqlPrecompiledQueryTestHelpers.cs | 16 + 9 files changed, 2434 insertions(+), 9 deletions(-) create mode 100644 test/EFCore.PG.FunctionalTests/Query/AdHocPrecompiledQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/PrecompiledQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/Query/PrecompiledSqlPregenerationQueryNpgsqlTest.cs create mode 100644 test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlPrecompiledQueryTestHelpers.cs diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs index 3d57bc228..40c732644 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgAnyExpression.cs @@ -80,7 +80,7 @@ public virtual PgAnyExpression Update(SqlExpression item, SqlExpression array) public override Expression Quote() => New( _quotingConstructor ??= typeof(PgAnyExpression).GetConstructor( - [typeof(SqlExpression), typeof(SqlExpression), typeof(PgAllOperatorType), typeof(RelationalTypeMapping)])!, + [typeof(SqlExpression), typeof(SqlExpression), typeof(PgAnyOperatorType), typeof(RelationalTypeMapping)])!, Item.Quote(), Array.Quote(), Constant(OperatorType), diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryCompilationContext.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryCompilationContext.cs index 517462731..259cd1ec7 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryCompilationContext.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryCompilationContext.cs @@ -18,7 +18,25 @@ public NpgsqlQueryCompilationContext( QueryCompilationContextDependencies dependencies, RelationalQueryCompilationContextDependencies relationalDependencies, bool async) - : base(dependencies, relationalDependencies, async) + : this( + dependencies, relationalDependencies, async, precompiling: false, + nonNullableReferenceTypeParameters: null) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public NpgsqlQueryCompilationContext( + QueryCompilationContextDependencies dependencies, + RelationalQueryCompilationContextDependencies relationalDependencies, + bool async, + bool precompiling, + IReadOnlySet? nonNullableReferenceTypeParameters) + : base(dependencies, relationalDependencies, async, precompiling, nonNullableReferenceTypeParameters) { } @@ -30,4 +48,7 @@ public NpgsqlQueryCompilationContext( /// public override bool IsBuffering => base.IsBuffering || QuerySplittingBehavior == Microsoft.EntityFrameworkCore.QuerySplittingBehavior.SplitQuery; + + /// + public override bool SupportsPrecompiledQuery => true; } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryCompilationContextFactory.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryCompilationContextFactory.cs index f3a43623b..7f6bbfc3a 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryCompilationContextFactory.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryCompilationContextFactory.cs @@ -36,4 +36,15 @@ public NpgsqlQueryCompilationContextFactory( /// public virtual QueryCompilationContext Create(bool async) => new NpgsqlQueryCompilationContext(_dependencies, _relationalDependencies, async); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual QueryCompilationContext CreatePrecompiled(bool async, IReadOnlySet nonNullableReferenceTypeParameters) + => new NpgsqlQueryCompilationContext( + _dependencies, _relationalDependencies, async, precompiling: true, + nonNullableReferenceTypeParameters); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 9ce217f02..39c670f2b 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -525,7 +525,13 @@ when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPref } } - private static string? ConstructLikePatternParameter( + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static string? ConstructLikePatternParameter( QueryContext queryContext, string baseParameterName, StartsEndsWithContains methodType) @@ -548,10 +554,36 @@ when patternParameter.Name.StartsWith(QueryCompilationContext.QueryParameterPref _ => throw new UnreachableException() }; - private enum StartsEndsWithContains + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public enum StartsEndsWithContains { + /// + /// 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. + /// StartsWith, + + /// + /// 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. + /// EndsWith, + + /// + /// 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. + /// Contains } diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs index 76e12c800..f5e354650 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlComplianceTest.cs @@ -10,11 +10,6 @@ public class NpgsqlComplianceTest : RelationalComplianceTestBase typeof(UdfDbFunctionTestBase<>), typeof(UpdateSqlGeneratorTestBase), - // Precompiled query/NativeAOT (#3257) - typeof(AdHocPrecompiledQueryRelationalTestBase), - typeof(PrecompiledQueryRelationalTestBase), - typeof(PrecompiledSqlPregenerationQueryRelationalTestBase), - // Disabled typeof(GraphUpdatesTestBase<>), typeof(ProxyGraphUpdatesTestBase<>), diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocPrecompiledQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocPrecompiledQueryNpgsqlTest.cs new file mode 100644 index 000000000..f97055924 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocPrecompiledQueryNpgsqlTest.cs @@ -0,0 +1,91 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class AdHocPrecompiledQueryNpgsqlTest(ITestOutputHelper testOutputHelper) + : AdHocPrecompiledQueryRelationalTestBase(testOutputHelper) +{ + protected override bool AlwaysPrintGeneratedSources + => false; + + public override async Task Index_no_evaluatability() + { + await base.Index_no_evaluatability(); + + AssertSql( + """ +SELECT j."Id", j."IntList", j."JsonThing" +FROM "JsonEntities" AS j +WHERE j."IntList"[j."Id" + 1] = 2 +"""); + } + + public override async Task Index_with_captured_variable() + { + await base.Index_with_captured_variable(); + + AssertSql( + """ +@__id_0='1' + +SELECT j."Id", j."IntList", j."JsonThing" +FROM "JsonEntities" AS j +WHERE j."IntList"[@__id_0 + 1] = 2 +"""); + } + + public override async Task JsonScalar() + { + await base.JsonScalar(); + + AssertSql( + """ +SELECT j."Id", j."IntList", j."JsonThing" +FROM "JsonEntities" AS j +WHERE (j."JsonThing" ->> 'StringProperty') = 'foo' +"""); + } + + public override async Task Materialize_non_public() + { + await base.Materialize_non_public(); + + AssertSql( + """ +@p0='10' (Nullable = true) +@p1='9' (Nullable = true) +@p2='8' (Nullable = true) + +INSERT INTO "NonPublicEntities" ("PrivateAutoProperty", "PrivateProperty", "_privateField") +VALUES (@p0, @p1, @p2) +RETURNING "Id"; +""", + // + """ +SELECT n."Id", n."PrivateAutoProperty", n."PrivateProperty", n."_privateField" +FROM "NonPublicEntities" AS n +LIMIT 2 +"""); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + protected override PrecompiledQueryTestHelpers PrecompiledQueryTestHelpers + => NpgsqlPrecompiledQueryTestHelpers.Instance; + + protected override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + { + builder = base.AddOptions(builder); + + // TODO: Figure out if there's a nice way to continue using the retrying strategy + var sqlServerOptionsBuilder = new NpgsqlDbContextOptionsBuilder(builder); + sqlServerOptionsBuilder.ExecutionStrategy(d => new NonRetryingExecutionStrategy(d)); + return builder; + } +} diff --git a/test/EFCore.PG.FunctionalTests/Query/PrecompiledQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrecompiledQueryNpgsqlTest.cs new file mode 100644 index 000000000..c64f357b1 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/PrecompiledQueryNpgsqlTest.cs @@ -0,0 +1,2013 @@ +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +public class PrecompiledQueryNpgsqlTest( + PrecompiledQueryNpgsqlTest.PrecompiledQueryNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : PrecompiledQueryRelationalTestBase(fixture, testOutputHelper), + IClassFixture +{ + protected override bool AlwaysPrintGeneratedSources + => true; + + #region Expression types + + public override async Task BinaryExpression() + { + await base.BinaryExpression(); + + AssertSql( + """ +@__id_0='3' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" > @__id_0 +"""); + } + + public override async Task Conditional_no_evaluatable() + { + await base.Conditional_no_evaluatable(); + + AssertSql( + """ +SELECT CASE + WHEN b."Id" = 2 THEN 'yes' + ELSE 'no' +END +FROM "Blogs" AS b +"""); + } + + public override async Task Conditional_contains_captured_variable() + { + await base.Conditional_contains_captured_variable(); + + AssertSql( + """ +@__yes_0='yes' + +SELECT CASE + WHEN b."Id" = 2 THEN @__yes_0 + ELSE 'no' +END +FROM "Blogs" AS b +"""); + } + + public override async Task Invoke_no_evaluatability_is_not_supported() + { + await base.Invoke_no_evaluatability_is_not_supported(); + + AssertSql(); + } + + public override async Task ListInit_no_evaluatability() + { + await base.ListInit_no_evaluatability(); + + AssertSql( + """ +SELECT b."Id", b."Id" + 1 +FROM "Blogs" AS b +"""); + } + + public override async Task ListInit_with_evaluatable_with_captured_variable() + { + await base.ListInit_with_evaluatable_with_captured_variable(); + + AssertSql( + """ +SELECT b."Id" +FROM "Blogs" AS b +"""); + } + + public override async Task ListInit_with_evaluatable_without_captured_variable() + { + await base.ListInit_with_evaluatable_without_captured_variable(); + + AssertSql( + """ +SELECT b."Id" +FROM "Blogs" AS b +"""); + } + + public override async Task ListInit_fully_evaluatable() + { + await base.ListInit_fully_evaluatable(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" IN (7, 8) +LIMIT 2 +"""); + } + + public override async Task MethodCallExpression_no_evaluatability() + { + await base.MethodCallExpression_no_evaluatability(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Name" IS NOT NULL AND left(b."Name", length(b."Name")) = b."Name" +"""); + } + + public override async Task MethodCallExpression_with_evaluatable_with_captured_variable() + { + await base.MethodCallExpression_with_evaluatable_with_captured_variable(); + + AssertSql( + """ +@__pattern_0_startswith='foo%' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Name" LIKE @__pattern_0_startswith +"""); + } + + public override async Task MethodCallExpression_with_evaluatable_without_captured_variable() + { + await base.MethodCallExpression_with_evaluatable_without_captured_variable(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Name" LIKE 'foo%' +"""); + } + + public override async Task MethodCallExpression_fully_evaluatable() + { + await base.MethodCallExpression_fully_evaluatable(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task New_with_no_arguments() + { + await base.New_with_no_arguments(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 0 +"""); + } + + public override async Task Where_New_with_captured_variable() + { + await base.Where_New_with_captured_variable(); + + AssertSql(); + } + + public override async Task Select_New_with_captured_variable() + { + await base.Select_New_with_captured_variable(); + + AssertSql( + """ +SELECT b."Name" +FROM "Blogs" AS b +"""); + } + + public override async Task MemberInit_no_evaluatable() + { + await base.MemberInit_no_evaluatable(); + + AssertSql( + """ +SELECT b."Id", b."Name" +FROM "Blogs" AS b +"""); + } + + public override async Task MemberInit_contains_captured_variable() + { + await base.MemberInit_contains_captured_variable(); + + AssertSql( + """ +@__id_0='8' + +SELECT @__id_0 AS "Id", b."Name" +FROM "Blogs" AS b +"""); + } + + public override async Task MemberInit_evaluatable_as_constant() + { + await base.MemberInit_evaluatable_as_constant(); + + AssertSql( + """ +SELECT 1 AS "Id", 'foo' AS "Name" +FROM "Blogs" AS b +"""); + } + + public override async Task MemberInit_evaluatable_as_parameter() + { + await base.MemberInit_evaluatable_as_parameter(); + + AssertSql( + """ +SELECT 1 +FROM "Blogs" AS b +"""); + } + + public override async Task NewArray() + { + await base.NewArray(); + + AssertSql( + """ +@__i_0='8' + +SELECT ARRAY[b."Id",b."Id" + @__i_0]::integer[] +FROM "Blogs" AS b +"""); + } + + public override async Task Unary() + { + await base.Unary(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id"::smallint = 8 +"""); + } + + public virtual async Task Collate() + { + await Test("""_ = context.Blogs.Where(b => EF.Functions.Collate(b.Name, "German_PhoneBook_CI_AS") == "foo").ToList();"""); + + AssertSql(); + } + + #endregion Expression types + + #region Terminating operators + + public override async Task Terminating_AsEnumerable() + { + await base.Terminating_AsEnumerable(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_AsAsyncEnumerable_on_DbSet() + { + await base.Terminating_AsAsyncEnumerable_on_DbSet(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_AsAsyncEnumerable_on_IQueryable() + { + await base.Terminating_AsAsyncEnumerable_on_IQueryable(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" > 8 +"""); + } + + public override async Task Foreach_sync_over_operator() + { + await base.Foreach_sync_over_operator(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" > 8 +"""); + } + + public override async Task Terminating_ToArray() + { + await base.Terminating_ToArray(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ToArrayAsync() + { + await base.Terminating_ToArrayAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ToDictionary() + { + await base.Terminating_ToDictionary(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ToDictionaryAsync() + { + await base.Terminating_ToDictionaryAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task ToDictionary_over_anonymous_type() + { + await base.ToDictionary_over_anonymous_type(); + + AssertSql( + """ +SELECT b."Id", b."Name" +FROM "Blogs" AS b +"""); + } + + public override async Task ToDictionaryAsync_over_anonymous_type() + { + await base.ToDictionaryAsync_over_anonymous_type(); + + AssertSql( + """ +SELECT b."Id", b."Name" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ToHashSet() + { + await base.Terminating_ToHashSet(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ToHashSetAsync() + { + await base.Terminating_ToHashSetAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ToLookup() + { + await base.Terminating_ToLookup(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ToList() + { + await base.Terminating_ToList(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ToListAsync() + { + await base.Terminating_ToListAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task Foreach_sync_over_DbSet_property_is_not_supported() + { + await base.Foreach_sync_over_DbSet_property_is_not_supported(); + + AssertSql(); + } + + public override async Task Foreach_async_is_not_supported() + { + await base.Foreach_async_is_not_supported(); + + AssertSql(); + } + + #endregion Terminating operators + + #region Reducing terminating operators + + public override async Task Terminating_All() + { + await base.Terminating_All(); + + AssertSql( + """ +SELECT NOT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" <= 7) +""", + // + """ +SELECT NOT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" <= 8) +"""); + } + + public override async Task Terminating_AllAsync() + { + await base.Terminating_AllAsync(); +AssertSql( + """ +SELECT NOT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" <= 7) +""", + // + """ +SELECT NOT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" <= 8) +"""); + } + + public override async Task Terminating_Any() + { + await base.Terminating_Any(); +AssertSql( + """ +SELECT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" > 7) +""", + // + """ +SELECT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" < 7) +""", + // + """ +SELECT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" > 7) +""", + // + """ +SELECT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" < 7) +"""); + } + + public override async Task Terminating_AnyAsync() + { + await base.Terminating_AnyAsync(); + + AssertSql( + """ +SELECT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" > 7) +""", + // + """ +SELECT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" < 7) +""", + // + """ +SELECT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" > 7) +""", + // + """ +SELECT EXISTS ( + SELECT 1 + FROM "Blogs" AS b + WHERE b."Id" < 7) +"""); + } + + public override async Task Terminating_Average() + { + await base.Terminating_Average(); + + AssertSql( + """ +SELECT avg(b."Id"::double precision) +FROM "Blogs" AS b +""", + // + """ +SELECT avg(b."Id"::double precision) +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_AverageAsync() + { + await base.Terminating_AverageAsync(); + + AssertSql( + """ +SELECT avg(b."Id"::double precision) +FROM "Blogs" AS b +""", + // + """ +SELECT avg(b."Id"::double precision) +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_Contains() + { + await base.Terminating_Contains(); + + AssertSql( + """ +@__p_0='8' + +SELECT @__p_0 IN ( + SELECT b."Id" + FROM "Blogs" AS b +) +""", + // + """ +@__p_0='7' + +SELECT @__p_0 IN ( + SELECT b."Id" + FROM "Blogs" AS b +) +"""); + } + + public override async Task Terminating_ContainsAsync() + { + await base.Terminating_ContainsAsync(); + + AssertSql( + """ +@__p_0='8' + +SELECT @__p_0 IN ( + SELECT b."Id" + FROM "Blogs" AS b +) +""", + // + """ +@__p_0='7' + +SELECT @__p_0 IN ( + SELECT b."Id" + FROM "Blogs" AS b +) +"""); + } + + public override async Task Terminating_Count() + { + await base.Terminating_Count(); + + AssertSql( + """ +SELECT count(*)::int +FROM "Blogs" AS b +""", + // + """ +SELECT count(*)::int +FROM "Blogs" AS b +WHERE b."Id" > 8 +"""); + } + + public override async Task Terminating_CountAsync() + { + await base.Terminating_CountAsync(); + + AssertSql( + """ +SELECT count(*)::int +FROM "Blogs" AS b +""", + // + """ +SELECT count(*)::int +FROM "Blogs" AS b +WHERE b."Id" > 8 +"""); + } + + public override async Task Terminating_ElementAt() + { + await base.Terminating_ElementAt(); + + AssertSql( + """ +@__p_0='1' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +LIMIT 1 OFFSET @__p_0 +""", + // + """ +@__p_0='3' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +LIMIT 1 OFFSET @__p_0 +"""); + } + + public override async Task Terminating_ElementAtAsync() + { + await base.Terminating_ElementAtAsync(); + + AssertSql( + """ +@__p_0='1' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +LIMIT 1 OFFSET @__p_0 +""", + // + """ +@__p_0='3' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +LIMIT 1 OFFSET @__p_0 +"""); + } + + public override async Task Terminating_ElementAtOrDefault() + { + await base.Terminating_ElementAtOrDefault(); + + AssertSql( + """ +@__p_0='1' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +LIMIT 1 OFFSET @__p_0 +""", + // + """ +@__p_0='3' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +LIMIT 1 OFFSET @__p_0 +"""); + } + + public override async Task Terminating_ElementAtOrDefaultAsync() + { + await base.Terminating_ElementAtOrDefaultAsync(); + + AssertSql( + """ +@__p_0='1' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +LIMIT 1 OFFSET @__p_0 +""", + // + """ +@__p_0='3' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +LIMIT 1 OFFSET @__p_0 +"""); + } + + public override async Task Terminating_First() + { + await base.Terminating_First(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 1 +"""); + } + + public override async Task Terminating_FirstAsync() + { + await base.Terminating_FirstAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 1 +"""); + } + + public override async Task Terminating_FirstOrDefault() + { + await base.Terminating_FirstOrDefault(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 1 +"""); + } + + public override async Task Terminating_FirstOrDefaultAsync() + { + await base.Terminating_FirstOrDefaultAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 1 +"""); + } + + public override async Task Terminating_GetEnumerator() + { + await base.Terminating_GetEnumerator(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +"""); + } + + public override async Task Terminating_Last() + { + await base.Terminating_Last(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +"""); + } + + public override async Task Terminating_LastAsync() + { + await base.Terminating_LastAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +"""); + } + + public override async Task Terminating_LastOrDefault() + { + await base.Terminating_LastOrDefault(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +"""); + } + + public override async Task Terminating_LastOrDefaultAsync() + { + await base.Terminating_LastOrDefaultAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +ORDER BY b."Id" DESC NULLS LAST +LIMIT 1 +"""); + } + + public override async Task Terminating_LongCount() + { + await base.Terminating_LongCount(); + + AssertSql( + """ +SELECT count(*) +FROM "Blogs" AS b +""", + // + """ +SELECT count(*) +FROM "Blogs" AS b +WHERE b."Id" = 8 +"""); + } + + public override async Task Terminating_LongCountAsync() + { + await base.Terminating_LongCountAsync(); + + AssertSql( + """ +SELECT count(*) +FROM "Blogs" AS b +""", + // + """ +SELECT count(*) +FROM "Blogs" AS b +WHERE b."Id" = 8 +"""); + } + + public override async Task Terminating_Max() + { + await base.Terminating_Max(); +AssertSql( + """ +SELECT max(b."Id") +FROM "Blogs" AS b +""", + // + """ +SELECT max(b."Id") +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_MaxAsync() + { + await base.Terminating_MaxAsync(); + + AssertSql( + """ +SELECT max(b."Id") +FROM "Blogs" AS b +""", + // + """ +SELECT max(b."Id") +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_Min() + { + await base.Terminating_Min(); + + AssertSql( + """ +SELECT min(b."Id") +FROM "Blogs" AS b +""", + // + """ +SELECT min(b."Id") +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_MinAsync() + { + await base.Terminating_MinAsync(); + + AssertSql( + """ +SELECT min(b."Id") +FROM "Blogs" AS b +""", + // + """ +SELECT min(b."Id") +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_Single() + { + await base.Terminating_Single(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 2 +"""); + } + + public override async Task Terminating_SingleAsync() + { + await base.Terminating_SingleAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 2 +"""); + } + + public override async Task Terminating_SingleOrDefault() + { + await base.Terminating_SingleOrDefault(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 2 +"""); + } + + public override async Task Terminating_SingleOrDefaultAsync() + { + await base.Terminating_SingleOrDefaultAsync(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 8 +LIMIT 2 +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = 7 +LIMIT 2 +"""); + } + + public override async Task Terminating_Sum() + { + await base.Terminating_Sum(); + + AssertSql( + """ +SELECT COALESCE(sum(b."Id"), 0)::int +FROM "Blogs" AS b +""", + // + """ +SELECT COALESCE(sum(b."Id"), 0)::int +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_SumAsync() + { + await base.Terminating_SumAsync(); + + AssertSql( + """ +SELECT COALESCE(sum(b."Id"), 0)::int +FROM "Blogs" AS b +""", + // + """ +SELECT COALESCE(sum(b."Id"), 0)::int +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ExecuteDelete() + { + await base.Terminating_ExecuteDelete(); + + AssertSql( + """ +DELETE FROM "Blogs" AS b +WHERE b."Id" > 8 +""", + // + """ +SELECT count(*)::int +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ExecuteDeleteAsync() + { + await base.Terminating_ExecuteDeleteAsync(); + + AssertSql( + """ +DELETE FROM "Blogs" AS b +WHERE b."Id" > 8 +""", + // + """ +SELECT count(*)::int +FROM "Blogs" AS b +"""); + } + + public override async Task Terminating_ExecuteUpdate() + { + await base.Terminating_ExecuteUpdate(); + + AssertSql( + """ +@__suffix_0='Suffix' + +UPDATE "Blogs" AS b +SET "Name" = COALESCE(b."Name", '') || @__suffix_0 +WHERE b."Id" > 8 +""", + // + """ +SELECT count(*)::int +FROM "Blogs" AS b +WHERE b."Id" = 9 AND b."Name" = 'Blog2Suffix' +"""); + } + + public override async Task Terminating_ExecuteUpdateAsync() + { + await base.Terminating_ExecuteUpdateAsync(); + + AssertSql( + """ +@__suffix_0='Suffix' + +UPDATE "Blogs" AS b +SET "Name" = COALESCE(b."Name", '') || @__suffix_0 +WHERE b."Id" > 8 +""", + // + """ +SELECT count(*)::int +FROM "Blogs" AS b +WHERE b."Id" = 9 AND b."Name" = 'Blog2Suffix' +"""); + } + + #endregion Reducing terminating operators + + #region SQL expression quotability + + public override async Task Union() + { + await base.Union(); + + AssertSql( + """ +SELECT u."Id", u."BlogId", u."Title" +FROM ( + SELECT p."Id", p."BlogId", p."Title" + FROM "Posts" AS p + WHERE p."Id" > 11 + UNION + SELECT p0."Id", p0."BlogId", p0."Title" + FROM "Posts" AS p0 + WHERE p0."Id" < 21 +) AS u +ORDER BY u."Id" NULLS FIRST +"""); + } + + public override async Task UnionOnEntitiesWithJson() + { + await base.UnionOnEntitiesWithJson(); + + AssertSql( + """ +SELECT [u].[Id], [u].[Name], [u].[Json] +FROM ( + SELECT [b].[Id], [b].[Name], [b].[Json] + FROM [Blogs] AS [b] + WHERE [b].[Id] > 7 + UNION + SELECT [b0].[Id], [b0].[Name], [b0].[Json] + FROM [Blogs] AS [b0] + WHERE [b0].[Id] < 10 +) AS [u] +ORDER BY [u].[Id] +"""); + } + + public override async Task Concat() + { + await base.Concat(); + + AssertSql( + """ +SELECT u."Id", u."BlogId", u."Title" +FROM ( + SELECT p."Id", p."BlogId", p."Title" + FROM "Posts" AS p + WHERE p."Id" > 11 + UNION ALL + SELECT p0."Id", p0."BlogId", p0."Title" + FROM "Posts" AS p0 + WHERE p0."Id" < 21 +) AS u +ORDER BY u."Id" NULLS FIRST +"""); + } + + public override async Task ConcatOnEntitiesWithJson() + { + await base.ConcatOnEntitiesWithJson(); + + AssertSql( + """ +SELECT [u].[Id], [u].[Name], [u].[Json] +FROM ( + SELECT [b].[Id], [b].[Name], [b].[Json] + FROM [Blogs] AS [b] + WHERE [b].[Id] > 7 + UNION ALL + SELECT [b0].[Id], [b0].[Name], [b0].[Json] + FROM [Blogs] AS [b0] + WHERE [b0].[Id] < 10 +) AS [u] +ORDER BY [u].[Id] +"""); + } + + public override async Task Intersect() + { + await base.Intersect(); + + AssertSql( + """ +SELECT i."Id", i."BlogId", i."Title" +FROM ( + SELECT p."Id", p."BlogId", p."Title" + FROM "Posts" AS p + WHERE p."Id" > 11 + INTERSECT + SELECT p0."Id", p0."BlogId", p0."Title" + FROM "Posts" AS p0 + WHERE p0."Id" < 22 +) AS i +ORDER BY i."Id" NULLS FIRST +"""); + } + + public override async Task IntersectOnEntitiesWithJson() + { + await base.IntersectOnEntitiesWithJson(); + + AssertSql( + """ +SELECT [i].[Id], [i].[Name], [i].[Json] +FROM ( + SELECT [b].[Id], [b].[Name], [b].[Json] + FROM [Blogs] AS [b] + WHERE [b].[Id] > 7 + INTERSECT + SELECT [b0].[Id], [b0].[Name], [b0].[Json] + FROM [Blogs] AS [b0] + WHERE [b0].[Id] > 8 +) AS [i] +ORDER BY [i].[Id] +"""); + } + + public override async Task Except() + { + await base.Except(); + + AssertSql( + """ +SELECT e."Id", e."BlogId", e."Title" +FROM ( + SELECT p."Id", p."BlogId", p."Title" + FROM "Posts" AS p + WHERE p."Id" > 11 + EXCEPT + SELECT p0."Id", p0."BlogId", p0."Title" + FROM "Posts" AS p0 + WHERE p0."Id" > 21 +) AS e +ORDER BY e."Id" NULLS FIRST +"""); + } + + public override async Task ExceptOnEntitiesWithJson() + { + await base.ExceptOnEntitiesWithJson(); + + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Json] +FROM ( + SELECT [b].[Id], [b].[Name], [b].[Json] + FROM [Blogs] AS [b] + WHERE [b].[Id] > 7 + EXCEPT + SELECT [b0].[Id], [b0].[Name], [b0].[Json] + FROM [Blogs] AS [b0] + WHERE [b0].[Id] > 8 +) AS [e] +ORDER BY [e].[Id] +"""); + } + + public override async Task ValuesExpression() + { + await base.ValuesExpression(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE ( + SELECT count(*)::int + FROM (VALUES (7::int), (b."Id")) AS v("Value") + WHERE v."Value" > 8) = 2 +"""); + } + + public override async Task Contains_with_parameterized_collection() + { + await base.Contains_with_parameterized_collection(); + + AssertSql( + """ +@__ids_0={ '1', '2', '3' } (DbType = Object) + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = ANY (@__ids_0) +"""); + } + + public override async Task FromSqlRaw() + { + await base.FromSqlRaw(); + +AssertSql( + """ +SELECT m."Id", m."Name", m."Json" +FROM ( + SELECT * FROM "Blogs" WHERE "Id" > 8 +) AS m +ORDER BY m."Id" NULLS FIRST +"""); + } + + public override async Task FromSql_with_FormattableString_parameters() + { + await base.FromSql_with_FormattableString_parameters(); + + AssertSql( + """ +p0='8' +p1='9' + +SELECT m."Id", m."Name", m."Json" +FROM ( + SELECT * FROM "Blogs" WHERE "Id" > @p0 AND "Id" < @p1 +) AS m +ORDER BY m."Id" NULLS FIRST +"""); + } + + #endregion SQL expression quotability + + #region Different query roots + + public override async Task DbContext_as_local_variable() + { + await base.DbContext_as_local_variable(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task DbContext_as_field() + { + await base.DbContext_as_field(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task DbContext_as_property() + { + await base.DbContext_as_property(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task DbContext_as_captured_variable() + { + await base.DbContext_as_captured_variable(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + public override async Task DbContext_as_method_invocation_result() + { + await base.DbContext_as_method_invocation_result(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + #endregion Different query roots + + #region Negative cases + + public override async Task Dynamic_query_does_not_get_precompiled() + { + await base.Dynamic_query_does_not_get_precompiled(); + + AssertSql(); + } + + public override async Task ToList_over_objects_does_not_get_precompiled() + { + await base.ToList_over_objects_does_not_get_precompiled(); + + AssertSql(); + } + + public override async Task Query_compilation_failure() + { + await base.Query_compilation_failure(); + + AssertSql(); + } + + public override async Task EF_Constant_is_not_supported() + { + await base.EF_Constant_is_not_supported(); + + AssertSql(); + } + + public override async Task NotParameterizedAttribute_with_constant() + { + await base.NotParameterizedAttribute_with_constant(); +AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Name" = 'Blog2' +LIMIT 2 +"""); + } + + public override async Task NotParameterizedAttribute_is_not_supported_with_non_constant_argument() + { + await base.NotParameterizedAttribute_is_not_supported_with_non_constant_argument(); + + AssertSql(); + } + + public override async Task Query_syntax_is_not_supported() + { + await base.Query_syntax_is_not_supported(); + + AssertSql(); + } + + #endregion Negative cases + + public override async Task Select_changes_type() + { + await base.Select_changes_type(); + + AssertSql( + """ +SELECT b."Name" +FROM "Blogs" AS b +"""); + } + + public override async Task OrderBy() + { + await base.OrderBy(); + +AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Name" NULLS FIRST +"""); + } + + public override async Task Skip() + { + await base.Skip(); + + AssertSql( + """ +@__p_0='1' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Name" NULLS FIRST +OFFSET @__p_0 +"""); + } + + public override async Task Take() + { + await base.Take(); + + AssertSql( + """ +@__p_0='1' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Name" NULLS FIRST +LIMIT @__p_0 +"""); + } + + public override async Task Project_anonymous_object() + { + await base.Project_anonymous_object(); + + AssertSql( + """ +SELECT COALESCE(b."Name", '') || 'Foo' AS "Foo" +FROM "Blogs" AS b +"""); + } + + public override async Task Two_captured_variables_in_same_lambda() + { + await base.Two_captured_variables_in_same_lambda(); + + AssertSql( + """ +@__yes_0='yes' +@__no_1='no' + +SELECT CASE + WHEN b."Id" = 3 THEN @__yes_0 + ELSE @__no_1 +END +FROM "Blogs" AS b +"""); + } + + public override async Task Two_captured_variables_in_different_lambdas() + { + //Throws because the base startswith is uses a different case "Blog" vs "blog" and postgresql LIKE is case sensitive unlike SQL Server + //Base test fixed upstream for later versions + await Assert.ThrowsAsync(() => base.Two_captured_variables_in_different_lambdas()); + +// AssertSql( +// """ +//@__starts_0_startswith='Blog%' +//@__ends_1_endswith='%2' +// +//SELECT b."Id", b."Name", b."Json" +//FROM "Blogs" AS b +//WHERE b."Name" LIKE @__starts_0_startswith AND b."Name" LIKE @__ends_1_endswith +//LIMIT 2 +//"""); + } + + public override async Task Same_captured_variable_twice_in_same_lambda() + { + await base.Same_captured_variable_twice_in_same_lambda(); + + AssertSql( + """ +@__foo_0_startswith='X%' +@__foo_0_endswith='%X' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Name" LIKE @__foo_0_startswith AND b."Name" LIKE @__foo_0_endswith +"""); + } + + public override async Task Same_captured_variable_twice_in_different_lambdas() + { + await base.Same_captured_variable_twice_in_different_lambdas(); + + AssertSql( + """ +@__foo_0_startswith='X%' +@__foo_0_endswith='%X' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Name" LIKE @__foo_0_startswith AND b."Name" LIKE @__foo_0_endswith +"""); + } + + public override async Task Include_single() + { + await base.Include_single(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json", p."Id", p."BlogId", p."Title" +FROM "Blogs" AS b +LEFT JOIN "Posts" AS p ON b."Id" = p."BlogId" +WHERE b."Id" > 8 +ORDER BY b."Id" NULLS FIRST +"""); + } + + public override async Task Include_split() + { + await base.Include_split(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +""", + // + """ +SELECT p."Id", p."BlogId", p."Title", b."Id" +FROM "Blogs" AS b +INNER JOIN "Posts" AS p ON b."Id" = p."BlogId" +ORDER BY b."Id" NULLS FIRST +"""); + } + + public override async Task Final_GroupBy() + { + await base.Final_GroupBy(); + + AssertSql( + """ +SELECT b."Name", b."Id", b."Json" +FROM "Blogs" AS b +ORDER BY b."Name" NULLS FIRST +"""); + } + + public override async Task Multiple_queries_with_captured_variables() + { + await base.Multiple_queries_with_captured_variables(); + + AssertSql( + """ +@__id1_0='8' +@__id2_1='9' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = @__id1_0 OR b."Id" = @__id2_1 +""", + // + """ +@__id1_0='8' + +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +WHERE b."Id" = @__id1_0 +LIMIT 2 +"""); + } + + public override async Task Unsafe_accessor_gets_generated_once_for_multiple_queries() + { + await base.Unsafe_accessor_gets_generated_once_for_multiple_queries(); + + AssertSql( + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +""", + // + """ +SELECT b."Id", b."Name", b."Json" +FROM "Blogs" AS b +"""); + } + + [ConditionalFact] + public virtual void Check_all_tests_overridden() + => TestHelpers.AssertAllMethodsOverridden(GetType()); + + public class PrecompiledQueryNpgsqlFixture : PrecompiledQueryRelationalFixture + { + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + { + builder = base.AddOptions(builder); + + // TODO: Figure out if there's a nice way to continue using the retrying strategy + var npgsqlOptionsBuilder = new NpgsqlDbContextOptionsBuilder(builder); + npgsqlOptionsBuilder.ExecutionStrategy(d => new NonRetryingExecutionStrategy(d)); + return builder; + } + + protected override async Task SeedAsync(PrecompiledQueryContext context) + { + var blog1 = new Blog { Id = 8, Name = "Blog1", Json = [] }; + var blog2 = new Blog + { + Id = 9, + Name = "Blog2", + Json = + [ + new JsonRoot { Number = 1, Text = "One", Inner = new JsonBranch { Date = new DateTime(2001, 1, 1,0, 0, 0, DateTimeKind.Utc) } }, + new JsonRoot { Number = 2, Text = "Two", Inner = new JsonBranch { Date = new DateTime(2002, 2, 2,0, 0, 0, DateTimeKind.Utc) } }, + ] + }; + + context.Blogs.AddRange(blog1, blog2); + + var post11 = new Post { Id = 11, Title = "Post11", Blog = blog1 }; + var post12 = new Post { Id = 12, Title = "Post12", Blog = blog1 }; + var post21 = new Post { Id = 21, Title = "Post21", Blog = blog2 }; + var post22 = new Post { Id = 22, Title = "Post22", Blog = blog2 }; + var post23 = new Post { Id = 23, Title = "Post23", Blog = blog2 }; + + context.Posts.AddRange(post11, post12, post21, post22, post23); + await context.SaveChangesAsync(); + } + + public override PrecompiledQueryTestHelpers PrecompiledQueryTestHelpers => NpgsqlPrecompiledQueryTestHelpers.Instance; + } +} diff --git a/test/EFCore.PG.FunctionalTests/Query/PrecompiledSqlPregenerationQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrecompiledSqlPregenerationQueryNpgsqlTest.cs new file mode 100644 index 000000000..37958e5b0 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/Query/PrecompiledSqlPregenerationQueryNpgsqlTest.cs @@ -0,0 +1,246 @@ +using Microsoft.EntityFrameworkCore.Query.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; + +// ReSharper disable InconsistentNaming + +public class PrecompiledSqlPregenerationQueryNpgsqlTest( + PrecompiledSqlPregenerationQueryNpgsqlTest.PrecompiledSqlPregenerationQueryNpgsqlFixture fixture, + ITestOutputHelper testOutputHelper) + : PrecompiledSqlPregenerationQueryRelationalTestBase(fixture, testOutputHelper), + IClassFixture +{ + protected override bool AlwaysPrintGeneratedSources + => false; + + public override async Task No_parameters() + { + await base.No_parameters(); + + AssertSql( + """ +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Name" = 'foo' +"""); + } + + public override async Task Non_nullable_value_type() + { + await base.Non_nullable_value_type(); + + AssertSql( + """ +@__id_0='8' + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Id" = @__id_0 +"""); + } + + public override async Task Nullable_value_type() + { + await base.Nullable_value_type(); + + AssertSql( + """ +@__id_0='8' (Nullable = true) + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Id" = @__id_0 +"""); + } + + public override async Task Nullable_reference_type() + { + await base.Nullable_reference_type(); + + AssertSql( + """ +@__name_0='bar' + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Name" = @__name_0 +"""); + } + + public override async Task Non_nullable_reference_type() + { + await base.Non_nullable_reference_type(); + + AssertSql( + """ +@__name_0='bar' (Nullable = false) + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Name" = @__name_0 +"""); + } + + public override async Task Nullable_and_non_nullable_value_types() + { + await base.Nullable_and_non_nullable_value_types(); + + AssertSql( + """ +@__id1_0='8' (Nullable = true) +@__id2_1='9' + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Id" = @__id1_0 OR b."Id" = @__id2_1 +"""); + } + + public override async Task Two_nullable_reference_types() + { + await base.Two_nullable_reference_types(); + + AssertSql( + """ +@__name1_0='foo' +@__name2_1='bar' + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Name" = @__name1_0 OR b."Name" = @__name2_1 +"""); + } + + public override async Task Two_non_nullable_reference_types() + { + await base.Two_non_nullable_reference_types(); + + AssertSql( + """ +@__name1_0='foo' (Nullable = false) +@__name2_1='bar' (Nullable = false) + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Name" = @__name1_0 OR b."Name" = @__name2_1 +"""); + } + + public override async Task Nullable_and_non_nullable_reference_types() + { + await base.Nullable_and_non_nullable_reference_types(); + + AssertSql( + """ +@__name1_0='foo' +@__name2_1='bar' (Nullable = false) + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Name" = @__name1_0 OR b."Name" = @__name2_1 +"""); + } + + public override async Task Too_many_nullable_parameters_prevent_pregeneration() + { + await base.Too_many_nullable_parameters_prevent_pregeneration(); + + AssertSql( + """ +@__name1_0='foo' +@__name2_1='bar' +@__name3_2='baz' +@__name4_3='baq' + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Name" = @__name1_0 OR b."Name" = @__name2_1 OR b."Name" = @__name3_2 OR b."Name" = @__name4_3 +"""); + } + + public override async Task Many_non_nullable_parameters_do_not_prevent_pregeneration() + { + await base.Many_non_nullable_parameters_do_not_prevent_pregeneration(); + + AssertSql( + """ +@__name1_0='foo' (Nullable = false) +@__name2_1='bar' (Nullable = false) +@__name3_2='baz' (Nullable = false) +@__name4_3='baq' (Nullable = false) + +SELECT b."Id", b."Name" +FROM "Blogs" AS b +WHERE b."Name" = @__name1_0 OR b."Name" = @__name2_1 OR b."Name" = @__name3_2 OR b."Name" = @__name4_3 +"""); + } + + #region Tests for the different querying enumerables + + public override async Task Include_single_query() + { + await base.Include_single_query(); + + AssertSql( + """ +SELECT b."Id", b."Name", p."Id", p."BlogId", p."Title" +FROM "Blogs" AS b +LEFT JOIN "Post" AS p ON b."Id" = p."BlogId" +ORDER BY b."Id" NULLS FIRST +"""); + } + + public override async Task Include_split_query() + { + await base.Include_split_query(); + + AssertSql( + """ +SELECT b."Id", b."Name" +FROM "Blogs" AS b +ORDER BY b."Id" NULLS FIRST +""", + // + """ +SELECT p."Id", p."BlogId", p."Title", b."Id" +FROM "Blogs" AS b +INNER JOIN "Post" AS p ON b."Id" = p."BlogId" +ORDER BY b."Id" NULLS FIRST +"""); + } + + public override async Task Final_GroupBy() + { + await base.Final_GroupBy(); + + AssertSql( + """ +SELECT b."Name", b."Id" +FROM "Blogs" AS b +ORDER BY b."Name" NULLS FIRST +"""); + } + + #endregion Tests for the different querying enumerables + + public class PrecompiledSqlPregenerationQueryNpgsqlFixture : PrecompiledSqlPregenerationQueryRelationalFixture + { + protected override ITestStoreFactory TestStoreFactory + => NpgsqlTestStoreFactory.Instance; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + { + builder = base.AddOptions(builder); + + // TODO: Figure out if there's a nice way to continue using the retrying strategy + var npgsqlOptionsBuilder = new NpgsqlDbContextOptionsBuilder(builder); + npgsqlOptionsBuilder + .ExecutionStrategy(d => new NonRetryingExecutionStrategy(d)); + return builder; + } + + public override PrecompiledQueryTestHelpers PrecompiledQueryTestHelpers => NpgsqlPrecompiledQueryTestHelpers.Instance; + } +} diff --git a/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlPrecompiledQueryTestHelpers.cs b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlPrecompiledQueryTestHelpers.cs new file mode 100644 index 000000000..104096b41 --- /dev/null +++ b/test/EFCore.PG.FunctionalTests/TestUtilities/NpgsqlPrecompiledQueryTestHelpers.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; + +public class NpgsqlPrecompiledQueryTestHelpers : PrecompiledQueryTestHelpers +{ + public static NpgsqlPrecompiledQueryTestHelpers Instance = new(); + + protected override IEnumerable BuildProviderMetadataReferences() + { + yield return MetadataReference.CreateFromFile(typeof(NpgsqlOptionsExtension).Assembly.Location); + yield return MetadataReference.CreateFromFile(typeof(NpgsqlConnection).Assembly.Location); + yield return MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location); + } +} From 922c0896a5bc84ff3fd387946520a843843baa4b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 23 Sep 2024 23:33:58 +0200 Subject: [PATCH 063/107] Catch exception instead of trying to check if the history repository exists (#3294) Fixes #2878 --- .../Internal/NpgsqlHistoryRepository.cs | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index ddc8d3be7..b0469daa9 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -75,22 +75,8 @@ private RelationalCommandParameterObject CreateRelationalCommandParameters() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string ExistsSql - { - get - { - var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); - - return - $""" -SELECT EXISTS ( - SELECT 1 FROM pg_catalog.pg_class c - JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace - WHERE n.nspname={stringTypeMapping.GenerateSqlLiteral(TableSchema ?? "public")} AND - c.relname={stringTypeMapping.GenerateSqlLiteral(TableName)} -) -"""; - } - } + => throw new UnreachableException( + "We should not be checking for the existence of the history table, but rather creating it and catching exceptions (see below)"); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -194,6 +180,58 @@ public override string GetEndIfScript() END $EF$; """; + /// + /// Calls the base implementation, but catches "table not found" exceptions; we do this rather than try to detect whether the + /// migration table already exists (see override below), since it's difficult to reliably check if the + /// migration history table exists or not (because user may set PG search_path, which determines unqualified tables + /// references when creating, selecting). + /// + public override IReadOnlyList GetAppliedMigrations() + { + try + { + return base.GetAppliedMigrations(); + } + catch (PostgresException e) when (e.SqlState is "3D000" or "42P01") + { + return []; + } + } + + /// + /// Calls the base implementation, but catches "table not found" exceptions; we do this rather than try to detect whether the + /// migration table already exists (see override below), since it's difficult to reliably check if the + /// migration history table exists or not (because user may set PG search_path, which determines unqualified tables + /// references when creating, selecting). + /// + public override async Task> GetAppliedMigrationsAsync(CancellationToken cancellationToken = default) + { + try + { + return await base.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); + } + catch (PostgresException e) when (e.SqlState is "3D000" or "42P01") + { + return []; + } + } + + /// + /// Always returns for PostgreSQL - it's difficult to reliably check if the migration history table + /// exists or not (because user may set PG search_path, which determines unqualified tables references when creating, + /// selecting). So we instead catch the "table doesn't exist" exceptions instead. + /// + public override bool Exists() + => true; + + /// + /// Always returns for PostgreSQL - it's difficult to reliably check if the migration history table + /// exists or not (because user may set PG search_path, which determines unqualified tables references when creating, + /// selecting). So we instead catch the "table doesn't exist" exceptions instead. + /// + public override Task ExistsAsync(CancellationToken cancellationToken = default) + => Task.FromResult(true); + private sealed class NpgsqlMigrationDatabaseLock(IHistoryRepository historyRepository) : IMigrationsDatabaseLock { /// From d316b35b6cf33279f23a0cb87edff2ca5e549a52 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 26 Sep 2024 22:00:51 +0200 Subject: [PATCH 064/107] Properly escape constant regex patterns (#3299) Fixes #3292 --- .../Query/Internal/NpgsqlQuerySqlGenerator.cs | 2 +- .../Query/NorthwindFunctionsQueryNpgsqlTest.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index 3418d5045..5c1ed24f1 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -987,7 +987,7 @@ protected virtual Expression VisitRegexMatch(PgRegexMatchExpression expression, } else { - Sql.Append(constantPattern); + Sql.Append(constantPattern.Replace("'", "''")); Sql.Append("'"); } diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs index f522b5ac4..43c78f8c2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindFunctionsQueryNpgsqlTest.cs @@ -111,6 +111,23 @@ await AssertQuery( """); } + [Theory] + [MemberData(nameof(IsAsyncData))] + public async Task Regex_IsMatch_with_constant_pattern_properly_escaped(bool async) + { + await AssertQuery( + async, + cs => cs.Set().Where(c => Regex.IsMatch(c.CompanyName, "^A';foo")), + assertEmpty: true); + + AssertSql( + """ +SELECT c."CustomerID", c."Address", c."City", c."CompanyName", c."ContactName", c."ContactTitle", c."Country", c."Fax", c."Phone", c."PostalCode", c."Region" +FROM "Customers" AS c +WHERE c."CompanyName" ~ '(?p)^A'';foo' +"""); + } + [Theory] [MemberData(nameof(IsAsyncData))] public async Task Regex_IsMatch_with_parameter_pattern(bool async) From 58608e88dbe8b191293b6e3875e71ded11a80464 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 27 Sep 2024 19:33:16 +0200 Subject: [PATCH 065/107] Handle another error code when concurrently creating the migrations history table (#3301) --- .../Migrations/Internal/NpgsqlHistoryRepository.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index b0469daa9..3c8513ec3 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -98,7 +98,9 @@ bool IHistoryRepository.CreateIfNotExists() GetCreateIfNotExistsCommands(), Dependencies.Connection, new MigrationExecutionState(), commitTransaction: true) != 0; } - catch (PostgresException e) when (e.SqlState is "23505" or "42P07") + catch (PostgresException e) when (e.SqlState is PostgresErrorCodes.UniqueViolation + or PostgresErrorCodes.DuplicateTable + or PostgresErrorCodes.DuplicateObject) { return false; } @@ -116,7 +118,9 @@ async Task IHistoryRepository.CreateIfNotExistsAsync(CancellationToken can cancellationToken: cancellationToken).ConfigureAwait(false)) != 0; } - catch (PostgresException e) when (e.SqlState is "23505" or "42P07") + catch (PostgresException e) when (e.SqlState is PostgresErrorCodes.UniqueViolation + or PostgresErrorCodes.DuplicateTable + or PostgresErrorCodes.DuplicateObject) { return false; } @@ -192,7 +196,7 @@ public override IReadOnlyList GetAppliedMigrations() { return base.GetAppliedMigrations(); } - catch (PostgresException e) when (e.SqlState is "3D000" or "42P01") + catch (PostgresException e) when (e.SqlState is PostgresErrorCodes.InvalidCatalogName or PostgresErrorCodes.UndefinedTable) { return []; } @@ -210,7 +214,7 @@ public override async Task> GetAppliedMigrationsAsync( { return await base.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); } - catch (PostgresException e) when (e.SqlState is "3D000" or "42P01") + catch (PostgresException e) when (e.SqlState is PostgresErrorCodes.InvalidCatalogName or PostgresErrorCodes.UndefinedTable) { return []; } From 303ffc3bd71910d4bb079dfb9490b6f0f5e5e4fc Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 30 Sep 2024 09:55:13 +0200 Subject: [PATCH 066/107] Prefix JSON geometry representation with SRID (#3307) Fixes #3236 --- .../NpgsqlJsonGeometryWktReaderWriter.cs | 13 ++- .../JsonTypesNpgsqlTest.cs | 98 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs index 3b5920e27..4685aa0ce 100644 --- a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs +++ b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs @@ -29,7 +29,18 @@ public override Geometry FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, Geometry value) - => writer.WriteStringValue(value.ToText()); + { + var wkt = value.ToText(); + + // If the SRID is defined, prefix the WKT with it (SRID=4326;POINT(-44.3 60.1)) + // Although this is a PostgreSQL extension, NetTopologySuite supports it (see #3236) + if (value.SRID > 0) + { + wkt = $"SRID={value.SRID};{wkt}"; + } + + writer.WriteStringValue(wkt); + } /// public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index 575433a57..1f15efa71 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -3,6 +3,8 @@ using System.Collections; using System.Globalization; using System.Numerics; +using NetTopologySuite; +using NetTopologySuite.Geometries; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; using Xunit.Sdk; @@ -463,6 +465,102 @@ public virtual Task Can_read_write_LogSequenceNumber_JSON_values(ulong value, st new NpgsqlLogSequenceNumber(value), json); + [ConditionalFact] + public override async Task Can_read_write_point() + { + var factory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + + await Can_read_and_write_JSON_value( + nameof(PointType.Point), + factory.CreatePoint(new Coordinate(2, 4)), + """{"Prop":"SRID=4326;POINT (2 4)"}"""); + } + + [ConditionalFact] + public virtual async Task Can_read_write_point_without_SRID() + => await Can_read_and_write_JSON_value( + nameof(PointType.Point), + new Point(2, 4), + """{"Prop":"POINT (2 4)"}"""); + + [ConditionalFact] + public override async Task Can_read_write_point_with_M() + { + var factory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + + await Can_read_and_write_JSON_value( + nameof(PointMType.PointM), + factory.CreatePoint(new CoordinateM(2, 4, 6)), + """{"Prop":"SRID=4326;POINT (2 4)"}"""); + } + + public override async Task Can_read_write_point_with_Z() + { + var factory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + + await Can_read_and_write_JSON_value( + nameof(PointZType.PointZ), + factory.CreatePoint(new CoordinateZ(2, 4, 6)), + """{"Prop":"SRID=4326;POINT Z(2 4 6)"}"""); + } + + public override async Task Can_read_write_point_with_Z_and_M() + { + var factory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + + await Can_read_and_write_JSON_value( + nameof(PointZMType.PointZM), + factory.CreatePoint(new CoordinateZM(1, 2, 3, 4)), + """{"Prop":"SRID=4326;POINT Z(1 2 3)"}"""); + } + + [ConditionalFact] + public override async Task Can_read_write_line_string() + { + var factory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + + await Can_read_and_write_JSON_value( + nameof(LineStringType.LineString), + factory.CreateLineString([new Coordinate(0, 0), new Coordinate(1, 0)]), + """{"Prop":"SRID=4326;LINESTRING (0 0, 1 0)"}"""); + } + + public override async Task Can_read_write_multi_line_string() + { + var factory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + + await Can_read_and_write_JSON_value( + nameof(MultiLineStringType.MultiLineString), + factory.CreateMultiLineString( + [ + factory.CreateLineString( + [new Coordinate(0, 0), new Coordinate(0, 1)]), + factory.CreateLineString( + [new Coordinate(1, 0), new Coordinate(1, 1)]) + ]), + """{"Prop":"SRID=4326;MULTILINESTRING ((0 0, 0 1), (1 0, 1 1))"}"""); + } + + public override async Task Can_read_write_polygon() + { + var factory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + + await Can_read_and_write_JSON_value( + nameof(PolygonType.Polygon), + factory.CreatePolygon([new Coordinate(0, 0), new Coordinate(1, 0), new Coordinate(0, 1), new Coordinate(0, 0)]), + """{"Prop":"SRID=4326;POLYGON ((0 0, 1 0, 0 1, 0 0))"}"""); + } + + public override async Task Can_read_write_polygon_typed_as_geometry() + { + var factory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326); + + await Can_read_and_write_JSON_value( + nameof(GeometryType.Geometry), + factory.CreatePolygon([new Coordinate(0, 0), new Coordinate(1, 0), new Coordinate(0, 1), new Coordinate(0, 0)]), + """{"Prop":"SRID=4326;POLYGON ((0 0, 1 0, 0 1, 0 0))"}"""); + } + protected class LogSequenceNumberType { public NpgsqlLogSequenceNumber LogSequenceNumber { get; set; } From 8d6301c933ec30e1bf77b65b840c5f3bf29009db Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 13 Oct 2024 20:50:11 +0200 Subject: [PATCH 067/107] Bump dependencies (#3316) * EF 9.0.0-rc.2.24474.1 * Npgsql 8.0.5 * dotnet SDK 9.0.100-rc.2 --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- Directory.Packages.props | 10 +++------- global.json | 2 +- .../EFCore.PG.FunctionalTests.csproj | 4 ---- .../Query/PrimitiveCollectionsQueryNpgsqlTest.cs | 16 ++++++++++++++++ 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 62d3c4038..202f1bed6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: pull_request: env: - dotnet_sdk_version: '9.0.100-rc.1.24452.12' + dotnet_sdk_version: '9.0.100-rc.2.24474.11' postgis_version: 3 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d8101b9f1..655cd1811 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ on: - cron: '30 22 * * 6' env: - dotnet_sdk_version: '9.0.100-rc.1.24452.12' + dotnet_sdk_version: '9.0.100-rc.2.24474.11' jobs: analyze: diff --git a/Directory.Packages.props b/Directory.Packages.props index 91a34ec82..b71a08758 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,8 +1,8 @@ - [9.0.0-rc.2.24460.3] - 9.0.0-rc.2.24456.9 - 8.0.4 + [9.0.0-rc.2.24474.1] + 9.0.0-rc.2.24473.5 + 8.0.5 @@ -21,10 +21,6 @@ - - - - diff --git a/global.json b/global.json index 22cf64c12..58f954fc2 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100-rc.1.24452.12", + "version": "9.0.100-rc.2.24474.11", "rollForward": "latestMajor", "allowPrerelease": true } diff --git a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj index 4011ace02..ce5bd43a8 100644 --- a/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj +++ b/test/EFCore.PG.FunctionalTests/EFCore.PG.FunctionalTests.csproj @@ -19,10 +19,6 @@ - - - - diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index b9a92210d..b83f8bdb2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -1418,6 +1418,22 @@ WHERE cardinality(@__ints_0 || p."Ints") = 2 """); } + public override async Task Parameter_collection_with_type_inference_for_JsonScalarExpression(bool async) + { + await base.Parameter_collection_with_type_inference_for_JsonScalarExpression(async); + + AssertSql( + """ +@__values_0={ 'one', 'two' } (DbType = Object) + +SELECT CASE + WHEN p."Id" <> 0 THEN @__values_0[p."Int" % 2 + 1] + ELSE 'foo' +END +FROM "PrimitiveCollectionsEntity" AS p +"""); + } + public override async Task Column_collection_Union_parameter_collection(bool async) { await base.Column_collection_Union_parameter_collection(async); From a8677e4f9afe4e5955bc4d810cc0665c7a49d4f3 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 13 Oct 2024 21:09:42 +0200 Subject: [PATCH 068/107] Configure CI for released PG17 (#3300) --- .github/workflows/build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 202f1bed6..adff570bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,16 +23,16 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, windows-2022] - pg_major: [16, 15, 14, 13, 12] + pg_major: [17, 16, 15, 14, 13, 12] config: [Release] include: - - os: ubuntu-22.04 - pg_major: 16 - config: Debug - os: ubuntu-22.04 pg_major: 17 - config: Release - pg_prerelease: 'PG Prerelease' + config: Debug +# - os: ubuntu-22.04 +# pg_major: 17 +# config: Release +# pg_prerelease: 'PG Prerelease' outputs: is_release: ${{ steps.analyze_tag.outputs.is_release }} From 1e3b9b292f54155f1e63d3efa12eb3f1024412fa Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 26 Oct 2024 16:51:32 +0200 Subject: [PATCH 069/107] Translate array.Where(i => i != x) to array_remove (#3328) Closes #3078 --- ...yableMethodTranslatingExpressionVisitor.cs | 55 +++++++++++++++++++ ...thwindAggregateOperatorsQueryNpgsqlTest.cs | 22 ++++++-- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 17 ++++++ 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs index 9453c824b..8d2bc0623 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs @@ -968,6 +968,61 @@ [new PgUnnestExpression(tableAlias, sliceExpression, "value")], return base.TranslateTake(source, count); } + /// + /// 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 ShapedQueryExpression? TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate) + { + // Simplify x.Array.Where(i => i != 3) => array_remove(x.Array, 3) instead of subquery + if (predicate.Body is BinaryExpression + { + NodeType: ExpressionType.NotEqual, + Left: var left, + Right: var right + } + && (left == predicate.Parameters[0] ? right : right == predicate.Parameters[0] ? left : null) is Expression itemToFilterOut + && source.TryExtractArray(out var array, out var projectedColumn) + && TranslateExpression(itemToFilterOut) is SqlExpression translatedItemToFilterOut) + { + var simplifiedTranslation = _sqlExpressionFactory.Function( + "array_remove", + [array, translatedItemToFilterOut], + nullable: true, + argumentsPropagateNullability: TrueArrays[2], + array.Type, + array.TypeMapping); + +#pragma warning disable EF1001 // SelectExpression constructors are currently internal + var tableAlias = ((SelectExpression)source.QueryExpression).Tables[0].Alias!; + var selectExpression = new SelectExpression( + [new PgUnnestExpression(tableAlias, simplifiedTranslation, "value")], + new ColumnExpression("value", tableAlias, projectedColumn.Type, projectedColumn.TypeMapping, projectedColumn.IsNullable), + [GenerateOrdinalityIdentifier(tableAlias)], + _queryCompilationContext.SqlAliasManager); +#pragma warning restore EF1001 // Internal EF Core API usage. + + // TODO: Simplify by using UpdateQueryExpression after https://github.com/dotnet/efcore/issues/31511 + Expression shaperExpression = new ProjectionBindingExpression( + selectExpression, new ProjectionMember(), source.ShaperExpression.Type.MakeNullable()); + + if (source.ShaperExpression.Type != shaperExpression.Type) + { + Check.DebugAssert( + source.ShaperExpression.Type.MakeNullable() == shaperExpression.Type, + "expression.Type must be nullable of targetType"); + + shaperExpression = Expression.Convert(shaperExpression, source.ShaperExpression.Type); + } + + return new ShapedQueryExpression(selectExpression, shaperExpression); + } + + return base.TranslateWhere(source, predicate); + } + /// /// 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 diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs index 6765d9481..fd286401b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs @@ -110,12 +110,24 @@ await Assert.ThrowsAsync( public override async Task Contains_with_local_enumerable_inline_closure_mix(bool async) { - // Issue #31776 - await Assert.ThrowsAsync( - async () => - await base.Contains_with_local_enumerable_inline_closure_mix(async)); + await base.Contains_with_local_enumerable_inline_closure_mix(async); - AssertSql(); + AssertSql( + """ +@__p_0={ 'ABCDE', 'ALFKI' } (DbType = Object) + +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" = ANY (array_remove(@__p_0, NULL)) +""", + // + """ +@__p_0={ 'ABCDE', 'ANATR' } (DbType = Object) + +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" = ANY (array_remove(@__p_0, NULL)) +"""); } public override async Task Contains_with_local_non_primitive_list_closure_mix(bool async) diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index b83f8bdb2..1fd148a09 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -2027,6 +2027,23 @@ WHERE CASE """); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Array_remove(bool async) + { + await AssertQuery( + async, + // ReSharper disable once ReplaceWithSingleCallToCount + ss => ss.Set().Where(e => e.Ints.Where(i => i != 1).Count() == 1)); + + AssertSql( + """ +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE cardinality(array_remove(p."Ints", 1)) = 1 +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); From 90e2c70759488c0924ab0f3c5eca64b3bf799d4e Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 27 Oct 2024 20:57:59 +0100 Subject: [PATCH 070/107] Remove enum/extension conventions when creating migrations history table (#3329) Fixes #3324 --- .../Internal/NpgsqlHistoryRepository.cs | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index 3c8513ec3..1366a1b6e 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -1,4 +1,6 @@ -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Conventions; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Internal; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -8,6 +10,8 @@ /// public class NpgsqlHistoryRepository : HistoryRepository, IHistoryRepository { + private IModel? _model; + /// /// 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 @@ -87,6 +91,50 @@ protected override string ExistsSql protected override bool InterpretExistsResult(object? value) => (bool?)value == true; + /// + /// 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 IReadOnlyList GetCreateCommands() + { + // TODO: This is all a hack around https://github.com/dotnet/efcore/issues/34991: we have provider-specific conventions which add + // enums and extensions to the model, and the default EF logic causes them to be created at this point, when the history table is + // being created. + var model = EnsureModel(); + + var operations = Dependencies.ModelDiffer.GetDifferences(null, model.GetRelationalModel()); + var commandList = Dependencies.MigrationsSqlGenerator.Generate(operations, model); + return commandList; + } + + private IModel EnsureModel() + { + if (_model == null) + { + var conventionSet = Dependencies.ConventionSetBuilder.CreateConventionSet(); + + conventionSet.Remove(typeof(DbSetFindingConvention)); + conventionSet.Remove(typeof(RelationalDbFunctionAttributeConvention)); + // TODO: this whole method exists only so we can remove this convention (https://github.com/dotnet/efcore/issues/34991) + conventionSet.Remove(typeof(NpgsqlPostgresModelFinalizingConvention)); + + var modelBuilder = new ModelBuilder(conventionSet); + modelBuilder.Entity( + x => + { + ConfigureTable(x); + x.ToTable(TableName, TableSchema); + }); + + _model = Dependencies.ModelRuntimeInitializer.Initialize( + (IModel)modelBuilder.Model, designTime: true, validationLogger: null); + } + + return _model; + } + bool IHistoryRepository.CreateIfNotExists() { // In PG, doing CREATE TABLE IF NOT EXISTS isn't concurrency-safe, and can result a "duplicate table" error or in a unique From 222c13a40cce0a9cc1c981044e86ccbda423a4c7 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 27 Oct 2024 23:32:05 +0100 Subject: [PATCH 071/107] Use Npgsql 9.0.0-preview.1-ci.20241025T100626 (#3330) --- Directory.Packages.props | 2 +- .../Internal/NpgsqlDataSourceManager.cs | 19 ++++++++++++------- .../Internal/NpgsqlRelationalConnection.cs | 18 +++++++++++------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b71a08758..a0246f86e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ [9.0.0-rc.2.24474.1] 9.0.0-rc.2.24473.5 - 8.0.5 + 9.0.0-preview.1-ci.20241025T100626 diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs b/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs index 323935116..bd67b7689 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs @@ -136,14 +136,19 @@ enumDefinition.StoreTypeSchema is null } // Legacy authentication-related callbacks at the EF level; apply these when building a data source as well. - if (npgsqlOptionsExtension.ProvideClientCertificatesCallback is not null) + if (npgsqlOptionsExtension.ProvideClientCertificatesCallback is not null + || npgsqlOptionsExtension.RemoteCertificateValidationCallback is not null) { - dataSourceBuilder.UseClientCertificatesCallback(x => npgsqlOptionsExtension.ProvideClientCertificatesCallback(x)); - } - - if (npgsqlOptionsExtension.RemoteCertificateValidationCallback is not null) - { - dataSourceBuilder.UseUserCertificateValidationCallback(npgsqlOptionsExtension.RemoteCertificateValidationCallback); + dataSourceBuilder.UseSslClientAuthenticationOptionsCallback(o => + { + if (npgsqlOptionsExtension.ProvideClientCertificatesCallback is not null) + { + o.ClientCertificates ??= new(); + npgsqlOptionsExtension.ProvideClientCertificatesCallback(o.ClientCertificates); + } + + o.RemoteCertificateValidationCallback = npgsqlOptionsExtension.RemoteCertificateValidationCallback; + }); } // Finally, if the user has provided a data source builder configuration action, invoke it. diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs index d4363b4d7..94854dcd2 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs @@ -99,14 +99,18 @@ protected override DbConnection CreateDbConnection() var conn = new NpgsqlConnection(ConnectionString); - if (_provideClientCertificatesCallback is not null) + if (_provideClientCertificatesCallback is not null || _remoteCertificateValidationCallback is not null) { - conn.ProvideClientCertificatesCallback = _provideClientCertificatesCallback; - } - - if (_remoteCertificateValidationCallback is not null) - { - conn.UserCertificateValidationCallback = _remoteCertificateValidationCallback; + conn.SslClientAuthenticationOptionsCallback = o => + { + if (_provideClientCertificatesCallback is not null) + { + o.ClientCertificates ??= new(); + _provideClientCertificatesCallback(o.ClientCertificates); + } + + o.RemoteCertificateValidationCallback = _remoteCertificateValidationCallback; + }; } if (_providePasswordCallback is not null) From 02190c1cd7ff6c7d541176c4652cb85ae277c4fa Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 28 Oct 2024 00:26:47 +0100 Subject: [PATCH 072/107] Use Npgsql's new CloneWithAsync (#3332) Closes #3303 --- .../Storage/Internal/INpgsqlRelationalConnection.cs | 2 +- src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs | 4 +++- .../Storage/Internal/NpgsqlRelationalConnection.cs | 9 +++++++-- test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs index cfeef63a2..7c0cb1c02 100644 --- a/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs @@ -22,5 +22,5 @@ public interface INpgsqlRelationalConnection : IRelationalConnection /// 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. /// - INpgsqlRelationalConnection CloneWith(string connectionString); + ValueTask CloneWith(string connectionString, bool async, CancellationToken cancellationToken = default); } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs index 72f8e6c5f..f53909c36 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs @@ -208,7 +208,9 @@ private async Task Exists(bool async, CancellationToken cancellationToken var unpooledCsb = new NpgsqlConnectionStringBuilder(_connection.ConnectionString) { Pooling = false, Multiplexing = false }; using var _ = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled); - var unpooledRelationalConnection = _connection.CloneWith(unpooledCsb.ToString()); + var unpooledRelationalConnection = + await _connection.CloneWith(unpooledCsb.ToString(), async, cancellationToken).ConfigureAwait(false); + try { if (async) diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs index 94854dcd2..e30e6a953 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs @@ -226,9 +226,14 @@ public override Transaction? CurrentAmbientTransaction /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual INpgsqlRelationalConnection CloneWith(string connectionString) + public virtual async ValueTask CloneWith( + string connectionString, + bool async, + CancellationToken cancellationToken = default) { - var clonedDbConnection = DbConnection.CloneWith(connectionString); + var clonedDbConnection = async + ? await DbConnection.CloneWithAsync(connectionString, cancellationToken).ConfigureAwait(false) + : DbConnection.CloneWith(connectionString); var relationalOptions = RelationalOptionsExtension.Extract(Dependencies.ContextOptions) .WithConnectionString(null) diff --git a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs index ee6f3033c..3f7d9d3ed 100644 --- a/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlRelationalConnectionTest.cs @@ -350,7 +350,7 @@ public void CurrentAmbientTransaction_returns_transaction_with_enlist_enabled(st } [ConditionalFact] - public void CloneWith_with_connection_and_connection_string() + public async Task CloneWith_with_connection_and_connection_string() { var services = NpgsqlTestHelpers.Instance.CreateContextServices( new DbContextOptionsBuilder() @@ -359,7 +359,7 @@ public void CloneWith_with_connection_and_connection_string() var relationalConnection = (NpgsqlRelationalConnection)services.GetRequiredService(); - var clone = relationalConnection.CloneWith("Host=localhost;Database=DummyDatabase;Application Name=foo"); + var clone = await relationalConnection.CloneWith("Host=localhost;Database=DummyDatabase;Application Name=foo", async: true); Assert.Equal("Host=localhost;Database=DummyDatabase;Application Name=foo", clone.ConnectionString); } From c484080baff333cf9605e09f243b8c49624ad610 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 28 Oct 2024 08:42:50 +0100 Subject: [PATCH 073/107] Translations around DateOnly.DayNumber (#3333) Closes #3194 --- .../NpgsqlDateTimeMemberTranslator.cs | 24 ++++++--- .../NpgsqlDateTimeMethodTranslator.cs | 16 ++++++ .../NpgsqlSqlTranslatingExpressionVisitor.cs | 42 +++++++++++----- .../Query/NpgsqlSqlExpressionFactory.cs | 12 +++++ .../Query/TimestampQueryTest.cs | 50 +++++++++++++++++++ 5 files changed, 125 insertions(+), 19 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs index 2cbac35b8..f3a44238f 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs @@ -56,6 +56,11 @@ public NpgsqlDateTimeMemberTranslator(IRelationalTypeMappingSource typeMappingSo return translated; } + if (declaringType == typeof(DateOnly) && TranslateDateOnly(instance, member) is { } translated2) + { + return translated2; + } + if (member.Name == nameof(DateTime.Date)) { // Note that DateTime.Date returns a DateTime, not a DateOnly (introduced later); so we convert using date_trunc (which returns @@ -190,13 +195,7 @@ SqlExpression LocalNow() return _sqlExpressionFactory.Convert(result, typeof(int)); } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual SqlExpression? TranslateDateTimeOffset(SqlExpression instance, MemberInfo member) + private SqlExpression? TranslateDateTimeOffset(SqlExpression instance, MemberInfo member) => member.Name switch { // We only support UTC DateTimeOffset, so DateTimeOffset.DateTime is just a matter of converting to timestamp without time zone @@ -226,6 +225,17 @@ SqlExpression LocalNow() _ => null }; + private SqlExpression? TranslateDateOnly(SqlExpression? instance, MemberInfo member) + => member.Name switch + { + // We use fragment rather than a DateOnly constant, since 0001-01-01 gets rendered as -infinity by default. + // TODO: Set the right type/type mapping after https://github.com/dotnet/efcore/pull/34995 is merged + nameof(DateOnly.DayNumber) when instance is not null + => _sqlExpressionFactory.Subtract(instance, _sqlExpressionFactory.Fragment("DATE '0001-01-01'")), + + _ => null + }; + // Various conversion functions translated here (date_part, ::time) exist only for timestamp without time zone, so if we pass in a // timestamptz it gets implicitly converted to a local timestamp based on TimeZone; that's the wrong behavior (these conversions are not // supposed to be sensitive to TimeZone). diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs index 101ae9b19..2cb2a5d22 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMethodTranslator.cs @@ -67,6 +67,10 @@ private static readonly MethodInfo DateOnly_AddMonths private static readonly MethodInfo DateOnly_AddYears = typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddYears), [typeof(int)])!; + private static readonly MethodInfo DateOnly_FromDayNumber + = typeof(DateOnly).GetRuntimeMethod( + nameof(DateOnly.FromDayNumber), [typeof(int)])!; + private static readonly MethodInfo TimeOnly_FromDateTime = typeof(TimeOnly).GetRuntimeMethod(nameof(TimeOnly.FromDateTime), [typeof(DateTime)])!; @@ -226,6 +230,18 @@ public NpgsqlDateTimeMethodTranslator( { return _sqlExpressionFactory.MakePostgresBinary(PgExpressionType.Distance, arguments[1], arguments[2]); } + + if (method == DateOnly_FromDayNumber) + { + // We use fragment rather than a DateOnly constant, since 0001-01-01 gets rendered as -infinity by default. + // TODO: Set the right type/type mapping after https://github.com/dotnet/efcore/pull/34995 is merged + return new SqlBinaryExpression( + ExpressionType.Add, + _sqlExpressionFactory.Fragment("DATE '0001-01-01'"), + arguments[0], + typeof(DateOnly), + _typeMappingSource.FindMapping(typeof(DateOnly))); + } } else { diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 39c670f2b..7e2fd3d47 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs @@ -218,21 +218,39 @@ when binaryExpression.Left.Type.UnwrapNullableType().FullName == "NodaTime.Local var translation = base.VisitBinary(binaryExpression); - // A somewhat hacky workaround for #2942. - // When an optional owned JSON entity is compared to null, we get WHERE (x -> y) IS NULL. - // The -> operator (returning jsonb) is used rather than ->> (returning text), since an entity type is being extracted, and further - // JSON operations may need to be composed. However, when the value extracted is a JSON null, a non-NULL jsonb value is returned, - // and comparing that to relational NULL returns false. - // Pattern-match this and force the use of ->> by changing the mapping to be a scalar rather than an entity type. - if (translation is SqlUnaryExpression + switch (translation) + { + // Optimize (x - c) - (y - c) to x - y. + // This is particularly useful for DateOnly.DayNumber - DateOnly.DayNumber, which is the way to express DateOnly subtraction + // (the subtraction operator isn't defined over DateOnly in .NET). The translation of x.DayNumber is x - DATE '0001-01-01', + // so the below is a useful simplification. + // TODO: As this is a generic mathematical simplification, we should move it to a generic optimization phase in EF Core. + case SqlBinaryExpression + { + OperatorType: ExpressionType.Subtract, + Left: SqlBinaryExpression { OperatorType: ExpressionType.Subtract, Left: var left1, Right: var right1 }, + Right: SqlBinaryExpression { OperatorType: ExpressionType.Subtract, Left: var left2, Right: var right2 } + } originalBinary when right1.Equals(right2): + { + return new SqlBinaryExpression(ExpressionType.Subtract, left1, left2, originalBinary.Type, originalBinary.TypeMapping); + } + + // A somewhat hacky workaround for #2942. + // When an optional owned JSON entity is compared to null, we get WHERE (x -> y) IS NULL. + // The -> operator (returning jsonb) is used rather than ->> (returning text), since an entity type is being extracted, and + // further JSON operations may need to be composed. However, when the value extracted is a JSON null, a non-NULL jsonb value is + // returned, and comparing that to relational NULL returns false. + // Pattern-match this and force the use of ->> by changing the mapping to be a scalar rather than an entity type. + case SqlUnaryExpression { OperatorType: ExpressionType.Equal or ExpressionType.NotEqual, Operand: JsonScalarExpression { TypeMapping: NpgsqlOwnedJsonTypeMapping } operand - } unary) - { - return unary.Update( - new JsonScalarExpression( - operand.Json, operand.Path, operand.Type, _typeMappingSource.FindMapping("text"), operand.IsNullable)); + } unary: + { + return unary.Update( + new JsonScalarExpression( + operand.Json, operand.Path, operand.Type, _typeMappingSource.FindMapping("text"), operand.IsNullable)); + } } return translation; diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 84fac6794..73c64c92c 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -476,6 +476,18 @@ private SqlBinaryExpression ApplyTypeMappingOnSqlBinary(SqlBinaryExpression bina binary.Type, typeMapping ?? _typeMappingSource.FindMapping(binary.Type, "interval")); } + + // TODO: This is a hack until https://github.com/dotnet/efcore/pull/34995 is done; the translation of DateOnly.DayNumber + // generates a substraction with a fragment, but for now we can't assign a type/type mapping to a fragment. + case ExpressionType.Subtract when left.Type == typeof(DateOnly) && right is SqlFragmentExpression: + { + return new SqlBinaryExpression( + ExpressionType.Subtract, + ApplyDefaultTypeMapping(left), + right, + typeof(int), + _typeMappingSource.FindMapping(typeof(int))); + } } // If this is a row value comparison (e.g. (a, b) > (5, 6)), doing type mapping inference on each corresponding pair. diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index 429c4cc9c..7b6e17486 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -744,6 +744,56 @@ WHERE CAST(e."TimestamptzDateTime" AT TIME ZONE 'UTC' AS date) + TIME '15:26:38' """); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task DateOnly_DayNumber(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(e => DateOnly.FromDateTime(e.TimestamptzDateTime).DayNumber == 729490)); + + AssertSql( + """ +SELECT e."Id", e."TimestampDateTime", e."TimestampDateTimeArray", e."TimestampDateTimeOffset", e."TimestampDateTimeOffsetArray", e."TimestampDateTimeRange", e."TimestamptzDateTime", e."TimestamptzDateTimeArray", e."TimestamptzDateTimeRange" +FROM "Entities" AS e +WHERE CAST(e."TimestamptzDateTime" AT TIME ZONE 'UTC' AS date) - DATE '0001-01-01' = 729490 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task DateOnly_DayNumber_subtraction(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where( + e => DateOnly.FromDateTime(e.TimestamptzDateTime).DayNumber - + DateOnly.FromDateTime(e.TimestamptzDateTime - TimeSpan.FromDays(3)).DayNumber == 3)); + + AssertSql( + """ +SELECT e."Id", e."TimestampDateTime", e."TimestampDateTimeArray", e."TimestampDateTimeOffset", e."TimestampDateTimeOffsetArray", e."TimestampDateTimeRange", e."TimestamptzDateTime", e."TimestamptzDateTimeArray", e."TimestamptzDateTimeRange" +FROM "Entities" AS e +WHERE CAST(e."TimestamptzDateTime" AT TIME ZONE 'UTC' AS date) - CAST((e."TimestamptzDateTime" - INTERVAL '3 00:00:00') AT TIME ZONE 'UTC' AS date) = 3 +"""); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task DateOnly_FromDayNumber(bool async) + { + await AssertQuery( + async, + ss => ss.Set().Where(e => DateOnly.FromDayNumber(e.Id) == new DateOnly(0001, 01, 03))); + + AssertSql( + """ +SELECT e."Id", e."TimestampDateTime", e."TimestampDateTimeArray", e."TimestampDateTimeOffset", e."TimestampDateTimeOffsetArray", e."TimestampDateTimeRange", e."TimestamptzDateTime", e."TimestamptzDateTimeArray", e."TimestamptzDateTimeRange" +FROM "Entities" AS e +WHERE DATE '0001-01-01' + e."Id" = DATE '0001-01-03' +"""); + } + #endregion DateOnly #region TimeOnly From 342c5606a18adb63ef634a5a8abd163dec2f79d0 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 28 Oct 2024 10:53:20 +0100 Subject: [PATCH 074/107] Support mapping jsonpath (#3334) Closes #3044 --- src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs | 2 ++ test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index d1b922231..9d17aae52 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -84,6 +84,7 @@ static NpgsqlTypeMappingSource() private readonly NpgsqlCharacterCharTypeMapping _singleChar = new("character(1)"); private readonly NpgsqlStringTypeMapping _xml = new("xml", NpgsqlDbType.Xml); private readonly NpgsqlStringTypeMapping _citext = new("citext", NpgsqlDbType.Citext); + private readonly NpgsqlStringTypeMapping _jsonpath = new("jsonpath", NpgsqlDbType.JsonPath); // JSON mappings - EF owned entity support private readonly NpgsqlOwnedJsonTypeMapping _jsonbOwned = new("jsonb"); @@ -234,6 +235,7 @@ public NpgsqlTypeMappingSource( { "text", [_text] }, { "jsonb", [_jsonbString, _jsonbDocument, _jsonbElement] }, { "json", [_jsonString, _jsonDocument, _jsonElement] }, + { "jsonpath", [_jsonpath] }, { "xml", [_xml] }, { "citext", [_citext] }, { "character varying", [_varchar] }, diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs index e95bbdd19..3809227de 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -41,6 +41,7 @@ public class NpgsqlTypeMappingSourceTest [InlineData("geometry(POLYGONM)", typeof(Polygon), null, null, null, false)] [InlineData("xid", typeof(uint), null, null, null, false)] [InlineData("xid8", typeof(ulong), null, null, null, false)] + [InlineData("jsonpath", typeof(string), null, null, null, false)] public void By_StoreType(string typeName, Type type, int? size, int? precision, int? scale, bool fixedLength) { var mapping = CreateTypeMappingSource().FindMapping(typeName); From 72cb52e7fef5c64fcf6f61e2886010a8994fcfbb Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 28 Oct 2024 22:21:43 +0100 Subject: [PATCH 075/107] Call NpgsqlDataSource.Clear when doing EnsureDeleted (#3337) Fixes #3326 --- Directory.Packages.props | 2 +- .../Internal/INpgsqlRelationalConnection.cs | 12 ++++++++- .../Storage/Internal/NpgsqlDatabaseCreator.cs | 21 +++++++++++++-- .../Internal/NpgsqlRelationalConnection.cs | 26 ++++++++++++------- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a0246f86e..1bd68eb76 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ [9.0.0-rc.2.24474.1] 9.0.0-rc.2.24473.5 - 9.0.0-preview.1-ci.20241025T100626 + 9.0.0-preview.1-ci.20241028T080028 diff --git a/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs index 7c0cb1c02..f8f62958f 100644 --- a/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/INpgsqlRelationalConnection.cs @@ -1,4 +1,6 @@ -namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; +using System.Data.Common; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -8,6 +10,14 @@ /// public interface INpgsqlRelationalConnection : IRelationalConnection { + /// + /// 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. + /// + DbDataSource? DataSource { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs index f53909c36..884cb6431 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs @@ -273,7 +273,15 @@ private static bool IsDoesNotExist(PostgresException exception) /// public override void Delete() { - ClearAllPools(); + switch (_connection.DataSource) + { + case NpgsqlDataSource dataSource: + dataSource.Clear(); + break; + case null: + ClearAllPools(); + break; + } using (var masterConnection = _connection.CreateAdminConnection()) { @@ -290,7 +298,16 @@ public override void Delete() /// public override async Task DeleteAsync(CancellationToken cancellationToken = default) { - ClearAllPools(); + switch (_connection.DataSource) + { + case NpgsqlDataSource dataSource: + // TODO: Do this asynchronously once https://github.com/npgsql/npgsql/issues/4499 is done + dataSource.Clear(); + break; + case null: + ClearAllPools(); + break; + } var masterConnection = _connection.CreateAdminConnection(); await using (masterConnection) diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs index e30e6a953..89df883b0 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs @@ -21,7 +21,13 @@ public class NpgsqlRelationalConnection : RelationalConnection, INpgsqlRelationa private readonly ProvidePasswordCallback? _providePasswordCallback; #pragma warning restore CS0618 - private DbDataSource? _dataSource; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public DbDataSource? DataSource { get; private set; } /// /// Indicates whether the store connection supports ambient transactions @@ -58,7 +64,7 @@ protected NpgsqlRelationalConnection(RelationalConnectionDependencies dependenci { if (dataSource is not null) { - _dataSource = dataSource; + DataSource = dataSource; #if DEBUG // We validate in NpgsqlOptionsExtensions.Validate that DataSource and these callbacks aren't specified together @@ -92,9 +98,9 @@ protected NpgsqlRelationalConnection(RelationalConnectionDependencies dependenci /// protected override DbConnection CreateDbConnection() { - if (_dataSource is not null) + if (DataSource is not null) { - return _dataSource.CreateConnection(); + return DataSource.CreateConnection(); } var conn = new NpgsqlConnection(ConnectionString); @@ -132,12 +138,12 @@ protected override DbConnection CreateDbConnection() // TODO: Remove after DbDataSource support is added to EF Core (https://github.com/dotnet/efcore/issues/28266) public override string? ConnectionString { - get => _dataSource is null ? base.ConnectionString : _dataSource.ConnectionString; + get => DataSource is null ? base.ConnectionString : DataSource.ConnectionString; set { base.ConnectionString = value; - _dataSource = null; + DataSource = null; } } @@ -155,7 +161,7 @@ public override string? ConnectionString { base.DbConnection = value; - _dataSource = null; + DataSource = null; } } @@ -167,12 +173,12 @@ public override string? ConnectionString /// public virtual DbDataSource? DbDataSource { - get => _dataSource; + get => DataSource; set { DbConnection = null; ConnectionString = null; - _dataSource = value; + DataSource = value; } } @@ -196,7 +202,7 @@ public virtual INpgsqlRelationalConnection CreateAdminConnection() Multiplexing = false }.ToString(); - var adminNpgsqlOptions = _dataSource is not null + var adminNpgsqlOptions = DataSource is not null ? npgsqlOptions.WithConnection(((NpgsqlConnection)CreateDbConnection()).CloneWith(adminConnectionString)) : npgsqlOptions.Connection is not null ? npgsqlOptions.WithConnection(DbConnection.CloneWith(adminConnectionString)) From 45c82278109f56ec11076e2e7b93d1ff77a097f4 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 29 Oct 2024 17:20:18 +0100 Subject: [PATCH 076/107] Fix JsonValueReaderWriter check to support polymorphism (#3339) Fixes #3169 --- .../Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index 6672f0520..052d653e4 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -142,10 +142,10 @@ private static RelationalTypeMappingParameters CreateParameters(string storeType #pragma warning restore EF1001 var elementJsonReaderWriter = elementMapping.JsonValueReaderWriter; - if (elementJsonReaderWriter is not null && elementJsonReaderWriter.ValueType != typeof(TElement).UnwrapNullableType()) + if (elementJsonReaderWriter is not null && !typeof(TElement).UnwrapNullableType().IsAssignableTo(elementJsonReaderWriter.ValueType)) { throw new InvalidOperationException( - $"When building an array mapping over '{typeof(TElement).Name}', the JsonValueReaderWriter for element mapping '{elementMapping.GetType().Name}' is incorrect ('{elementMapping.JsonValueReaderWriter?.GetType().Name ?? ""}' instead of '{typeof(TElement).UnwrapNullableType()}')."); + $"When building an array mapping over '{typeof(TElement).Name}', the JsonValueReaderWriter for element mapping '{elementMapping.GetType().Name}' is incorrect ('{elementJsonReaderWriter.ValueType.GetType().Name}' instead of '{typeof(TElement).UnwrapNullableType()}', the JsonValueReaderWriter is '{elementJsonReaderWriter.GetType().Name}')."); } // If there's no JsonValueReaderWriter on the element, we also don't set one on its array (this is for rare edge cases such as From e4375aada3a704dd0de089c0b759dcafce878948 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 29 Oct 2024 22:41:46 +0100 Subject: [PATCH 077/107] Fix scalar casting from string for POCO/DOM JSON mode (#3340) Fixes #2774 Fixes #3158 --- .../Internal/NpgsqlJsonDomTranslator.cs | 30 +--------- .../Internal/NpgsqlJsonPocoTranslator.cs | 56 +++++++------------ 2 files changed, 24 insertions(+), 62 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs index 7a94102cb..ec838d19d 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs @@ -130,9 +130,11 @@ public NpgsqlJsonDomTranslator( typeof(string), _stringTypeMapping); + // The PostgreSQL traversal operator always returns text - for these scalar-returning methods, apply a conversion from string. return method.Name == nameof(JsonElement.GetString) ? traversalToText - : ConvertFromText(traversalToText, method.ReturnType); + : _sqlExpressionFactory.Convert( + traversalToText, method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model)); } if (method == GetArrayLength) @@ -152,30 +154,4 @@ public NpgsqlJsonDomTranslator( return null; } - - // The PostgreSQL traversal operator always returns text, so we need to convert to int, bool, etc. - private SqlExpression ConvertFromText(SqlExpression expression, Type returnType) - { - switch (Type.GetTypeCode(returnType)) - { - case TypeCode.Boolean: - case TypeCode.Byte: - case TypeCode.DateTime: - case TypeCode.Decimal: - case TypeCode.Double: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.SByte: - case TypeCode.Single: - case TypeCode.UInt16: - case TypeCode.UInt32: - case TypeCode.UInt64: - return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model)); - default: - return returnType == typeof(Guid) - ? _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model)) - : expression; - } - } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs index 61a0946c3..fbaf19142 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs @@ -97,7 +97,8 @@ public NpgsqlJsonPocoTranslator( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual SqlExpression? TranslateMemberAccess(SqlExpression instance, SqlExpression member, Type returnType) - => instance switch + { + return instance switch { // The first time we see a JSON traversal it's on a column - create a JsonTraversalExpression. // Traversals on top of that get appended into the same expression. @@ -119,6 +120,25 @@ PgJsonTraversalExpression prevPathTraversal _ => null }; + // The PostgreSQL traversal operator always returns text. + // If the type returned is a scalar (int, bool, etc.), we need to apply a conversion from string. + SqlExpression ConvertFromText(SqlExpression expression, Type returnType) + => _typeMappingSource.FindMapping(returnType.UnwrapNullableType(), _model) switch + { + // Type mapping not found - this isn't a scalar + null => expression, + + // Arrays are dealt with as JSON arrays, not array scalars + NpgsqlArrayTypeMapping => expression, + + // Text types don't a a conversion to string + { StoreTypeNameBase: "text" or "varchar" or "char" } => expression, + + // For any other type mapping, this is a scalar; apply a conversion to the type from string. + var mapping => _sqlExpressionFactory.Convert(expression, returnType, mapping) + }; + } + /// /// 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 @@ -159,38 +179,4 @@ PgJsonTraversalExpression prevPathTraversal return null; } } - - // The PostgreSQL traversal operator always returns text, so we need to convert to int, bool, etc. - private SqlExpression ConvertFromText(SqlExpression expression, Type returnType) - { - var unwrappedReturnType = returnType.UnwrapNullableType(); - - switch (Type.GetTypeCode(unwrappedReturnType)) - { - case TypeCode.Boolean: - case TypeCode.Byte: - case TypeCode.DateTime: - case TypeCode.Decimal: - case TypeCode.Double: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.SByte: - case TypeCode.Single: - case TypeCode.UInt16: - case TypeCode.UInt32: - case TypeCode.UInt64: - return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model)); - } - - if (unwrappedReturnType == typeof(Guid) - || unwrappedReturnType == typeof(DateTimeOffset) - || unwrappedReturnType == typeof(DateOnly) - || unwrappedReturnType == typeof(TimeOnly)) - { - return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model)); - } - - return expression; - } } From 592588def5b598eabeecd2cd6025d5e801eba538 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 31 Oct 2024 12:10:37 +0100 Subject: [PATCH 078/107] Fix arrays over user-defined ranges (#3342) Fixes #3137 --- .../Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index 052d653e4..a336b1148 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -190,7 +190,7 @@ protected NpgsqlArrayTypeMapping(RelationalTypeMappingParameters parameters) // Otherwise let the ADO.NET layer infer the PostgreSQL type. We can't always let it infer, otherwise // when given a byte[] it will infer byte (but we want smallint[]) NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Array - | (ElementTypeMapping is INpgsqlTypeMapping elementNpgsqlTypeMapping + | (ElementTypeMapping is INpgsqlTypeMapping { NpgsqlDbType: not NpgsqlTypes.NpgsqlDbType.Unknown } elementNpgsqlTypeMapping ? elementNpgsqlTypeMapping.NpgsqlDbType : ElementTypeMapping.DbType.HasValue ? new NpgsqlParameter { DbType = ElementTypeMapping.DbType.Value }.NpgsqlDbType From 4078905a4ab5aa4430dcd90a0ee1d972a866c171 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 31 Oct 2024 15:58:10 +0100 Subject: [PATCH 079/107] Don't open connection if already open when reloading types (#3343) Fixes #3210 --- src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs index 0d32f58c0..8691f9322 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs @@ -80,14 +80,14 @@ public override void Migrate(string? targetMigration) if (reloadTypes && _connection.DbConnection is NpgsqlConnection npgsqlConnection) { - npgsqlConnection.Open(); + _connection.Open(); try { npgsqlConnection.ReloadTypes(); } catch { - npgsqlConnection.Close(); + _connection.Close(); } } } @@ -123,14 +123,14 @@ public override async Task MigrateAsync(string? targetMigration, CancellationTok if (reloadTypes && _connection.DbConnection is NpgsqlConnection npgsqlConnection) { - await npgsqlConnection.OpenAsync(cancellationToken).ConfigureAwait(false); + await _connection.OpenAsync(cancellationToken).ConfigureAwait(false); try { await npgsqlConnection.ReloadTypesAsync().ConfigureAwait(false); } catch { - await npgsqlConnection.CloseAsync().ConfigureAwait(false); + await _connection.CloseAsync().ConfigureAwait(false); } } } From ff8513a35d735c7536dbdccaf664919f3853e560 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 5 Nov 2024 13:38:09 +0100 Subject: [PATCH 080/107] Invoke connection interceptor before tweaking connection string in Exists() (#3349) Fixes #2880 --- .../Storage/Internal/NpgsqlDatabaseCreator.cs | 74 +++++++++---------- .../NpgsqlDatabaseCreatorTest.cs | 5 +- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs index 884cb6431..db12255d0 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs @@ -10,27 +10,13 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class NpgsqlDatabaseCreator : RelationalDatabaseCreator -{ - private readonly INpgsqlRelationalConnection _connection; - private readonly IRawSqlCommandBuilder _rawSqlCommandBuilder; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public NpgsqlDatabaseCreator( +public class NpgsqlDatabaseCreator( RelationalDatabaseCreatorDependencies dependencies, INpgsqlRelationalConnection connection, - IRawSqlCommandBuilder rawSqlCommandBuilder) - : base(dependencies) - { - _connection = connection; - _rawSqlCommandBuilder = rawSqlCommandBuilder; - } - + IRawSqlCommandBuilder rawSqlCommandBuilder, + IRelationalConnectionDiagnosticsLogger connectionLogger) + : RelationalDatabaseCreator(dependencies) +{ /// /// 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 @@ -55,7 +41,7 @@ public NpgsqlDatabaseCreator( /// public override void Create() { - using (var masterConnection = _connection.CreateAdminConnection()) + using (var masterConnection = connection.CreateAdminConnection()) { try { @@ -82,7 +68,7 @@ public override void Create() /// public override async Task CreateAsync(CancellationToken cancellationToken = default) { - var masterConnection = _connection.CreateAdminConnection(); + var masterConnection = connection.CreateAdminConnection(); await using (masterConnection.ConfigureAwait(false)) { try @@ -112,7 +98,7 @@ await Dependencies.MigrationCommandExecutor public override bool HasTables() => Dependencies.ExecutionStrategy .Execute( - _connection, + connection, connection => (bool)CreateHasTablesCommand() .ExecuteScalar( new RelationalCommandParameterObject( @@ -130,7 +116,7 @@ public override bool HasTables() /// public override Task HasTablesAsync(CancellationToken cancellationToken = default) => Dependencies.ExecutionStrategy.ExecuteAsync( - _connection, + connection, async (connection, ct) => (bool)(await CreateHasTablesCommand() .ExecuteScalarAsync( new RelationalCommandParameterObject( @@ -142,7 +128,7 @@ public override Task HasTablesAsync(CancellationToken cancellationToken = cancellationToken: ct).ConfigureAwait(false))!, cancellationToken); private IRelationalCommand CreateHasTablesCommand() - => _rawSqlCommandBuilder + => rawSqlCommandBuilder .Build( """ SELECT CASE WHEN COUNT(*) = 0 THEN FALSE ELSE TRUE END @@ -173,7 +159,7 @@ private IReadOnlyList CreateCreateOperations() [ new NpgsqlCreateDatabaseOperation { - Name = _connection.DbConnection.Database, + Name = connection.DbConnection.Database, Template = designTimeModel.GetDatabaseTemplate(), Collation = designTimeModel.GetCollation(), Tablespace = designTimeModel.GetTablespace() @@ -201,15 +187,29 @@ public override Task ExistsAsync(CancellationToken cancellationToken = def private async Task Exists(bool async, CancellationToken cancellationToken = default) { + var logger = connectionLogger; + var startTime = DateTimeOffset.UtcNow; + + var interceptionResult = async + ? await logger.ConnectionOpeningAsync(connection, startTime, cancellationToken).ConfigureAwait(false) + : logger.ConnectionOpening(connection, startTime); + + if (interceptionResult.IsSuppressed) + { + // If the connection attempt was suppressed by an interceptor, assume that the interceptor took care of all the opening + // details, and the database exists. + return true; + } + // When checking whether a database exists, pooling must be off, otherwise we may // attempt to reuse a pooled connection, which may be broken (this happened in the tests). // If Pooling is off, but Multiplexing is on - NpgsqlConnectionStringBuilder.Validate will throw, // so we turn off Multiplexing as well. - var unpooledCsb = new NpgsqlConnectionStringBuilder(_connection.ConnectionString) { Pooling = false, Multiplexing = false }; + var unpooledCsb = new NpgsqlConnectionStringBuilder(connection.ConnectionString) { Pooling = false, Multiplexing = false }; using var _ = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled); var unpooledRelationalConnection = - await _connection.CloneWith(unpooledCsb.ToString(), async, cancellationToken).ConfigureAwait(false); + await connection.CloneWith(unpooledCsb.ToString(), async, cancellationToken).ConfigureAwait(false); try { @@ -273,7 +273,7 @@ private static bool IsDoesNotExist(PostgresException exception) /// public override void Delete() { - switch (_connection.DataSource) + switch (connection.DataSource) { case NpgsqlDataSource dataSource: dataSource.Clear(); @@ -283,7 +283,7 @@ public override void Delete() break; } - using (var masterConnection = _connection.CreateAdminConnection()) + using (var masterConnection = connection.CreateAdminConnection()) { Dependencies.MigrationCommandExecutor .ExecuteNonQuery(CreateDropCommands(), masterConnection); @@ -298,7 +298,7 @@ public override void Delete() /// public override async Task DeleteAsync(CancellationToken cancellationToken = default) { - switch (_connection.DataSource) + switch (connection.DataSource) { case NpgsqlDataSource dataSource: // TODO: Do this asynchronously once https://github.com/npgsql/npgsql/issues/4499 is done @@ -309,7 +309,7 @@ public override async Task DeleteAsync(CancellationToken cancellationToken = def break; } - var masterConnection = _connection.CreateAdminConnection(); + var masterConnection = connection.CreateAdminConnection(); await using (masterConnection) { await Dependencies.MigrationCommandExecutor @@ -339,7 +339,7 @@ public override void CreateTables() try { - Dependencies.MigrationCommandExecutor.ExecuteNonQuery(commands, _connection); + Dependencies.MigrationCommandExecutor.ExecuteNonQuery(commands, connection); } catch (PostgresException e) when (e is { SqlState: "23505", ConstraintName: "pg_type_typname_nsp_index" }) { @@ -347,7 +347,7 @@ public override void CreateTables() // (happens in the tests). Simply ignore the error. } - if (reloadTypes && _connection.DbConnection is NpgsqlConnection npgsqlConnection) + if (reloadTypes && connection.DbConnection is NpgsqlConnection npgsqlConnection) { npgsqlConnection.Open(); try @@ -375,7 +375,7 @@ public override async Task CreateTablesAsync(CancellationToken cancellationToken try { - await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(commands, _connection, cancellationToken) + await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(commands, connection, cancellationToken) .ConfigureAwait(false); } catch (PostgresException e) when (e is { SqlState: "23505", ConstraintName: "pg_type_typname_nsp_index" }) @@ -389,7 +389,7 @@ await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(commands, _conn .OfType() .Any(o => o.GetPostgresExtensions().Any() || o.GetPostgresEnums().Any() || o.GetPostgresRanges().Any()); - if (reloadTypes && _connection.DbConnection is NpgsqlConnection npgsqlConnection) + if (reloadTypes && connection.DbConnection is NpgsqlConnection npgsqlConnection) { await npgsqlConnection.OpenAsync(cancellationToken).ConfigureAwait(false); try @@ -409,7 +409,7 @@ private IReadOnlyList CreateDropCommands() { // TODO Check DbConnection.Database always gives us what we want // Issue #775 - new NpgsqlDropDatabaseOperation { Name = _connection.DbConnection.Database } + new NpgsqlDropDatabaseOperation { Name = connection.DbConnection.Database } }; return Dependencies.MigrationsSqlGenerator.Generate(operations); @@ -422,5 +422,5 @@ private static void ClearAllPools() // Clear connection pool for the database connection since after the 'create database' call, a previously // invalid connection may now be valid. private void ClearPool() - => NpgsqlConnection.ClearPool((NpgsqlConnection)_connection.DbConnection); + => NpgsqlConnection.ClearPool((NpgsqlConnection)connection.DbConnection); } diff --git a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs index edf3087bd..9954a7b15 100644 --- a/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs +++ b/test/EFCore.PG.FunctionalTests/NpgsqlDatabaseCreatorTest.cs @@ -632,8 +632,9 @@ public class Blog public class TestDatabaseCreator( RelationalDatabaseCreatorDependencies dependencies, INpgsqlRelationalConnection connection, - IRawSqlCommandBuilder rawSqlCommandBuilder) - : NpgsqlDatabaseCreator(dependencies, connection, rawSqlCommandBuilder) + IRawSqlCommandBuilder rawSqlCommandBuilder, + IRelationalConnectionDiagnosticsLogger connectionLogger) + : NpgsqlDatabaseCreator(dependencies, connection, rawSqlCommandBuilder, connectionLogger) { public bool HasTablesBase() => HasTables(); From 3f584caeefc67b77b52ebc0d7fd84860e0ea5b9a Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 5 Nov 2024 14:24:32 +0100 Subject: [PATCH 081/107] Remove old obsoleted APIs (#3350) --- .../Internal/NpgsqlAnnotationCodeGenerator.cs | 6 - .../NpgsqlEntityTypeBuilderExtensions.cs | 41 --- .../NpgsqlIndexBuilderExtensions.cs | 277 ------------------ .../NpgsqlModelBuilderExtensions.cs | 88 ------ .../NpgsqlIndexExtensions.cs | 52 ---- .../NpgsqlModelExtensions.cs | 70 ----- .../NpgsqlPropertyExtensions.cs | 17 -- .../NpgsqlMigrationBuilderExtensions.cs | 26 -- .../Internal/NpgsqlAnnotationProvider.cs | 14 - .../Migrations/MigrationsNpgsqlTest.cs | 99 ------- 10 files changed, 690 deletions(-) diff --git a/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs b/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs index d11a08d6a..40d5f17df 100644 --- a/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs +++ b/src/EFCore.PG/Design/Internal/NpgsqlAnnotationCodeGenerator.cs @@ -105,10 +105,6 @@ private static readonly MethodInfo IndexHasOperatorsMethodInfo = typeof(NpgsqlIndexBuilderExtensions).GetRequiredRuntimeMethod( nameof(NpgsqlIndexBuilderExtensions.HasOperators), typeof(IndexBuilder), typeof(string[])); - private static readonly MethodInfo IndexHasSortOrderMethodInfo - = typeof(NpgsqlIndexBuilderExtensions).GetRequiredRuntimeMethod( - nameof(NpgsqlIndexBuilderExtensions.HasSortOrder), typeof(IndexBuilder), typeof(SortOrder[])); - private static readonly MethodInfo IndexHasNullSortOrderMethodInfo = typeof(NpgsqlIndexBuilderExtensions).GetRequiredRuntimeMethod( nameof(NpgsqlIndexBuilderExtensions.HasNullSortOrder), typeof(IndexBuilder), typeof(NullSortOrder[])); @@ -423,8 +419,6 @@ public override IReadOnlyList GenerateFluentApiCalls( => new MethodCallCodeFragment(IndexHasMethodMethodInfo, annotation.Value), NpgsqlAnnotationNames.IndexOperators => new MethodCallCodeFragment(IndexHasOperatorsMethodInfo, annotation.Value), - NpgsqlAnnotationNames.IndexSortOrder - => new MethodCallCodeFragment(IndexHasSortOrderMethodInfo, annotation.Value), NpgsqlAnnotationNames.IndexNullSortOrder => new MethodCallCodeFragment(IndexHasNullSortOrderMethodInfo, annotation.Value), NpgsqlAnnotationNames.IndexInclude diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlEntityTypeBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlEntityTypeBuilderExtensions.cs index 0d78f939f..52d134bf4 100644 --- a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlEntityTypeBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlEntityTypeBuilderExtensions.cs @@ -294,45 +294,4 @@ public static EntityTypeBuilder UseCockroachDbInterleaveInParent - /// Configures using the auto-updating system column xmin as the optimistic concurrency token. - /// - /// The builder for the entity type being configured. - /// The same builder instance so that multiple calls can be chained. - /// - /// See Concurrency tokens - /// for more information on using optimistic concurrency in PostgreSQL. - /// - [Obsolete("Use EF Core's standard IsRowVersion() or [Timestamp], see https://learn.microsoft.com/ef/core/saving/concurrency")] - public static EntityTypeBuilder UseXminAsConcurrencyToken( - this EntityTypeBuilder entityTypeBuilder) - { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - - entityTypeBuilder.Property("xmin") - .HasColumnType("xid") - .ValueGeneratedOnAddOrUpdate() - .IsConcurrencyToken(); - - return entityTypeBuilder; - } - - /// - /// Configures using the auto-updating system column xmin as the optimistic concurrency token. - /// - /// - /// See http://www.npgsql.org/efcore/miscellaneous.html#optimistic-concurrency-and-concurrency-tokens - /// - /// The builder for the entity type being configured. - /// The same builder instance so that multiple calls can be chained. - [Obsolete("Use EF Core's standard IsRowVersion() or [Timestamp], see https://learn.microsoft.com/ef/core/saving/concurrency")] - public static EntityTypeBuilder UseXminAsConcurrencyToken( - this EntityTypeBuilder entityTypeBuilder) - where TEntity : class - => (EntityTypeBuilder)UseXminAsConcurrencyToken((EntityTypeBuilder)entityTypeBuilder); - - #endregion Obsolete } diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlIndexBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlIndexBuilderExtensions.cs index cbe0ea1fa..741bff72a 100644 --- a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlIndexBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlIndexBuilderExtensions.cs @@ -847,281 +847,4 @@ public static bool CanSetStorageParameter( } #endregion Storage parameters - - #region Sort order (legacy) - - /// - /// The PostgreSQL index sort ordering to be used. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - /// The builder for the index being configured. - /// The sort order to use for each column. - /// A builder to further configure the index. - [Obsolete("Use IsDescending instead")] - public static IndexBuilder HasSortOrder( - this IndexBuilder indexBuilder, - params SortOrder[]? values) - { - Check.NotNull(indexBuilder, nameof(indexBuilder)); - Check.NullButNotEmpty(values, nameof(values)); - - var isDescending = new bool[indexBuilder.Metadata.Properties.Count]; - - for (var i = 0; i < isDescending.Length; i++) - { - isDescending[i] = values?.Length > i && values[i] == SortOrder.Descending; - } - - indexBuilder.IsDescending(isDescending); - - return indexBuilder; - } - - /// - /// The PostgreSQL index sort ordering to be used. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - /// The builder for the index being configured. - /// The sort order to use for each column. - /// A builder to further configure the index. - [Obsolete("Use IsDescending instead")] - public static IndexBuilder HasSortOrder( - this IndexBuilder indexBuilder, - params SortOrder[]? values) - => (IndexBuilder)HasSortOrder((IndexBuilder)indexBuilder, values); - - /// - /// The PostgreSQL index sort ordering to be used. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - /// The builder for the index being configured. - /// Indicates whether the configuration was specified using a data annotation. - /// The sort order to use for each column. - /// A builder to further configure the index. - [Obsolete("Use IsDescending instead")] - public static IConventionIndexBuilder? HasSortOrder( - this IConventionIndexBuilder indexBuilder, - IReadOnlyList? values, - bool fromDataAnnotation) - { - if (indexBuilder.CanSetSortOrder(values, fromDataAnnotation)) - { - Check.NotNull(indexBuilder, nameof(indexBuilder)); - Check.NullButNotEmpty(values, nameof(values)); - - var isDescending = new bool[indexBuilder.Metadata.Properties.Count]; - - for (var i = 0; i < isDescending.Length; i++) - { - isDescending[i] = values?.Count > i && values[i] == SortOrder.Descending; - } - - indexBuilder.IsDescending(isDescending); - - return indexBuilder; - } - - return null; - } - - /// - /// Returns a value indicating whether the PostgreSQL index sort ordering can be set. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - /// The builder for the index being configured. - /// The sort order to use for each column. - /// Indicates whether the configuration was specified using a data annotation. - /// A builder to further configure the index. - [Obsolete("Use IsDescending instead")] - public static bool CanSetSortOrder( - this IConventionIndexBuilder indexBuilder, - IReadOnlyList? values, - bool fromDataAnnotation) - { - Check.NotNull(indexBuilder, nameof(indexBuilder)); - - return indexBuilder.CanSetAnnotation(NpgsqlAnnotationNames.IndexSortOrder, values, fromDataAnnotation); - } - - #endregion Sort order (obsolete) - - #region Obsolete - - /// - /// The PostgreSQL index collation to be used. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-collations.html - /// - /// The builder for the index being configured. - /// The sort options to use for each column. - /// A builder to further configure the index. - [Obsolete("Use UseCollation")] - public static IndexBuilder HasCollation( - this IndexBuilder indexBuilder, - params string[]? values) - => UseCollation(indexBuilder, values); - - /// - /// The PostgreSQL index collation to be used. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-collations.html - /// - /// The builder for the index being configured. - /// The sort options to use for each column. - /// A builder to further configure the index. - [Obsolete("Use UseCollation")] - public static IndexBuilder HasCollation( - this IndexBuilder indexBuilder, - params string[]? values) - => UseCollation(indexBuilder, values); - - /// - /// The PostgreSQL index collation to be used. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-collations.html - /// - /// The builder for the index being configured. - /// The sort options to use for each column. - /// Indicates whether the configuration was specified using a data annotation. - /// A builder to further configure the index. - [Obsolete("Use UseCollation")] - public static IConventionIndexBuilder? HasCollation( - this IConventionIndexBuilder indexBuilder, - IReadOnlyList? values, - bool fromDataAnnotation) - => UseCollation(indexBuilder, values, fromDataAnnotation); - - /// - /// Returns a value indicating whether the PostgreSQL index collation can be set. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-collations.html - /// - /// The builder for the index being configured. - /// The sort options to use for each column. - /// Indicates whether the configuration was specified using a data annotation. - /// A builder to further configure the index. - [Obsolete("Use CanSetHasCollation")] - public static bool CanSetHasCollation( - this IConventionIndexBuilder indexBuilder, - IReadOnlyList? values, - bool fromDataAnnotation) - => CanSetCollation(indexBuilder, values, fromDataAnnotation); - - /// - /// The PostgreSQL index method to be used. Null selects the default (currently btree). - /// - /// - /// http://www.postgresql.org/docs/current/static/sql-createindex.html - /// - /// The builder for the index being configured. - /// The name of the index. - /// Indicates whether the configuration was specified using a data annotation. - /// true if the index can be configured with the method - [Obsolete("Use CanSetMethod")] - public static bool CanSetHasMethod( - this IConventionIndexBuilder indexBuilder, - string? method, - bool fromDataAnnotation = false) - => CanSetMethod(indexBuilder, method, fromDataAnnotation); - - /// - /// Returns a value indicating whether the PostgreSQL index operators can be set. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-opclass.html - /// - /// The builder for the index being configured. - /// The operators to use for each column. - /// Indicates whether the configuration was specified using a data annotation. - /// true if the index can be configured with the method. - [Obsolete("Use CanSetOperators")] - public static bool CanSetHasOperators( - this IConventionIndexBuilder indexBuilder, - IReadOnlyList? operators, - bool fromDataAnnotation) - => CanSetOperators(indexBuilder, operators, fromDataAnnotation); - - /// - /// Returns a value indicating whether the index can be configured as a full-text tsvector expression index. - /// - /// The builder for the index being configured. - /// - /// - /// The text search configuration for this generated tsvector property, or null if this is not a - /// generated tsvector property. - /// - /// - /// See https://www.postgresql.org/docs/current/textsearch-controls.html for more information. - /// - /// - /// Indicates whether the configuration was specified using a data annotation. - /// true if the index can be configured as a full-text tsvector expression index. - [Obsolete("Use CanSetIsTsVectorExpressionIndex")] - public static bool CanSetToTsVector( - this IConventionIndexBuilder indexBuilder, - string? config, - bool fromDataAnnotation = false) - => CanSetIsTsVectorExpressionIndex(indexBuilder, config, fromDataAnnotation); - - /// - /// Returns a value indicating whether the PostgreSQL index sort ordering can be set. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - /// The builder for the index being configured. - /// The sort order to use for each column. - /// Indicates whether the configuration was specified using a data annotation. - /// A builder to further configure the index. - [Obsolete("Use CanSetSortOrder")] - public static bool CanSetHasSortOrder( - this IConventionIndexBuilder indexBuilder, - IReadOnlyList? values, - bool fromDataAnnotation) - => CanSetSortOrder(indexBuilder, values, fromDataAnnotation); - - /// - /// Returns a value indicating whether the PostgreSQL index null sort ordering can be set. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - /// The builder for the index being configured. - /// The sort order to use for each column. - /// Indicates whether the configuration was specified using a data annotation. - /// A builder to further configure the index. - [Obsolete("Use CanSetNullSortOrder")] - public static bool CanSetHasNullSortOrder( - this IConventionIndexBuilder indexBuilder, - IReadOnlyList? values, - bool fromDataAnnotation) - => CanSetNullSortOrder(indexBuilder, values, fromDataAnnotation); - - /// - /// Returns a value indicating whether the given include properties can be set. - /// - /// The builder for the index being configured. - /// An array of property names to be used in 'include' clause. - /// Indicates whether the configuration was specified using a data annotation. - /// true if the given include properties can be set. - [Obsolete("Use CanSetIncludeProperties")] - public static bool CanSetInclude( - this IConventionIndexBuilder indexBuilder, - IReadOnlyList? propertyNames, - bool fromDataAnnotation = false) - => CanSetIncludeProperties(indexBuilder, propertyNames, fromDataAnnotation); - - #endregion Obsolete } diff --git a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs index f38fda9af..6650eb824 100644 --- a/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/BuilderExtensions/NpgsqlModelBuilderExtensions.cs @@ -768,94 +768,6 @@ public static ModelBuilder HasCollation( #endregion Collation management - #region Default column collation - - /// - /// Configures the default collation for all columns in the database. This causes EF Core to specify an explicit - /// collation when creating each column (unless overridden). - /// - /// - ///

- /// An alternative is to specify a database collation via - /// , - /// which will specify the query on CREATE DATABASE instead of for each and every column. However, - /// PostgreSQL support is limited for the collations that can be specific via this mechanism; ICU collations - - /// which include all case-insensitive collations - are currently unsupported. - ///

- ///

- /// For more information, see https://www.postgresql.org/docs/current/collation.html. - ///

- ///
- /// The model builder. - /// The collation. - /// A builder to further configure the property. - [Obsolete("Use EF Core's standard model bulk configuration API")] - public static ModelBuilder UseDefaultColumnCollation(this ModelBuilder modelBuilder, string? collation) - { - Check.NotNull(modelBuilder, nameof(modelBuilder)); - Check.NullButNotEmpty(collation, nameof(collation)); - - modelBuilder.Model.SetDefaultColumnCollation(collation); - - return modelBuilder; - } - - /// - /// Configures the default collation for all columns in the database. This causes EF Core to specify an explicit - /// collation when creating each column (unless overridden). - /// - /// - ///

- /// An alternative is to specify a database collation via - /// , - /// which will specify the query on CREATE DATABASE instead of for each and every column. However, - /// PostgreSQL support is limited for the collations that can be specific via this mechanism; ICU collations - - /// which include all case-insensitive collations - are currently unsupported. - ///

- ///

- /// For more information, see https://www.postgresql.org/docs/current/collation.html. - ///

- ///
- /// The model builder. - /// The collation. - /// Indicates whether the configuration was specified using a data annotation. - /// A builder to further configure the property. - [Obsolete("Use EF Core's standard model bulk configuration API")] - public static IConventionModelBuilder? UseDefaultColumnCollation( - this IConventionModelBuilder modelBuilder, - string? collation, - bool fromDataAnnotation = false) - { - if (modelBuilder.CanSetDefaultColumnCollation(collation, fromDataAnnotation)) - { - modelBuilder.Metadata.SetDefaultColumnCollation(collation); - return modelBuilder; - } - - return null; - } - - /// - /// Returns a value indicating whether the given value can be set as the default column collation. - /// - /// The model builder. - /// The collation. - /// Indicates whether the configuration was specified using a data annotation. - /// true if the given value can be set as the collation. - [Obsolete("Use EF Core's standard model bulk configuration API")] - public static bool CanSetDefaultColumnCollation( - this IConventionModelBuilder modelBuilder, - string? collation, - bool fromDataAnnotation = false) - { - Check.NotNull(modelBuilder, nameof(modelBuilder)); - - return modelBuilder.CanSetAnnotation( - NpgsqlAnnotationNames.DefaultColumnCollation, collation, fromDataAnnotation); - } - - #endregion Default column collation - #region Helpers // See: https://github.com/npgsql/npgsql/blob/dev/src/Npgsql/TypeMapping/TypeMapperBase.cs#L132-L138 diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlIndexExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlIndexExtensions.cs index fe7154f9b..e3e86d6bb 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlIndexExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlIndexExtensions.cs @@ -479,56 +479,4 @@ public static object SetStorageParameter( } #endregion Storage parameters - - #region Sort order (legacy) - - /// - /// Returns the column sort orders to be used, or null if they have not been specified. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - [Obsolete("Use IsDescending instead")] - public static IReadOnlyList? GetSortOrder(this IReadOnlyIndex index) - => (IReadOnlyList?)index[NpgsqlAnnotationNames.IndexSortOrder]; - - /// - /// Sets the column sort orders to be used, or null if they have not been specified. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - [Obsolete("Use IsDescending instead")] - public static void SetSortOrder(this IMutableIndex index, IReadOnlyList? sortOrder) - => index.SetOrRemoveAnnotation(NpgsqlAnnotationNames.IndexSortOrder, sortOrder); - - /// - /// Sets the column sort orders to be used, or null if they have not been specified. - /// - /// - /// https://www.postgresql.org/docs/current/static/indexes-ordering.html - /// - [Obsolete("Use IsDescending instead")] - public static IReadOnlyList? SetSortOrder( - this IConventionIndex index, - IReadOnlyList? sortOrder, - bool fromDataAnnotation = false) - { - Check.NullButNotEmpty(sortOrder, nameof(sortOrder)); - - index.SetOrRemoveAnnotation(NpgsqlAnnotationNames.IndexSortOrder, sortOrder, fromDataAnnotation); - - return sortOrder; - } - - /// - /// Returns the for the index sort orders. - /// - /// The index. - /// The for the index sort orders. - [Obsolete("Use IsDescending instead")] - public static ConfigurationSource? GetSortOrderConfigurationSource(this IConventionIndex index) - => index.FindAnnotation(NpgsqlAnnotationNames.IndexSortOrder)?.GetConfigurationSource(); - - #endregion Sort order (legacy) } diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs index 35228a506..a69ccf8ac 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlModelExtensions.cs @@ -516,74 +516,4 @@ public static IReadOnlyList GetCollations(this IReadOnlyModel => PostgresCollation.GetCollations(model).ToArray(); #endregion Collation management - - #region Default column collation - - /// - /// Gets the default collation for all columns in the database, or if none is defined. - /// This causes EF Core to specify an explicit collation when creating all column, unless one is overridden - /// on a column. - /// - /// - ///

- /// See for another approach to defining a - /// database-wide collation. - ///

- ///

- /// For more information, see https://www.postgresql.org/docs/current/collation.html. - ///

- ///
- [Obsolete("Use EF Core's standard model bulk configuration API")] - public static string? GetDefaultColumnCollation(this IReadOnlyModel model) - => (string?)model[NpgsqlAnnotationNames.DefaultColumnCollation]; - - /// - /// Sets the default collation for all columns in the database, or null if none is defined. - /// This causes EF Core to specify an explicit collation when creating all column, unless one is overridden - /// on a column. - /// - /// - ///

- /// See for another approach to defining a - /// database-wide collation. - ///

- ///

- /// For more information, see https://www.postgresql.org/docs/current/collation.html. - ///

- ///
- [Obsolete("Use EF Core's standard model bulk configuration API")] - public static void SetDefaultColumnCollation(this IMutableModel model, string? collation) - => model.SetOrRemoveAnnotation(NpgsqlAnnotationNames.DefaultColumnCollation, collation); - - /// - /// Sets the default collation for all columns in the database, or null if none is defined. - /// This causes EF Core to specify an explicit collation when creating all column, unless one is overridden - /// on a column. - /// - /// - ///

- /// See - /// for another approach to defining a database-wide collation. - ///

- ///

- /// For more information, see https://www.postgresql.org/docs/current/collation.html. - ///

- ///
- [Obsolete("Use EF Core's standard model bulk configuration API")] - public static string? SetDefaultColumnCollation(this IConventionModel model, string? collation, bool fromDataAnnotation = false) - { - model.SetOrRemoveAnnotation(NpgsqlAnnotationNames.DefaultColumnCollation, collation, fromDataAnnotation); - return collation; - } - - /// - /// Returns the for the default column collation. - /// - /// The model. - /// The for the default column collation. - [Obsolete("Use EF Core's standard model bulk configuration API")] - public static ConfigurationSource? GetDefaultColumnCollationConfigurationSource(this IConventionModel model) - => model.FindAnnotation(NpgsqlAnnotationNames.DefaultColumnCollation)?.GetConfigurationSource(); - - #endregion Default column collation } diff --git a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs index ce01fb60e..8e4869b7b 100644 --- a/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs +++ b/src/EFCore.PG/Extensions/MetadataExtensions/NpgsqlPropertyExtensions.cs @@ -1142,23 +1142,6 @@ public static void SetTsVectorProperties( #endregion Generated tsvector column - #region Collation - - /// - /// Returns the collation to be used for the column - including the PostgreSQL-specific default column - /// collation defined at the model level (see - /// ). - /// - /// The property. - /// The collation for the column this property is mapped to. - [Obsolete("Use EF Core's standard model bulk configuration API")] - public static string? GetDefaultCollation(this IReadOnlyProperty property) - => property.FindTypeMapping() is StringTypeMapping - ? property.DeclaringType.Model.GetDefaultColumnCollation() - : null; - - #endregion Collation - #region Compression method /// diff --git a/src/EFCore.PG/Extensions/NpgsqlMigrationBuilderExtensions.cs b/src/EFCore.PG/Extensions/NpgsqlMigrationBuilderExtensions.cs index 20848999b..35ac4667b 100644 --- a/src/EFCore.PG/Extensions/NpgsqlMigrationBuilderExtensions.cs +++ b/src/EFCore.PG/Extensions/NpgsqlMigrationBuilderExtensions.cs @@ -44,30 +44,4 @@ public static MigrationBuilder EnsurePostgresExtension( return builder; } - - /// - /// 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. - /// - [Obsolete("Use EnsurePostgresExtension instead")] - public static MigrationBuilder CreatePostgresExtension( - this MigrationBuilder builder, - string name, - string? schema = null, - string? version = null) - => EnsurePostgresExtension(builder, name, schema, version); - - /// - /// 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. - /// - [Obsolete("This no longer does anything and should be removed.")] - public static MigrationBuilder DropPostgresExtension( - this MigrationBuilder builder, - string name) - => builder; } diff --git a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs index 0d9591058..a9493d6ae 100644 --- a/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs +++ b/src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs @@ -97,20 +97,6 @@ public override IEnumerable For(IColumn column, bool designTime) } } - // If the property has a collation explicitly defined on it via the standard EF mechanism, it will get - // passed on the Collation property (we don't need to do anything). - // Otherwise, if a model-wide default column collation exists, pass that through our custom annotation. - // Note that this mechanism is obsolete, and EF Core's bulk model configuration can be used instead; but we continue to support - // it for backwards compat. -#pragma warning disable CS0618 - if (column.PropertyMappings.All(m => m.Property.GetCollation() is null) - && column.PropertyMappings.Select(m => m.Property.GetDefaultCollation()) - .FirstOrDefault(c => c is not null) is { } defaultColumnCollation) - { - yield return new Annotation(NpgsqlAnnotationNames.DefaultColumnCollation, defaultColumnCollation); - } -#pragma warning restore CS0618 - if (column.PropertyMappings.Select(m => m.Property.GetTsVectorConfig()) .FirstOrDefault(c => c is not null) is { } tsVectorConfig) { diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index 38743f56e..db6cfe3fd 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -717,53 +717,6 @@ public override async Task Add_column_computed_with_collation(bool stored) AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "POSIX" GENERATED ALWAYS AS ('hello') STORED;"""); } -#pragma warning disable CS0618 - [ConditionalFact] - public async Task Add_column_with_default_column_collation() - { - await Test( - builder => - { - builder.UseDefaultColumnCollation("POSIX"); - builder.Entity("People").Property("Id"); - }, - _ => { }, - builder => builder.Entity("People").Property("Name"), - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal(2, table.Columns.Count); - var nameColumn = Assert.Single(table.Columns, c => c.Name == "Name"); - Assert.Equal("POSIX", nameColumn.Collation); - }); - - AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "POSIX";"""); - } - - [ConditionalFact] - public async Task Add_column_with_collation_overriding_default() - { - await Test( - builder => - { - builder.UseDefaultColumnCollation("POSIX"); - builder.Entity("People").Property("Id"); - }, - _ => { }, - builder => builder.Entity("People").Property("Name") - .UseCollation("C"), - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal(2, table.Columns.Count); - var nameColumn = Assert.Single(table.Columns, c => c.Name == "Name"); - Assert.Equal("C", nameColumn.Collation); - }); - - AssertSql("""ALTER TABLE "People" ADD "Name" text COLLATE "C";"""); - } -#pragma warning restore CS0618 - public override async Task Add_column_shared() { await base.Add_column_shared(); @@ -1705,30 +1658,6 @@ public override async Task Convert_string_column_to_a_json_column_containing_col Assert.Equal("42804", exception.SqlState); // column "Name" cannot be cast automatically to type jsonb } -#pragma warning disable CS0618 - public async Task Alter_column_change_default_column_collation() - { - await Test( - builder => builder.Entity( - "People", b => - { - b.Property("Id"); - b.Property("Name"); - }), - builder => builder.UseDefaultColumnCollation("POSIX"), - builder => builder.UseDefaultColumnCollation("C"), - _ => - { - // var table = Assert.Single(model.Tables); - // Assert.Equal(2, table.Columns.Count); - // var nameColumn = Assert.Single(table.Columns, c => c.Name == "Name"); - // Assert.Equal("C", nameColumn.Collation); - }); - - AssertSql("""ALTER TABLE "People" ALTER COLUMN "Name" TYPE text COLLATE "C";"""); - } -#pragma warning restore CS0618 - [Fact] public virtual async Task Alter_column_computed_set_collation() { @@ -1911,34 +1840,6 @@ public override async Task Create_index_descending_mixed() AssertSql("""CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");"""); } -#pragma warning disable CS0618 // HasSortOrder is obsolete - [Fact] - public virtual async Task Create_index_descending_mixed_legacy_api() - { - await Test( - builder => builder.Entity( - "People", e => - { - e.Property("Id"); - e.Property("X"); - e.Property("Y"); - e.Property("Z"); - }), - builder => { }, - builder => builder.Entity("People") - .HasIndex("X", "Y", "Z") - .HasSortOrder(SortOrder.Ascending, SortOrder.Descending), - model => - { - var table = Assert.Single(model.Tables); - var index = Assert.Single(table.Indexes); - Assert.Collection(index.IsDescending, Assert.False, Assert.True, Assert.False); - }); - - AssertSql("""CREATE INDEX "IX_People_X_Y_Z" ON "People" ("X", "Y" DESC, "Z");"""); - } -#pragma warning restore CS0618 - [Fact] public virtual async Task Create_index_descending_mixed_legacy_annotation() { From eca01842f77ae8e4377f95c75b72f4b3eeb54086 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 5 Nov 2024 16:45:02 +0100 Subject: [PATCH 082/107] Add tstzrange to NpgsqlRange (#3351) Fixes #3182 --- src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 9d17aae52..6dbbb7e40 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -149,7 +149,7 @@ static NpgsqlTypeMappingSource() private readonly NpgsqlRangeTypeMapping _int8range; private readonly NpgsqlRangeTypeMapping _numrange; private readonly NpgsqlRangeTypeMapping _tsrange; - private readonly NpgsqlRangeTypeMapping _tstzrange; + private readonly NpgsqlRangeTypeMapping _tstzrange, _tstzrangeDto; private readonly NpgsqlRangeTypeMapping _dateOnlyDaterange; private readonly NpgsqlRangeTypeMapping _dateTimeDaterange; @@ -207,6 +207,8 @@ public NpgsqlTypeMappingSource( "tsrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampRange, _timestamp); _tstzrange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( "tstzrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampTzRange, _timestamptz); + _tstzrangeDto = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "tstzrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampTzRange, _timestamptzDto); _dateOnlyDaterange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( "daterange", typeof(NpgsqlRange), NpgsqlDbType.DateRange, _dateDateOnly); _dateTimeDaterange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( @@ -279,7 +281,7 @@ public NpgsqlTypeMappingSource( { "int8range", [_int8range] }, { "numrange", [_numrange] }, { "tsrange", [_tsrange] }, - { "tstzrange", [_tstzrange] }, + { "tstzrange", [_tstzrange, _tstzrangeDto] }, { "daterange", [_dateOnlyDaterange, _dateTimeDaterange] }, { "tsquery", [_tsquery] }, { "tsvector", [_tsvector] }, From b875f5d1c8a9344c434d9a24474407ad0d80e703 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 6 Nov 2024 16:24:51 +0100 Subject: [PATCH 083/107] Fix error around mapping Nullable (#3353) Fixes #2977 --- .../Metadata/Conventions/NpgsqlJsonElementHackConvention.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.PG/Metadata/Conventions/NpgsqlJsonElementHackConvention.cs b/src/EFCore.PG/Metadata/Conventions/NpgsqlJsonElementHackConvention.cs index df2fb162a..dad88135f 100644 --- a/src/EFCore.PG/Metadata/Conventions/NpgsqlJsonElementHackConvention.cs +++ b/src/EFCore.PG/Metadata/Conventions/NpgsqlJsonElementHackConvention.cs @@ -21,7 +21,7 @@ public void ProcessPropertyAdded(IConventionPropertyBuilder propertyBuilder, ICo { var property = propertyBuilder.Metadata; - if (property.ClrType == typeof(JsonElement) && property.GetColumnType() is null) + if (property.ClrType.UnwrapNullableType() == typeof(JsonElement) && property.GetColumnType() is null) { property.SetTypeMapping(_jsonTypeMapping ??= new NpgsqlJsonTypeMapping("jsonb", typeof(JsonElement))); } From 74d8a51eaf4b3848f5eded5ff36dd0b3d87dc218 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 14 Nov 2024 14:49:44 +0100 Subject: [PATCH 084/107] Use .NET/EF 9.0 GA (#3361) --- .github/workflows/build.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- Directory.Packages.props | 4 +-- global.json | 4 +-- .../MigrationsInfrastructureNpgsqlTest.cs | 13 +++++++++ .../AdHocAdvancedMappingsQueryNpgsqlTest.cs | 15 ++++++++++ .../Query/AdHocPrecompiledQueryNpgsqlTest.cs | 29 +++++++++++++++++++ 7 files changed, 63 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index adff570bf..fca39f9ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: pull_request: env: - dotnet_sdk_version: '9.0.100-rc.2.24474.11' + dotnet_sdk_version: '9.0.100' postgis_version: 3 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 655cd1811..c5e68a34c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ on: - cron: '30 22 * * 6' env: - dotnet_sdk_version: '9.0.100-rc.2.24474.11' + dotnet_sdk_version: '9.0.100' jobs: analyze: diff --git a/Directory.Packages.props b/Directory.Packages.props index 1bd68eb76..949ee8f56 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - [9.0.0-rc.2.24474.1] - 9.0.0-rc.2.24473.5 + [9.0.0,10.0.0) + 9.0.0 9.0.0-preview.1-ci.20241028T080028 diff --git a/global.json b/global.json index 58f954fc2..733b653c1 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "9.0.100-rc.2.24474.11", + "version": "9.0.100", "rollForward": "latestMajor", - "allowPrerelease": true + "allowPrerelease": false } } diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs index c87d9507a..a67c241fa 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs @@ -7,6 +7,16 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations public class MigrationsInfrastructureNpgsqlTest(MigrationsInfrastructureNpgsqlTest.MigrationsInfrastructureNpgsqlFixture fixture) : MigrationsInfrastructureTestBase(fixture) { + // TODO: Remove once we sync to https://github.com/dotnet/efcore/pull/35106 + public override void Can_generate_no_migration_script() + { + } + + // TODO: Remove once we sync to https://github.com/dotnet/efcore/pull/35106 + public override void Can_generate_migration_from_initial_database_to_initial() + { + } + public override void Can_get_active_provider() { base.Can_get_active_provider(); @@ -159,6 +169,9 @@ public override void Can_diff_against_2_1_ASP_NET_Identity_model() // TODO: Implement } + protected override Task ExecuteSqlAsync(string value) + => ((NpgsqlTestStore)Fixture.TestStore).ExecuteNonQueryAsync(value); + public class MigrationsInfrastructureNpgsqlFixture : MigrationsInfrastructureFixtureBase { protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs index 0a2e11cce..a3bafed45 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocAdvancedMappingsQueryNpgsqlTest.cs @@ -21,4 +21,19 @@ public override Task Query_generates_correct_datetimeoffset_parameter_definition public override Task Projecting_one_of_two_similar_complex_types_picks_the_correct_one() => Assert.ThrowsAsync( () => base.Projecting_one_of_two_similar_complex_types_picks_the_correct_one()); + + // Cannot write DateTimeOffset with Offset=10:00:00 to PostgreSQL type 'timestamp with time zone', only offset 0 (UTC) is supported. + public override Task Projecting_expression_with_converter_with_closure(bool async) + => Assert.ThrowsAsync( + () => base.Projecting_one_of_two_similar_complex_types_picks_the_correct_one()); + + // Cannot write DateTimeOffset with Offset=10:00:00 to PostgreSQL type 'timestamp with time zone', only offset 0 (UTC) is supported. + public override Task Projecting_property_with_converter_with_closure(bool async) + => Assert.ThrowsAsync( + () => base.Projecting_one_of_two_similar_complex_types_picks_the_correct_one()); + + // Cannot write DateTimeOffset with Offset=10:00:00 to PostgreSQL type 'timestamp with time zone', only offset 0 (UTC) is supported. + public override Task Projecting_property_with_converter_without_closure(bool async) + => Assert.ThrowsAsync( + () => base.Projecting_one_of_two_similar_complex_types_picks_the_correct_one()); } diff --git a/test/EFCore.PG.FunctionalTests/Query/AdHocPrecompiledQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocPrecompiledQueryNpgsqlTest.cs index f97055924..8839ca80e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocPrecompiledQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocPrecompiledQueryNpgsqlTest.cs @@ -69,6 +69,35 @@ LIMIT 2 """); } + public override async Task Projecting_property_requiring_converter_with_closure_is_not_supported() + { + await base.Projecting_property_requiring_converter_with_closure_is_not_supported(); + + AssertSql(); + } + + public override async Task Projecting_expression_requiring_converter_without_closure_works() + { + await base.Projecting_expression_requiring_converter_without_closure_works(); + + AssertSql( + """ +SELECT b."AudiobookDate" +FROM "Books" AS b +"""); + } + + public override async Task Projecting_entity_with_property_requiring_converter_with_closure_works() + { + await base.Projecting_entity_with_property_requiring_converter_with_closure_works(); + + AssertSql( + """ +SELECT b."Id", b."AudiobookDate", b."Name", b."PublishDate" +FROM "Books" AS b +"""); + } + [ConditionalFact] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); From a4d67a11c2803d971ce8f3b7765d9f6009367494 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 14 Nov 2024 17:25:35 +0100 Subject: [PATCH 085/107] Detect and throw for NpgsqlTypeMappings as element type mappings (#3367) Closes #3362 --- ...sqlCSharpRuntimeAnnotationCodeGenerator.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs index 2cbdaa588..e484cb56e 100644 --- a/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs @@ -14,8 +14,11 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Design.Internal; /// doing so can result in application failures when updating to a new Entity Framework Core release. /// #pragma warning disable EF1001 // Internal EF Core API usage. -public class NpgsqlCSharpRuntimeAnnotationCodeGenerator : RelationalCSharpRuntimeAnnotationCodeGenerator +public class NpgsqlCSharpRuntimeAnnotationCodeGenerator + : RelationalCSharpRuntimeAnnotationCodeGenerator, ICSharpRuntimeAnnotationCodeGenerator { + private int _typeMappingNestingCount; + /// /// 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 @@ -42,8 +45,31 @@ public override bool Create( ValueComparer? keyValueComparer = null, ValueComparer? providerValueComparer = null) { - var result = base.Create(typeMapping, parameters, valueComparer, keyValueComparer, providerValueComparer); + _typeMappingNestingCount++; + try + { + var result = base.Create(typeMapping, parameters, valueComparer, keyValueComparer, providerValueComparer); + AddNpgsqlTypeMappingTweaks(typeMapping, parameters); + return result; + } + finally + { + _typeMappingNestingCount--; + } + } + + /// + /// 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. + /// + [EntityFrameworkInternal] + protected virtual void AddNpgsqlTypeMappingTweaks( + CoreTypeMapping typeMapping, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { var mainBuilder = parameters.MainBuilder; var npgsqlDbTypeBasedDefaultInstance = typeMapping switch @@ -57,6 +83,8 @@ public override bool Create( if (npgsqlDbTypeBasedDefaultInstance is not null) { + CheckElementTypeMapping(); + var npgsqlDbType = ((INpgsqlTypeMapping)typeMapping).NpgsqlDbType; if (npgsqlDbType != npgsqlDbTypeBasedDefaultInstance.NpgsqlDbType) @@ -83,6 +111,8 @@ public override bool Create( switch (typeMapping) { case NpgsqlEnumTypeMapping enumTypeMapping: + CheckElementTypeMapping(); + var code = Dependencies.CSharpHelper; mainBuilder.AppendLine(";"); @@ -119,6 +149,8 @@ public override bool Create( case NpgsqlRangeTypeMapping rangeTypeMapping: { + CheckElementTypeMapping(); + var defaultInstance = NpgsqlRangeTypeMapping.Default; var npgsqlDbTypeDifferent = rangeTypeMapping.NpgsqlDbType != defaultInstance.NpgsqlDbType; @@ -154,7 +186,14 @@ public override bool Create( } } - return result; + void CheckElementTypeMapping() + { + if (_typeMappingNestingCount > 1) + { + throw new NotSupportedException( + $"Non-default Npgsql type mappings ('{typeMapping.GetType().Name}' with store type '{(typeMapping as RelationalTypeMapping)?.StoreType}') aren't currently supported as element type mappings, see https://github.com/npgsql/efcore.pg/issues/3366."); + } + } } /// From dd8e6302f8d9d5fa2515445841facee2de2fd955 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 14 Nov 2024 19:52:30 +0100 Subject: [PATCH 086/107] Allow MapEnum() without an enum name (#3368) Closes #3178 --- src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs | 4 ++-- .../Infrastructure/Internal/NpgsqlOptionsExtension.cs | 2 +- src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs b/src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs index b34be200e..b02ad9b78 100644 --- a/src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs +++ b/src/EFCore.PG/Infrastructure/Internal/EnumDefinition.cs @@ -71,7 +71,7 @@ public sealed class EnumDefinition : IEquatable /// public EnumDefinition( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type clrType, - string name, + string? name, string? schema, INpgsqlNameTranslator nameTranslator) { @@ -80,7 +80,7 @@ public EnumDefinition( throw new ArgumentException($"Enum type mappings require a CLR enum. {clrType.FullName} is not an enum."); } - StoreTypeName = name; + StoreTypeName = name ?? nameTranslator.TranslateTypeName(clrType.Name); StoreTypeSchema = schema; ClrType = clrType; diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index e4abca786..7dba76190 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -214,7 +214,7 @@ public virtual NpgsqlOptionsExtension WithUserRangeDefinition( /// public virtual NpgsqlOptionsExtension WithEnumMapping( Type clrType, - string enumName, + string? enumName, string? schemaName, INpgsqlNameTranslator? nameTranslator) { diff --git a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs index 12b3fc599..ad07f951d 100644 --- a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs +++ b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs @@ -121,7 +121,7 @@ public virtual NpgsqlDbContextOptionsBuilder MapRange( /// The name of the PostgreSQL schema in which the range is defined. /// The name translator used to map enum value names to PostgreSQL enum values. public virtual NpgsqlDbContextOptionsBuilder MapEnum( - string enumName, + string? enumName = null, string? schemaName = null, INpgsqlNameTranslator? nameTranslator = null) where T : struct, Enum @@ -136,7 +136,7 @@ public virtual NpgsqlDbContextOptionsBuilder MapEnum( /// The name translator used to map enum value names to PostgreSQL enum values. public virtual NpgsqlDbContextOptionsBuilder MapEnum( Type clrType, - string enumName, + string? enumName = null, string? schemaName = null, INpgsqlNameTranslator? nameTranslator = null) => WithOption(e => e.WithEnumMapping(clrType, enumName, schemaName, nameTranslator)); From b9363f17a20372ba5af1421288271ab9a4fd4248 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 18 Nov 2024 16:05:59 +0100 Subject: [PATCH 087/107] Depend on Npgsql 9.0.0 GA --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 949ee8f56..8ebfed249 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ [9.0.0,10.0.0) 9.0.0 - 9.0.0-preview.1-ci.20241028T080028 + 9.0.0 From cbfc4d6e14a558ff05d049345fbcc6995be5c213 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 18 Nov 2024 16:07:37 +0100 Subject: [PATCH 088/107] Pass cancellation tokens to ReloadTypesAsync() --- src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs | 2 +- src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs index 8691f9322..e8a0bea28 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs @@ -126,7 +126,7 @@ public override async Task MigrateAsync(string? targetMigration, CancellationTok await _connection.OpenAsync(cancellationToken).ConfigureAwait(false); try { - await npgsqlConnection.ReloadTypesAsync().ConfigureAwait(false); + await npgsqlConnection.ReloadTypesAsync(cancellationToken).ConfigureAwait(false); } catch { diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs index db12255d0..4034f5ef2 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlDatabaseCreator.cs @@ -394,7 +394,7 @@ await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(commands, conne await npgsqlConnection.OpenAsync(cancellationToken).ConfigureAwait(false); try { - await npgsqlConnection.ReloadTypesAsync().ConfigureAwait(false); + await npgsqlConnection.ReloadTypesAsync(cancellationToken).ConfigureAwait(false); } catch { From f07040d3e35e2a228486f3ee9633784aa196a2c3 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 18 Nov 2024 16:09:58 +0100 Subject: [PATCH 089/107] Bump version to 9.0.0 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index cd7fb9c90..809a7eca0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0-rc.2 + 9.0.0 latest true latest From 7aa8be01820f9a5cfb918ce19bbdded8babb4e76 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 18 Nov 2024 16:30:17 +0100 Subject: [PATCH 090/107] Bump version to 9.0.1 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 809a7eca0..ffeac1f3b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.0 + 9.0.1 latest true latest From 2e9a27fa9e7c2988a39d98c9b89cafdcf5ccf42b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 19 Nov 2024 13:28:14 +0200 Subject: [PATCH 091/107] Depend on Npgsql 9.0.1 (#3376) (cherry picked from commit 773a330bf9ea59207b7a45b1496f77734847e859) --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8ebfed249..59515d0f6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ [9.0.0,10.0.0) 9.0.0 - 9.0.0 + 9.0.1 From 051d00cbbc51384c8c9a359881e569a1153356fa Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 19 Nov 2024 13:05:56 +0100 Subject: [PATCH 092/107] Bump version to 9.0.2 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index ffeac1f3b..712d9ae07 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.1 + 9.0.2 latest true latest From 17e2fc83c823823260e1e92a27ca53f5227ad02f Mon Sep 17 00:00:00 2001 From: Victor Irzak <6209775+virzak@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:15:59 -0500 Subject: [PATCH 093/107] Restore virtual modifier (#3382) (cherry picked from commit c6fb5e21e11466c7a0bdbbecdf09693966701fbd) --- .../Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs b/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs index fa467d127..f4cf99980 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlParameterBasedSqlProcessorFactory.cs @@ -27,6 +27,6 @@ public NpgsqlParameterBasedSqlProcessorFactory( /// /// Parameters for . /// A relational parameter based sql processor. - public RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters) + public virtual RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters) => new NpgsqlParameterBasedSqlProcessor(_dependencies, parameters); } From 697dd7182173ff3450eddc63b44837fba76331df Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 7 Dec 2024 11:04:54 +0100 Subject: [PATCH 094/107] Bump Npgsql dependency to 9.0.2 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 59515d0f6..362c7099e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ [9.0.0,10.0.0) 9.0.0 - 9.0.1 + 9.0.2 From e5e901e32c5515834f362d309cabda2bb692e1c2 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 7 Dec 2024 18:58:14 +0100 Subject: [PATCH 095/107] Bump version to 9.0.3 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 712d9ae07..6c16b9a8b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.2 + 9.0.3 latest true latest From c240ce58862c4aa4708026b4f5ceab4c1aa1000b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 28 Dec 2024 17:02:47 +0100 Subject: [PATCH 096/107] Redo enum label addition (#3425) To better handling reordering scenarios. Fixes #3424 (cherry picked from commit c2b3de42d62ff96423d5e7dcba54f3994d7b8477) --- .../NpgsqlMigrationsSqlGenerator.cs | 40 ++++++++++++++++--- .../Migrations/MigrationsNpgsqlTest.cs | 20 +++++++++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 800941791..f93fd5c59 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -1266,18 +1266,34 @@ protected virtual void GenerateEnumStatements(AlterDatabaseOperation operation, + "https://www.postgresql.org/docs/current/sql-altertype.html)"); } - for (var (newPos, oldPos) = (0, 0); newPos < newLabels.Count; newPos++) + for (var newPos = 0; newPos < newLabels.Count; newPos++) { var newLabel = newLabels[newPos]; - var oldLabel = oldPos < oldLabels.Count ? oldLabels[oldPos] : null; - - if (newLabel == oldLabel) + if (oldLabels.Contains(newLabel)) { - oldPos++; continue; } - GenerateAddEnumLabel(newEnum, newLabel, oldLabel, model, builder); + // We add the new label just after the last one we have in the new labels definition (when the last one is new, it will have + // just been added). + // If the new label happens to be the first one, add it before the first old label. Otherwise, if there are no old labels, + // just append the label (no before/after). + if (newPos == newLabels.Count - 1) + { + GenerateAddEnumLabel(newEnum, newLabel, beforeLabel: null, afterLabel: null, model, builder); + } + else if (newPos > 0) + { + GenerateAddEnumLabel(newEnum, newLabel, beforeLabel: null, afterLabel: newLabels[newPos - 1], model, builder); + } + else if (oldLabels.Count > 0) + { + GenerateAddEnumLabel(newEnum, newLabel, beforeLabel: oldLabels[0], afterLabel: null, model, builder); + } + else + { + GenerateAddEnumLabel(newEnum, newLabel, beforeLabel: null, afterLabel: null, model, builder); + } } } } @@ -1328,9 +1344,15 @@ protected virtual void GenerateAddEnumLabel( PostgresEnum enumType, string addedLabel, string? beforeLabel, + string? afterLabel, IModel? model, MigrationCommandListBuilder builder) { + if (beforeLabel is not null && afterLabel is not null) + { + throw new UnreachableException("Both beforeLabel and afterLabel can't be specified"); + } + var schema = enumType.Schema ?? model?.GetDefaultSchema(); builder @@ -1345,6 +1367,12 @@ protected virtual void GenerateAddEnumLabel( .Append(" BEFORE ") .Append(_stringTypeMapping.GenerateSqlLiteral(beforeLabel)); } + else if (afterLabel is not null) + { + builder + .Append(" AFTER ") + .Append(_stringTypeMapping.GenerateSqlLiteral(afterLabel)); + } builder.AppendLine(";"); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index db6cfe3fd..a641e9f0c 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2895,7 +2895,25 @@ await Test( l => Assert.Equal("Sad", l)); }); - AssertSql("""ALTER TYPE "Mood" ADD VALUE 'Angry' BEFORE 'Sad';"""); + AssertSql("""ALTER TYPE "Mood" ADD VALUE 'Angry' AFTER 'Happy';"""); + } + + [Fact] + public virtual async Task Alter_enum_change_label_ordering_does_nothing() + { + await Test( + builder => builder.HasPostgresEnum("Mood", ["Happy", "Sad"]), + builder => builder.HasPostgresEnum("Mood", ["Sad", "Happy"]), + model => + { + var moodEnum = Assert.Single(model.GetPostgresEnums()); + Assert.Collection( + moodEnum.Labels, + l => Assert.Equal("Happy", l), + l => Assert.Equal("Sad", l)); + }); + + AssertSql(); } [Fact] From 2e59bf59d63383ba90be7ffe2f10c506a0264540 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 17 Jan 2025 17:25:50 +0100 Subject: [PATCH 097/107] Fix quoted enum handling (#3434) Fixes #3433 (cherry picked from commit 068a7c6b4ec50ad4f6a62a260ddc523b9a8ac0c5) --- EFCore.PG.sln.DotSettings | 1 + .../Internal/NpgsqlTypeMappingSource.cs | 190 ++++++++++++++++-- .../Query/EnumQueryTest.cs | 52 ++++- .../Storage/NpgsqlTypeMappingSourceTest.cs | 29 +++ 4 files changed, 250 insertions(+), 22 deletions(-) diff --git a/EFCore.PG.sln.DotSettings b/EFCore.PG.sln.DotSettings index 9055198e9..ff5652c30 100644 --- a/EFCore.PG.sln.DotSettings +++ b/EFCore.PG.sln.DotSettings @@ -189,4 +189,5 @@ True True True + True \ No newline at end of file diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 6dbbb7e40..e5719e76e 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Immutable; using System.Data; +using System.Data.Common; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.NetworkInformation; @@ -771,6 +772,8 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) { var storeType = mappingInfo.StoreTypeName; var clrType = mappingInfo.ClrType; + string? schema; + string name; if (clrType is not null and not { IsEnum: true, IsClass: false }) { @@ -783,20 +786,31 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) if (storeType is null) { enumDefinition = _enumDefinitions.SingleOrDefault(m => m.ClrType == clrType); + + if (enumDefinition is null) + { + return null; + } + + (name, schema) = (enumDefinition.StoreTypeName, enumDefinition.StoreTypeSchema); } else { - // TODO: Not sure what to do about quoting. Is the user expected to configure properties - // TODO: with a quoted (schema-qualified) store type or not? - var dot = storeType.IndexOf('.'); - enumDefinition = dot is -1 - ? _enumDefinitions.SingleOrDefault(m => m.StoreTypeName == storeType) - : _enumDefinitions.SingleOrDefault(m => m.StoreTypeName == storeType[(dot + 1)..] && m.StoreTypeSchema == storeType[..dot]); - } - - if (enumDefinition is null) - { - return null; + // If the user is specifying the store type manually, they are not expected to have quotes in the name (e.g. because of upper- + // case characters). + // However, if we infer an enum array type mapping from an element (e.g. someEnums.Contains(b.SomeEnumColumn)), we get the + // element's store type - which for enums is quoted - and add []; so we get e.g. "MyEnum"[]. So we need to support quoted + // names here, by parsing the name and stripping the quotes. + ParseStoreTypeName(storeType, out name, out schema, out var size, out var precision, out var scale); + + enumDefinition = schema is null + ? _enumDefinitions.SingleOrDefault(m => m.StoreTypeName == name) + : _enumDefinitions.SingleOrDefault(m => m.StoreTypeName == name && m.StoreTypeSchema == schema); + + if (enumDefinition is null) + { + return null; + } } // We now have an enum definition from the context options. @@ -805,7 +819,6 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) // 1. The quoted type name is used in migrations, where quoting is needed // 2. The unquoted type name is set on NpgsqlParameter.DataTypeName // (though see https://github.com/npgsql/npgsql/issues/5710). - var (name, schema) = (enumDefinition.StoreTypeName, enumDefinition.StoreTypeSchema); return new NpgsqlEnumTypeMapping( _sqlGenerationHelper.DelimitIdentifier(name, schema), schema is null ? name : schema + "." + name, @@ -972,6 +985,8 @@ private static bool NameBasesUsesPrecision(ReadOnlySpan span) ref int? precision, ref int? scale) { + // TODO: Reimplement over ParseStoreTypeName below + if (storeTypeName is null) { return null; @@ -1056,4 +1071,155 @@ private static bool NameBasesUsesPrecision(ReadOnlySpan span) return new StringBuilder(preParens.Length).Append(preParens).Append(postParens).ToString(); } + + internal static void ParseStoreTypeName( + string storeTypeName, + out string name, + out string? schema, + out int? size, + out int? precision, + out int? scale) + { + var s = storeTypeName.AsSpan().Trim(); + var i = 0; + size = precision = scale = null; + + if (s.EndsWith("[]", StringComparison.Ordinal)) + { + // If this is an array store type, any facets (size, precision...) apply to the element and not to the array (e.g. varchar(32)[] + // is an array mapping with Size=null over an element mapping of varchar with Size=32). So just add everything up to the end. + // Note that if there's a schema (e.g. foo.varchar(32)[]), we return name=varchar(32), schema=foo. + name = s.ToString(); + schema = null; + return; + } + + name = ParseNameComponent(s); + + if (i < s.Length && s[i] == '.') + { + i++; + schema = name; + name = ParseNameComponent(s); + } + else + { + schema = null; + } + + s = s[i..]; + + if (s.Length == 0 || s[0] != '(') + { + // No facets + return; + } + + s = s[1..]; + + var closeParen = s.IndexOf(")", StringComparison.Ordinal); + if (closeParen == -1) + { + return; + } + + var inParens = s[..closeParen].Trim(); + // There may be stuff after the closing parentheses (e.g. timestamp(3) with time zone) + var postParens = s.Slice(closeParen + 1); + + switch (s.IndexOf(",", StringComparison.Ordinal)) + { + // No comma inside the parentheses, parse the value either as size or precision + case -1: + if (!int.TryParse(inParens, out var p)) + { + return; + } + + if (NameBasesUsesPrecision(name)) + { + precision = p; + // scale = 0; + } + else + { + size = p; + } + + break; + + case var comma: + if (int.TryParse(s[..comma].Trim(), out var parsedPrecision)) + { + precision = parsedPrecision; + } + else + { + return; + } + + if (int.TryParse(s[(comma + 1)..closeParen].Trim(), out var parsedScale)) + { + scale = parsedScale; + } + else + { + return; + } + + break; + } + + if (postParens.Length > 0) + { + // There's stuff after the parentheses (e.g. time(3) with time zone), append to the name + name += postParens.ToString(); + } + + string ParseNameComponent(ReadOnlySpan s) + { + var inQuotes = false; + StringBuilder builder = new(); + + if (s[i] == '"') + { + inQuotes = true; + i++; + } + + var start = i; + + for (; i < s.Length; i++) + { + var c = s[i]; + + if (inQuotes) + { + if (c == '"') + { + if (i + 1 < s.Length && s[i + 1] == '"') + { + builder.Append('"'); + i++; + continue; + } + + i++; + break; + } + } + else if (!char.IsWhiteSpace(c) && !char.IsAsciiLetterOrDigit(c) && c != '_') + { + break; + } + + builder.Append(c); + } + + var length = i - start; + return length == storeTypeName.Length + ? storeTypeName + : builder.ToString(); + } + } } diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index a3b899507..ce3826ba8 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -39,7 +39,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."MappedEnum" = 'sad'::test.mapped_enum """); @@ -57,7 +57,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."SchemaQualifiedEnum" = 'Happy (PgName)'::test.schema_qualified_enum """); @@ -78,7 +78,7 @@ await AssertQuery( """ @__sad_0='Sad' (DbType = Object) -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."MappedEnum" = @__sad_0 """); @@ -99,7 +99,7 @@ await AssertQuery( """ @__sad_0='1' -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."UnmappedEnum" = @__sad_0 """); @@ -120,7 +120,7 @@ await AssertQuery( """ @__sad_0='1' -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."UnmappedEnum" = @__sad_0 """); @@ -141,7 +141,7 @@ await AssertQuery( """ @__sad_0='Sad' (DbType = Object) -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."MappedEnum" = @__sad_0 """); @@ -160,7 +160,7 @@ await AssertQuery( AssertSql( """ -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."MappedEnum"::text LIKE '%sa%' """); @@ -181,7 +181,7 @@ await AssertQuery( """ @__values_0='0x01' (DbType = Object) -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."ByteEnum" = ANY (@__values_0) """); @@ -202,12 +202,33 @@ await AssertQuery( """ @__values_0='0x01' (DbType = Object) -SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum" +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s WHERE s."UnmappedByteEnum" = ANY (@__values_0) """); } + [ConditionalTheory] // #3433 + [MemberData(nameof(IsAsyncData))] + public async Task Where_uppercase_enum_array_contains_enum(bool async) + { + await using var ctx = CreateContext(); + + List values = [UppercaseNamedEnum.Sad]; + await AssertQuery( + async, + ss => ss.Set().Where(e => values.Contains(e.UppercaseNamedEnum))); + + AssertSql( + """ +@values={ 'Sad' } (DbType = Object) + +SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" +FROM test."SomeEntities" AS s +WHERE s."UppercaseNamedEnum" = ANY (@values) +"""); + } + #endregion #region Support @@ -240,6 +261,7 @@ public class SomeEnumEntity public UnmappedEnum UnmappedEnum { get; set; } public InferredEnum InferredEnum { get; set; } public SchemaQualifiedEnum SchemaQualifiedEnum { get; set; } + public UppercaseNamedEnum UppercaseNamedEnum { get; set; } public ByteEnum ByteEnum { get; set; } public UnmappedByteEnum UnmappedByteEnum { get; set; } public int EnumValue { get; set; } @@ -270,6 +292,12 @@ public enum SchemaQualifiedEnum Sad } + public enum UppercaseNamedEnum + { + Happy, + Sad + } + public enum ByteEnum : byte { Happy, @@ -304,7 +332,8 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build .MapEnum("mapped_enum", "test") .MapEnum("inferred_enum", "test") .MapEnum("byte_enum", "test") - .MapEnum("schema_qualified_enum", "test"); + .MapEnum("schema_qualified_enum", "test") + .MapEnum("UpperCaseEnum", "test"); return optionsBuilder; } @@ -341,6 +370,7 @@ public IReadOnlyDictionary EntityAsserters Assert.Equal(ee.UnmappedEnum, aa.UnmappedEnum); Assert.Equal(ee.InferredEnum, aa.InferredEnum); Assert.Equal(ee.SchemaQualifiedEnum, aa.SchemaQualifiedEnum); + Assert.Equal(ee.UppercaseNamedEnum, aa.UppercaseNamedEnum); Assert.Equal(ee.ByteEnum, aa.ByteEnum); Assert.Equal(ee.UnmappedByteEnum, aa.UnmappedByteEnum); Assert.Equal(ee.EnumValue, aa.EnumValue); @@ -370,6 +400,7 @@ public static IReadOnlyList CreateSomeEnumEntities() UnmappedEnum = UnmappedEnum.Happy, InferredEnum = InferredEnum.Happy, SchemaQualifiedEnum = SchemaQualifiedEnum.Happy, + UppercaseNamedEnum = UppercaseNamedEnum.Happy, ByteEnum = ByteEnum.Happy, UnmappedByteEnum = UnmappedByteEnum.Happy, EnumValue = (int)MappedEnum.Happy @@ -381,6 +412,7 @@ public static IReadOnlyList CreateSomeEnumEntities() UnmappedEnum = UnmappedEnum.Sad, InferredEnum = InferredEnum.Sad, SchemaQualifiedEnum = SchemaQualifiedEnum.Sad, + UppercaseNamedEnum = UppercaseNamedEnum.Sad, ByteEnum = ByteEnum.Sad, UnmappedByteEnum = UnmappedByteEnum.Sad, EnumValue = (int)MappedEnum.Sad diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs index 3809227de..be5062dda 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -291,6 +291,35 @@ public void Multirange_by_store_type_across_pg_versions() Assert.Same(typeof(List>), mappingDefault.ClrType); } +#nullable enable + [Theory] + [InlineData("integer", "integer", null, null, null, null)] + [InlineData("integer[]", "integer[]", null, null, null, null)] + [InlineData("foo.bar", "bar", "foo", null, null, null)] + [InlineData("foo.bar[]", "foo.bar[]", null, null, null, null)] + [InlineData("\"foo\"", "foo", null, null, null, null)] + [InlineData("\"fo.o\"", "fo.o", null, null, null, null)] + [InlineData("\"foo\".\"bar\"", "bar", "foo", null, null, null)] + [InlineData("\"f\"\"oo\"", "f\"oo", null, null, null, null)] + [InlineData("character varying", "character varying", null, null, null, null)] + [InlineData("with_underscore", "with_underscore", null, null, null, null)] + [InlineData("varchar(30)", "varchar", null, 30, null, null)] + [InlineData("varchar(30)[]", "varchar(30)[]", null, null, null, null)] + [InlineData("numeric(30)", "numeric", null, null, 30, null)] + [InlineData("numeric(30,3)", "numeric", null, null, 30, 3)] + public void ParseStoreType(string storeTypeName, string expectedName, string? expectedSchema, int? expectedSize, int? expectedPrecision, int? expectedScale) + { + NpgsqlTypeMappingSource.ParseStoreTypeName( + storeTypeName, out var name, out var schema, out var size, out var precision, out var scale); + + Assert.Equal(expectedName, name); + Assert.Equal(expectedSchema, schema); + Assert.Equal(expectedSize, size); + Assert.Equal(expectedPrecision, precision); + Assert.Equal(expectedScale, scale); + } +#nullable restore + #region Support private NpgsqlTypeMappingSource CreateTypeMappingSource(Version postgresVersion = null) From 74b858ae63efc603ffa74874e49c82eb58d43b09 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 17 Jan 2025 17:30:21 +0100 Subject: [PATCH 098/107] Depend on EF 9.0.1 --- Directory.Packages.props | 4 +-- .../MigrationsInfrastructureNpgsqlTest.cs | 25 +++++++++++-------- .../Query/EnumQueryTest.cs | 4 +-- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 22 ++++++++++++++++ 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 362c7099e..1b0afc7ba 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - [9.0.0,10.0.0) - 9.0.0 + [9.0.1,10.0.0) + 9.0.1 9.0.2 diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs index a67c241fa..542d3ddda 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsInfrastructureNpgsqlTest.cs @@ -7,16 +7,6 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations public class MigrationsInfrastructureNpgsqlTest(MigrationsInfrastructureNpgsqlTest.MigrationsInfrastructureNpgsqlFixture fixture) : MigrationsInfrastructureTestBase(fixture) { - // TODO: Remove once we sync to https://github.com/dotnet/efcore/pull/35106 - public override void Can_generate_no_migration_script() - { - } - - // TODO: Remove once we sync to https://github.com/dotnet/efcore/pull/35106 - public override void Can_generate_migration_from_initial_database_to_initial() - { - } - public override void Can_get_active_provider() { base.Can_get_active_provider(); @@ -24,6 +14,21 @@ public override void Can_get_active_provider() Assert.Equal("Npgsql.EntityFrameworkCore.PostgreSQL", ActiveProvider); } + // See #3407 + public override void Can_apply_two_migrations_in_transaction() + => Assert.ThrowsAny(() => base.Can_apply_two_migrations_in_transaction()); + + // See #3407 + public override Task Can_apply_two_migrations_in_transaction_async() + => Assert.ThrowsAnyAsync(() => base.Can_apply_two_migrations_in_transaction_async()); + + // This tests uses Fixture.CreateEmptyContext(), which does not go through MigrationsInfrastructureNpgsqlFixture.CreateContext() + // and therefore does not set the PostgresVersion in the context options. As a result, we try to drop the database with + // WITH (FORCE), which is only supported starting with PG 13. + [MinimumPostgresVersion(13, 0)] + public override Task Can_generate_no_migration_script() + => base.Can_generate_no_migration_script(); + [ConditionalFact(Skip = "https://github.com/dotnet/efcore/issues/33056")] public override void Can_apply_all_migrations() => base.Can_apply_all_migrations(); diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index ce3826ba8..7467b28f6 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -221,11 +221,11 @@ await AssertQuery( AssertSql( """ -@values={ 'Sad' } (DbType = Object) +@__values_0={ 'Sad' } (DbType = Object) SELECT s."Id", s."ByteEnum", s."EnumValue", s."InferredEnum", s."MappedEnum", s."SchemaQualifiedEnum", s."UnmappedByteEnum", s."UnmappedEnum", s."UppercaseNamedEnum" FROM test."SomeEntities" AS s -WHERE s."UppercaseNamedEnum" = ANY (@values) +WHERE s."UppercaseNamedEnum" = ANY (@__values_0) """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 1fd148a09..91f9d4e29 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -528,6 +528,28 @@ await AssertQuery( """); } + public override async Task Parameter_collection_ImmutableArray_of_ints_Contains_int(bool async) + { + await base.Parameter_collection_ImmutableArray_of_ints_Contains_int(async); + + AssertSql( + """ +@__ints_0={ '10', '999' } (Nullable = false) (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE p."Int" = ANY (@__ints_0) +""", + // + """ +@__ints_0={ '10', '999' } (Nullable = false) (DbType = Object) + +SELECT p."Id", p."Bool", p."Bools", p."DateTime", p."DateTimes", p."Enum", p."Enums", p."Int", p."Ints", p."NullableInt", p."NullableInts", p."NullableString", p."NullableStrings", p."String", p."Strings" +FROM "PrimitiveCollectionsEntity" AS p +WHERE NOT (p."Int" = ANY (@__ints_0) AND p."Int" = ANY (@__ints_0) IS NOT NULL) +"""); + } + public override async Task Parameter_collection_of_ints_Contains_nullable_int(bool async) { await base.Parameter_collection_of_ints_Contains_nullable_int(async); From e4a74e7968e2d453e4b4f5caca765b69a031f158 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 17 Jan 2025 19:01:49 +0100 Subject: [PATCH 099/107] Bump version to 9.0.4 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6c16b9a8b..9d7160b71 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 9.0.3 + 9.0.4 latest true latest From badb51eb49088fdf3f5a2b8c5887e4a0f73a1ea4 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 27 Jan 2025 17:48:38 +0100 Subject: [PATCH 100/107] Fix calling base method in NpgsqlMigrationsSqlGenerator (#3443) Fixes #3440 (cherry picked from commit 83f2cc37d97d0e3074ddcc628e8366c5825be4cd) --- src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index f93fd5c59..6e09ed6be 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -184,8 +184,8 @@ protected override void Generate( using (builder.Indent()) { - base.CreateTableColumns(operation, model, builder); - base.CreateTableConstraints(operation, model, builder); + CreateTableColumns(operation, model, builder); + CreateTableConstraints(operation, model, builder); builder.AppendLine(); } From 953ad1c83b22f44602eb799875ed2d4e564686df Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 15 Feb 2025 00:24:01 +0100 Subject: [PATCH 101/107] Generate UUIDv7 values for value-generated strings (#3462) Fixes #3460 (cherry picked from commit 16841f93e715456dfdffed3983498e3639d096f8) --- .../Internal/NpgsqlValueGeneratorSelector.cs | 15 ++++++++----- .../NpgsqlSequentialStringValueGenerator.cs | 22 +++++++++++++++++++ .../NpgsqlValueGeneratorSelectorTest.cs | 4 ++-- 3 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 src/EFCore.PG/ValueGeneration/NpgsqlSequentialStringValueGenerator.cs diff --git a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs index a9274f617..808532b42 100644 --- a/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs +++ b/src/EFCore.PG/ValueGeneration/Internal/NpgsqlValueGeneratorSelector.cs @@ -101,9 +101,14 @@ public override bool TrySelect(IProperty property, ITypeBase typeBase, out Value /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override ValueGenerator? FindForType(IProperty property, ITypeBase typeBase, Type clrType) - => property.ClrType.UnwrapNullableType() == typeof(Guid) - ? property.ValueGenerated == ValueGenerated.Never || property.GetDefaultValueSql() is not null - ? new TemporaryGuidValueGenerator() - : new NpgsqlSequentialGuidValueGenerator() - : base.FindForType(property, typeBase, clrType); + => property.ClrType.UnwrapNullableType() switch + { + var t when t == typeof(Guid) && property.ValueGenerated is not ValueGenerated.Never && property.GetDefaultValueSql() is null + => new NpgsqlSequentialGuidValueGenerator(), + + var t when t == typeof(string) && property.ValueGenerated is not ValueGenerated.Never && property.GetDefaultValueSql() is null + => new NpgsqlSequentialStringValueGenerator(), + + _ => base.FindForType(property, typeBase, clrType) + }; } diff --git a/src/EFCore.PG/ValueGeneration/NpgsqlSequentialStringValueGenerator.cs b/src/EFCore.PG/ValueGeneration/NpgsqlSequentialStringValueGenerator.cs new file mode 100644 index 000000000..8c4f595dc --- /dev/null +++ b/src/EFCore.PG/ValueGeneration/NpgsqlSequentialStringValueGenerator.cs @@ -0,0 +1,22 @@ +namespace Npgsql.EntityFrameworkCore.PostgreSQL.ValueGeneration; + +/// +/// This API supports the Entity Framework Core infrastructure and is not intended to be used +/// directly from your code. This API may change or be removed in future releases. +/// +public class NpgsqlSequentialStringValueGenerator : ValueGenerator +{ + private readonly NpgsqlSequentialGuidValueGenerator _guidGenerator = new(); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override bool GeneratesTemporaryValues => false; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override string Next(EntityEntry entry) => _guidGenerator.Next(entry).ToString(); +} diff --git a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs index 57b0d1b9a..9b6b3ae42 100644 --- a/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs +++ b/test/EFCore.PG.Tests/NpgsqlValueGeneratorSelectorTest.cs @@ -21,7 +21,7 @@ public void Returns_built_in_generators_for_types_setup_for_value_generation() AssertGenerator("NullableShort"); AssertGenerator("NullableByte"); AssertGenerator("Decimal"); - AssertGenerator("String"); + AssertGenerator("String"); AssertGenerator("Guid"); AssertGenerator("Binary"); } @@ -128,7 +128,7 @@ public void Returns_sequence_value_generators_when_configured_for_model() AssertGenerator>("NullableInt", setSequences: true); AssertGenerator>("NullableLong", setSequences: true); AssertGenerator>("NullableShort", setSequences: true); - AssertGenerator("String", setSequences: true); + AssertGenerator("String", setSequences: true); AssertGenerator("Guid", setSequences: true); AssertGenerator("Binary", setSequences: true); } From e952c84779500c65fd99393b887e341e523f6c33 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 15 Feb 2025 01:26:25 +0200 Subject: [PATCH 102/107] Stop testing on PG12 (out of support) (cherry picked from commit 3cbdba452533fa223acf0e39cf0d57c4a4c1e74a) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fca39f9ce..d6a5ceaa7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, windows-2022] - pg_major: [17, 16, 15, 14, 13, 12] + pg_major: [17, 16, 15, 14, 13] config: [Release] include: - os: ubuntu-22.04 From 6a3445557d14cb2eb6a766df02f89c06ff5cec00 Mon Sep 17 00:00:00 2001 From: Marcus Bernander Date: Sat, 15 Feb 2025 18:39:14 +0100 Subject: [PATCH 103/107] Always close connection after reloading types in migration. (#3465) Closes #3464 (cherry picked from commit 786b635d93d12917d20b8a791e547d7d67f8899c) --- src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs index e8a0bea28..601af5604 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlMigrator.cs @@ -85,7 +85,7 @@ public override void Migrate(string? targetMigration) { npgsqlConnection.ReloadTypes(); } - catch + finally { _connection.Close(); } @@ -128,7 +128,7 @@ public override async Task MigrateAsync(string? targetMigration, CancellationTok { await npgsqlConnection.ReloadTypesAsync(cancellationToken).ConfigureAwait(false); } - catch + finally { await _connection.CloseAsync().ConfigureAwait(false); } From 0fdf784b1f695a7e77f487f4d7b812d95744a093 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 28 Feb 2025 15:45:03 +0100 Subject: [PATCH 104/107] Fix nullability for IndexOf (array_position) (#3477) Fixes #3474 (cherry picked from commit 0212cae5c5b693c0a83a8c93bd1e6de79c2f219a) --- .../Internal/NpgsqlArrayMethodTranslator.cs | 10 ++++++++-- src/EFCore.PG/Utilities/Util.cs | 7 ++++++- .../Query/ArrayArrayQueryTest.cs | 4 ++-- .../Query/ArrayListQueryTest.cs | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs index e128dacea..847da4c17 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArrayMethodTranslator.cs @@ -141,7 +141,10 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) "array_position", [array, item], nullable: true, - TrueArrays[2], + // array_position can return NULL even if both its arguments are non-nullable; + // this is currently the way to express that (see + // https://github.com/dotnet/efcore/pull/33814#issuecomment-2687857927). + FalseArrays[2], arrayOrList.Type), _sqlExpressionFactory.Constant(1)), _sqlExpressionFactory.Constant(-1)); @@ -161,7 +164,10 @@ static bool IsMappedToNonArray(SqlExpression arrayOrList) "array_position", [array, item, startIndex], nullable: true, - TrueArrays[3], + // array_position can return NULL even if both its arguments are non-nullable; + // this is currently the way to express that (see + // https://github.com/dotnet/efcore/pull/33814#issuecomment-2687857927). + FalseArrays[3], arrayOrList.Type), _sqlExpressionFactory.Constant(1)), _sqlExpressionFactory.Constant(-1)); diff --git a/src/EFCore.PG/Utilities/Util.cs b/src/EFCore.PG/Utilities/Util.cs index bdbceb282..97bca187c 100644 --- a/src/EFCore.PG/Utilities/Util.cs +++ b/src/EFCore.PG/Utilities/Util.cs @@ -15,5 +15,10 @@ internal static class Statics [true, true, true, true, true, true, true, true] ]; - internal static readonly bool[][] FalseArrays = [[], [false], [false, false]]; + internal static readonly bool[][] FalseArrays = [ + [], + [false], + [false, false], + [false, false, false] + ]; } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs index e00543860..b89306d03 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayArrayQueryTest.cs @@ -835,7 +835,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE array_position(s."IntArray", 6) - 1 = 1 +WHERE COALESCE(array_position(s."IntArray", 6) - 1, -1) = 1 """); } @@ -849,7 +849,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE array_position(s."IntArray", 6, 2) - 1 = 1 +WHERE COALESCE(array_position(s."IntArray", 6, 2) - 1, -1) = 1 """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs index 361fb653b..99e88ded6 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ArrayListQueryTest.cs @@ -855,7 +855,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE array_position(s."IntList", 6) - 1 = 1 +WHERE COALESCE(array_position(s."IntList", 6) - 1, -1) = 1 """); } @@ -871,7 +871,7 @@ await AssertQuery( """ SELECT s."Id", s."ArrayContainerEntityId", s."ArrayOfStringConvertedToDelimitedString", s."Byte", s."ByteArray", s."Bytea", s."EnumConvertedToInt", s."EnumConvertedToString", s."IList", s."IntArray", s."IntList", s."ListOfStringConvertedToDelimitedString", s."NonNullableText", s."NullableEnumConvertedToString", s."NullableEnumConvertedToStringWithNonNullableLambda", s."NullableIntArray", s."NullableIntList", s."NullableStringArray", s."NullableStringList", s."NullableText", s."StringArray", s."StringList", s."ValueConvertedArrayOfEnum", s."ValueConvertedListOfEnum", s."Varchar10", s."Varchar15" FROM "SomeEntities" AS s -WHERE array_position(s."IntList", 6, 2) - 1 = 1 +WHERE COALESCE(array_position(s."IntList", 6, 2) - 1, -1) = 1 """); } From 4bf32483bccf20335ea16848bc8e6fb68eb68e7a Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 28 Feb 2025 16:35:22 +0100 Subject: [PATCH 105/107] Preserve collation when changing column type (#3479) Fixes #3476 (cherry picked from commit e327d6bccfe56748ae04bf276ec7455ff1dcf48e) --- .../NpgsqlMigrationsSqlGenerator.cs | 10 ++++++-- .../Migrations/MigrationsNpgsqlTest.cs | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs index 6e09ed6be..fccab7d18 100644 --- a/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs +++ b/src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs @@ -456,9 +456,15 @@ protected override void Generate(AlterColumnOperation operation, IModel? model, .Append("TYPE ") .Append(type); - if (newCollation != oldCollation) + if (newCollation is not null) { - builder.Append(" COLLATE ").Append(DelimitIdentifier(newCollation ?? "default")); + builder.Append(" COLLATE ").Append(DelimitIdentifier(newCollation)); + } + else if (type == oldType) + { + // If the type is the same, make it more explicit that we're just resetting the collation to the default + // (this isn't really required) + builder.Append(" COLLATE ").Append(DelimitIdentifier("default")); } builder.AppendLine(";"); diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index a641e9f0c..aff93c789 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -972,6 +972,30 @@ public override async Task Alter_column_change_type() AssertSql("""ALTER TABLE "People" ALTER COLUMN "SomeColumn" TYPE bigint;"""); } + [Fact] + public virtual async Task Alter_column_change_type_preserves_collation() + { + await Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").Property("SomeColumn") + .HasColumnType("varchar") + .UseCollation(NonDefaultCollation), + builder => builder.Entity("People").Property("SomeColumn") + .HasColumnType("text") + .UseCollation(NonDefaultCollation), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "SomeColumn"); + Assert.Equal(NonDefaultCollation, column.Collation); + }); + + AssertSql( + """ +ALTER TABLE "People" ALTER COLUMN "SomeColumn" TYPE text COLLATE "POSIX"; +"""); + } + public override async Task Alter_column_make_required() { await base.Alter_column_make_required(); From 80ec4ae3cec6ded1a9a660e67899b6f03c430ca6 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 1 Mar 2025 08:17:30 +0100 Subject: [PATCH 106/107] Preserve ConfigureDataSource() callback when applying other context options (#3482) Fixes #3478 (cherry picked from commit 0db010c8dc454aacee3eee61c669c6eeca2f5d80) --- src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index 7dba76190..269a66fea 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -109,6 +109,7 @@ public NpgsqlOptionsExtension(NpgsqlOptionsExtension copyFrom) : base(copyFrom) { DataSource = copyFrom.DataSource; + DataSourceBuilderAction = copyFrom.DataSourceBuilderAction; AdminDatabase = copyFrom.AdminDatabase; _postgresVersion = copyFrom._postgresVersion; UseRedshift = copyFrom.UseRedshift; From fd2380957bee5cd86f336466af36b08c0163f1a5 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 1 Mar 2025 09:24:20 +0200 Subject: [PATCH 107/107] Bump Npgsql to 9.0.3 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1b0afc7ba..5a263de4f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ [9.0.1,10.0.0) 9.0.1 - 9.0.2 + 9.0.3