From 03f9eb362cdd116b66af0c2c08983368b080d569 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 15 Sep 2023 13:33:38 +0300 Subject: [PATCH 01/10] React to PG16 collation-related change in tests (#2870) --- .../Migrations/MigrationsNpgsqlTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs index d68faa195..eed498f02 100644 --- a/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Migrations/MigrationsNpgsqlTest.cs @@ -2966,21 +2966,21 @@ public virtual async Task Create_collation_non_deterministic() { await Test( _ => { }, - builder => builder.HasCollation("some_collation", locale: "en-u-ks-primary", provider: "icu", deterministic: false), + builder => builder.HasCollation("some_collation", locale: "en-u-ks-level1", provider: "icu", deterministic: false), model => { var collation = Assert.Single(PostgresCollation.GetCollations(model)); Assert.Equal("some_collation", collation.Name); Assert.Equal("icu", collation.Provider); - Assert.Equal("en-u-ks-primary", collation.LcCollate); - Assert.Equal("en-u-ks-primary", collation.LcCtype); + Assert.Equal("en-u-ks-level1", collation.LcCollate); + Assert.Equal("en-u-ks-level1", collation.LcCtype); Assert.False(collation.IsDeterministic); }); AssertSql( """ -CREATE COLLATION some_collation (LOCALE = 'en-u-ks-primary', +CREATE COLLATION some_collation (LOCALE = 'en-u-ks-level1', PROVIDER = icu, DETERMINISTIC = False ); From 60b138d81554c4b66c7dfff1f3871157ee791901 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 16 Sep 2023 18:30:36 +0300 Subject: [PATCH 02/10] Sync to 8.0.0-rc.2.23465.12 (#2873) --- Directory.Packages.props | 4 +-- .../Internal/DateIntervalMultirangeMapping.cs | 2 +- .../Internal/DateIntervalRangeMapping.cs | 2 +- .../Storage/Internal/DateMapping.cs | 2 +- .../Internal/DurationIntervalMapping.cs | 2 +- .../Internal/IntervalMultirangeMapping.cs | 2 +- .../Storage/Internal/IntervalRangeMapping.cs | 2 +- .../Internal/LegacyTimestampInstantMapping.cs | 2 +- .../NpgsqlNodaTimeTypeMappingSourcePlugin.cs | 4 +-- .../Storage/Internal/PeriodIntervalMapping.cs | 2 +- .../Storage/Internal/TimeMapping.cs | 2 +- .../Storage/Internal/TimeTzMapping.cs | 2 +- .../Internal/TimestampLocalDateTimeMapping.cs | 2 +- .../Internal/TimestampTzInstantMapping.cs | 2 +- .../TimestampTzOffsetDateTimeMapping.cs | 2 +- .../TimestampTzZonedDateTimeMapping.cs | 2 +- .../Mapping/NpgsqlArrayTypeMapping.cs | 25 +++++++++++-------- .../Internal/NpgsqlTypeMappingSource.cs | 8 +++--- .../Query/ComplexTypeQueryNpgsqlTest.cs | 7 ++++++ .../PrimitiveCollectionsQueryNpgsqlTest.cs | 23 ++++++++--------- 20 files changed, 54 insertions(+), 45 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1fa19b779..e03ddcea0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - 8.0.0-rc.1.23419.6 - 8.0.0-rc.1.23419.4 + 8.0.0-rc.2.23465.12 + 8.0.0-rc.2.23465.13 8.0.0-preview.4 diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalMultirangeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalMultirangeMapping.cs index 4125aee51..64e442f3b 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalMultirangeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalMultirangeMapping.cs @@ -48,7 +48,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new DateIntervalMultirangeMapping(Parameters.WithStoreTypeAndSize(storeType, size), _dateIntervalRangeMapping); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalRangeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalRangeMapping.cs index 25243d387..b62db96a9 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalRangeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateIntervalRangeMapping.cs @@ -55,7 +55,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new DateIntervalRangeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs index 21190dd4c..4222f90ea 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DateMapping.cs @@ -48,7 +48,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new DateMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs index 0b035be53..a09c64cc2 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/DurationIntervalMapping.cs @@ -51,7 +51,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new DurationIntervalMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/IntervalMultirangeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/IntervalMultirangeMapping.cs index b127cd8f3..4b12fa7e2 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/IntervalMultirangeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/IntervalMultirangeMapping.cs @@ -48,7 +48,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new IntervalMultirangeMapping(Parameters.WithStoreTypeAndSize(storeType, size), _intervalRangeMapping); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs index d3b5266a8..7089a32e5 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/IntervalRangeMapping.cs @@ -58,7 +58,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new IntervalRangeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/LegacyTimestampInstantMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/LegacyTimestampInstantMapping.cs index 124f15b64..06acea1e0 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/LegacyTimestampInstantMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/LegacyTimestampInstantMapping.cs @@ -50,7 +50,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new LegacyTimestampInstantMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs b/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs index 9aae9f49b..68caddc01 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs @@ -192,14 +192,14 @@ public NpgsqlNodaTimeTypeMappingSourcePlugin(ISqlGenerationHelper sqlGenerationH { if (clrType is null) { - return mappings[0].Clone(in mappingInfo); + return mappings[0]; } foreach (var m in mappings) { if (m.ClrType == clrType) { - return m.Clone(in mappingInfo); + return m; } } diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs index ab48edcfe..dc35a2b3d 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/PeriodIntervalMapping.cs @@ -56,7 +56,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new PeriodIntervalMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs index 49688f58d..bc2f1934c 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeMapping.cs @@ -55,7 +55,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new TimeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs index 42f71e2ac..76dee137f 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimeTzMapping.cs @@ -67,7 +67,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new TimeTzMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs index cf6d35330..bb3035339 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampLocalDateTimeMapping.cs @@ -54,7 +54,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new TimestampLocalDateTimeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs index 4e2558e0a..84fef01c1 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzInstantMapping.cs @@ -44,7 +44,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new TimestampTzInstantMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs index 5c9da9dfd..be9ab26e7 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzOffsetDateTimeMapping.cs @@ -54,7 +54,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new TimestampTzOffsetDateTimeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs index 7afc730a7..5b7004721 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/TimestampTzZonedDateTimeMapping.cs @@ -49,7 +49,7 @@ 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. /// - public override RelationalTypeMapping Clone(string storeType, int? size) + public override RelationalTypeMapping WithStoreTypeAndSize(string storeType, int? size) => new TimestampTzZonedDateTimeMapping(Parameters.WithStoreTypeAndSize(storeType, size)); /// diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index 2b906f9fc..672a0d9ef 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -3,6 +3,7 @@ using System.Data.Common; using System.Text; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.ValueConversion; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; @@ -84,12 +85,11 @@ public NpgsqlArrayTypeMapping(string storeType, RelationalTypeMapping elementTyp private static RelationalTypeMappingParameters CreateParameters( string storeType, - RelationalTypeMapping elementTypeMapping) + RelationalTypeMapping elementMapping) { ValueConverter? converter = null; - // TODO: Make sure this all still makes sense - if (elementTypeMapping.Converter is { } elementConverter) + if (elementMapping.Converter is { } elementConverter) { var collectionTypeDefinition = typeof(TCollection) switch { @@ -133,22 +133,25 @@ static Type MakeCollectionType(Type collectionType, Type elementType, bool isEle } } - // TODO: Confirm model vs. provider type here! // We do GetElementType for multidimensional arrays - these don't implement generic IEnumerable<> - var modelElementType = typeof(TCollection).TryGetElementType(typeof(IEnumerable<>)) ?? typeof(TCollection).GetElementType(); + var elementType = typeof(TCollection).TryGetElementType(typeof(IEnumerable<>)) ?? typeof(TCollection).GetElementType(); - Check.DebugAssert(modelElementType is not null, "modelElementType cannot be null"); + Check.DebugAssert(elementType is not null, "modelElementType cannot be null"); +#pragma warning disable EF1001 var comparer = typeof(TCollection).IsArray && typeof(TCollection).GetArrayRank() > 1 ? null // TODO: Value comparer for multidimensional arrays : (ValueComparer?)Activator.CreateInstance( - modelElementType.IsNullableValueType() - ? typeof(NullableValueTypeListComparer<>).MakeGenericType(modelElementType.UnwrapNullableType()) - : typeof(ListComparer<>).MakeGenericType(elementTypeMapping.Comparer.Type), - elementTypeMapping.Comparer); + elementType.IsNullableValueType() + ? typeof(NullableValueTypeListComparer<>).MakeGenericType(elementType.UnwrapNullableType()) + : elementMapping.Comparer.Type.IsAssignableFrom(elementType) + ? typeof(ListComparer<>).MakeGenericType(elementType) + : typeof(ObjectListComparer<>).MakeGenericType(elementType), + elementMapping.Comparer.ToNullableComparer(elementType)!); +#pragma warning restore EF1001 return new RelationalTypeMappingParameters( - new CoreTypeMappingParameters(typeof(TCollection), converter, comparer, elementMapping: elementTypeMapping), + new CoreTypeMappingParameters(typeof(TCollection), converter, comparer, elementMapping: elementMapping), storeType); } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index e7d489dd8..ffd115729 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -467,14 +467,14 @@ protected virtual void SetupEnumMappings(ISqlGenerationHelper sqlGenerationHelpe { if (clrType is null) { - return mappings[0].Clone(in mappingInfo); + return mappings[0]; } foreach (var m in mappings) { if (m.ClrType == clrType) { - return m.Clone(in mappingInfo); + return m; } } @@ -523,14 +523,14 @@ protected virtual void SetupEnumMappings(ISqlGenerationHelper sqlGenerationHelpe // See #342 for when size > 10485760 return mappingInfo.Size <= 10485760 - ? mapping.Clone($"{mapping.StoreType}({mappingInfo.Size})", mappingInfo.Size) + ? mapping.WithStoreTypeAndSize($"{mapping.StoreType}({mappingInfo.Size})", mappingInfo.Size) : _text; } if (clrType == typeof(BitArray)) { mapping = mappingInfo.IsFixedLength ?? false ? _bit : _varbit; - return mapping.Clone($"{mapping.StoreType}({mappingInfo.Size})", mappingInfo.Size); + return mapping.WithStoreTypeAndSize($"{mapping.StoreType}({mappingInfo.Size})", mappingInfo.Size); } } diff --git a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs index 638c80576..60b6af10b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/ComplexTypeQueryNpgsqlTest.cs @@ -239,6 +239,13 @@ public override async Task Complex_type_equals_parameter(bool async) """); } + public override async Task Complex_type_equals_null(bool async) + { + await base.Complex_type_equals_null(async); + + AssertSql(); + } + public override async Task Subquery_over_complex_type(bool async) { await base.Subquery_over_complex_type(async); diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index 4df90b8de..a16486c3a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -5,7 +5,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; -public class PrimitiveCollectionsQueryNpgsqlTest : PrimitiveCollectionsQueryTestBase< +public class PrimitiveCollectionsQueryNpgsqlTest : PrimitiveCollectionsQueryRelationalTestBase< PrimitiveCollectionsQueryNpgsqlTest.PrimitiveCollectionsQueryNpgsqlFixture> { public PrimitiveCollectionsQueryNpgsqlTest(PrimitiveCollectionsQueryNpgsqlFixture fixture, ITestOutputHelper testOutputHelper) @@ -818,9 +818,9 @@ WHERE cardinality(@__ints1_0 || p."Ints" || @__ints2_1) = 4 """); } - public override async Task Column_collection_Concat_parameter_collection_equality_inline_collection_not_supported(bool async) + public override async Task Column_collection_Concat_parameter_collection_equality_inline_collection(bool async) { - await base.Column_collection_Concat_parameter_collection_equality_inline_collection_not_supported(async); + await base.Column_collection_Concat_parameter_collection_equality_inline_collection(async); AssertSql(); } @@ -960,10 +960,9 @@ public override void Parameter_collection_in_subquery_and_Convert_as_compiled_qu public override async Task Parameter_collection_in_subquery_Union_another_parameter_collection_as_compiled_query(bool async) { - var message = (await Assert.ThrowsAsync( - () => base.Parameter_collection_in_subquery_Union_another_parameter_collection_as_compiled_query(async))).Message; + await base.Parameter_collection_in_subquery_Union_another_parameter_collection_as_compiled_query(async); - Assert.Equal(RelationalStrings.SetOperationsRequireAtLeastOneSideWithValidTypeMapping("Union"), message); + AssertSql(); } public override async Task Parameter_collection_in_subquery_Count_as_compiled_query(bool async) @@ -1047,9 +1046,9 @@ ORDER BY p."Id" NULLS FIRST """); } - public override async Task Project_collection_of_ints_with_paging(bool async) + public override async Task Project_collection_of_nullable_ints_with_paging(bool async) { - await base.Project_collection_of_ints_with_paging(async); + await base.Project_collection_of_nullable_ints_with_paging(async); AssertSql( """ @@ -1060,9 +1059,9 @@ ORDER BY p."Id" NULLS FIRST """); } - public override async Task Project_collection_of_ints_with_paging2(bool async) + public override async Task Project_collection_of_nullable_ints_with_paging2(bool async) { - await base.Project_collection_of_ints_with_paging2(async); + await base.Project_collection_of_nullable_ints_with_paging2(async); AssertSql( """ @@ -1078,9 +1077,9 @@ OFFSET 1 """); } - public override async Task Project_collection_of_ints_with_paging3(bool async) + public override async Task Project_collection_of_nullable_ints_with_paging3(bool async) { - await base.Project_collection_of_ints_with_paging3(async); + await base.Project_collection_of_nullable_ints_with_paging3(async); AssertSql( """ From 4af0dc00ee37fb04a5d6ada33bd34a31d7eb33b0 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 25 Sep 2023 13:57:23 +0200 Subject: [PATCH 03/10] Various date/time timezone-related fixes (#2885) Fixes #2884 --- .../NpgsqlNodaTimeMemberTranslatorPlugin.cs | 8 +- .../NpgsqlDateTimeMemberTranslator.cs | 129 +++++++++++------- .../Query/GearsOfWarQueryNpgsqlTest.cs | 15 +- .../PrimitiveCollectionsQueryNpgsqlTest.cs | 4 +- .../Query/TPCGearsOfWarQueryNpgsqlTest.cs | 4 +- .../Query/TPTGearsOfWarQueryNpgsqlTest.cs | 4 +- .../Query/TimestampQueryTest.cs | 2 +- 7 files changed, 107 insertions(+), 59 deletions(-) diff --git a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs index 10ecc026b..5c7d98565 100644 --- a/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs +++ b/src/EFCore.PG.NodaTime/Query/Internal/NpgsqlNodaTimeMemberTranslatorPlugin.cs @@ -130,7 +130,7 @@ public NpgsqlNodaTimeMemberTranslator( || declaringType == typeof(LocalTime) || declaringType == typeof(Period)) { - return TranslateDateTime(instance, member, returnType); + return TranslateDateTime(instance, member); } if (declaringType == typeof(ZonedDateTime)) @@ -281,7 +281,7 @@ SqlExpression Upper() _dateTypeMapping); } - private SqlExpression? TranslateDateTime(SqlExpression instance, MemberInfo member, Type returnType) + private SqlExpression? TranslateDateTime(SqlExpression instance, MemberInfo member) => member.Name switch { "Year" or "Years" => GetDatePartExpression(instance, "year"), @@ -378,7 +378,7 @@ private SqlExpression GetDatePartExpressionDouble( return member == ZonedDateTime_LocalDateTime ? instance - : TranslateDateTime(instance, member, returnType); + : TranslateDateTime(instance, member); } // date_part, which is used to extract most components, doesn't have an overload for timestamptz, so passing one directly @@ -388,6 +388,6 @@ private SqlExpression GetDatePartExpressionDouble( return member == ZonedDateTime_LocalDateTime ? instance - : TranslateDateTime(instance, member, returnType); + : TranslateDateTime(instance, member); } } diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs index 59629e5d4..0fa881eda 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlDateTimeMemberTranslator.cs @@ -1,4 +1,5 @@ -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; +using System.Diagnostics.CodeAnalysis; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics; @@ -23,9 +24,7 @@ public class NpgsqlDateTimeMemberTranslator : IMemberTranslator /// 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 NpgsqlDateTimeMemberTranslator( - IRelationalTypeMappingSource typeMappingSource, - NpgsqlSqlExpressionFactory sqlExpressionFactory) + public NpgsqlDateTimeMemberTranslator(IRelationalTypeMappingSource typeMappingSource, NpgsqlSqlExpressionFactory sqlExpressionFactory) { _typeMappingSource = typeMappingSource; _timestampMapping = typeMappingSource.FindMapping("timestamp without time zone")!; @@ -40,19 +39,19 @@ public NpgsqlDateTimeMemberTranslator( Type returnType, IDiagnosticsLogger logger) { - var type = member.DeclaringType; + var declaringType = member.DeclaringType; - if (type != typeof(DateTime) - && type != typeof(DateTimeOffset) - && type != typeof(DateOnly) - && type != typeof(TimeOnly)) + if (declaringType != typeof(DateTime) + && declaringType != typeof(DateTimeOffset) + && declaringType != typeof(DateOnly) + && declaringType != typeof(TimeOnly)) { return null; } - if (type == typeof(DateTimeOffset) + if (declaringType == typeof(DateTimeOffset) && instance is not null - && TranslateDateTimeOffset(instance, member, returnType) is { } translated) + && TranslateDateTimeOffset(instance, member) is { } translated) { return translated; } @@ -65,19 +64,19 @@ public NpgsqlDateTimeMemberTranslator( // When given a timestamptz, date_trunc performs the truncation with respect to TimeZone; to avoid that, we use the overload // accepting a time zone, and pass UTC. For regular timestamp (or in legacy timestamp mode), we use the simpler overload without // a time zone. - switch (instance?.TypeMapping) + switch (instance) { - case NpgsqlTimestampTypeMapping: - case NpgsqlTimestampTzTypeMapping when NpgsqlTypeMappingSource.LegacyTimestampBehavior: + case { TypeMapping: NpgsqlTimestampTypeMapping }: + case { TypeMapping: NpgsqlTimestampTzTypeMapping } when NpgsqlTypeMappingSource.LegacyTimestampBehavior: return _sqlExpressionFactory.Function( "date_trunc", - new[] { _sqlExpressionFactory.Constant("day"), instance! }, + new[] { _sqlExpressionFactory.Constant("day"), instance }, nullable: true, argumentsPropagateNullability: TrueArrays[2], returnType, instance.TypeMapping); - case NpgsqlTimestampTzTypeMapping: + case { TypeMapping: NpgsqlTimestampTzTypeMapping }: return _sqlExpressionFactory.Function( "date_trunc", new[] { _sqlExpressionFactory.Constant("day"), instance, _sqlExpressionFactory.Constant("UTC") }, @@ -86,8 +85,9 @@ public NpgsqlDateTimeMemberTranslator( returnType, instance.TypeMapping); - // If DateTime.Date is invoked on a PostgreSQL date, simply no-op. - case NpgsqlDateTypeMapping: + // If DateTime.Date is invoked on a PostgreSQL date (or DateOnly, which can only be mapped to datE), simply no-op. + case { TypeMapping: NpgsqlDateTypeMapping }: + case { Type: var type } when type == typeof(DateOnly): return instance; default: @@ -98,14 +98,14 @@ public NpgsqlDateTimeMemberTranslator( return member.Name switch { // Legacy behavior - nameof(DateTime.Now) when NpgsqlTypeMappingSource.LegacyTimestampBehavior + nameof(DateTime.Now) when NpgsqlTypeMappingSource.LegacyTimestampBehavior => UtcNow(), nameof(DateTime.UtcNow) when NpgsqlTypeMappingSource.LegacyTimestampBehavior => _sqlExpressionFactory.AtUtc(UtcNow()), // Return a UTC timestamp, but as timestamp without time zone // We support getting a local DateTime via DateTime.Now (based on PG TimeZone), but there's no way to get a non-UTC // DateTimeOffset. - nameof(DateTime.Now) => type == typeof(DateTimeOffset) + nameof(DateTime.Now) => declaringType == typeof(DateTimeOffset) ? throw new InvalidOperationException("Cannot translate DateTimeOffset.Now - use UtcNow.") : LocalNow(), nameof(DateTime.UtcNow) => UtcNow(), @@ -113,27 +113,31 @@ public NpgsqlDateTimeMemberTranslator( nameof(DateTime.Today) => _sqlExpressionFactory.Function( "date_trunc", new[] { _sqlExpressionFactory.Constant("day"), LocalNow() }, - nullable: true, - argumentsPropagateNullability: TrueArrays[2], - returnType), + nullable: false, + argumentsPropagateNullability: FalseArrays[2], + typeof(DateTime), + _timestampMapping), - nameof(DateTime.Year) => GetDatePartExpression(instance!, "year"), - nameof(DateTime.Month) => GetDatePartExpression(instance!, "month"), - nameof(DateTime.DayOfYear) => GetDatePartExpression(instance!, "doy"), - nameof(DateTime.Day) => GetDatePartExpression(instance!, "day"), - nameof(DateTime.Hour) => GetDatePartExpression(instance!, "hour"), - nameof(DateTime.Minute) => GetDatePartExpression(instance!, "minute"), - nameof(DateTime.Second) => GetDatePartExpression(instance!, "second"), + nameof(DateTime.Year) => DatePart(instance!, "year"), + nameof(DateTime.Month) => DatePart(instance!, "month"), + nameof(DateTime.DayOfYear) => DatePart(instance!, "doy"), + nameof(DateTime.Day) => DatePart(instance!, "day"), + nameof(DateTime.Hour) => DatePart(instance!, "hour"), + nameof(DateTime.Minute) => DatePart(instance!, "minute"), + nameof(DateTime.Second) => DatePart(instance!, "second"), nameof(DateTime.Millisecond) => null, // Too annoying // .NET's DayOfWeek is an enum, but its int values happen to correspond to PostgreSQL - nameof(DateTime.DayOfWeek) => GetDatePartExpression(instance!, "dow", floor: true), + nameof(DateTime.DayOfWeek) => DatePart(instance!, "dow", floor: true), - nameof(DateTime.TimeOfDay) => _sqlExpressionFactory.Convert( - instance!, - typeof(TimeSpan), - _typeMappingSource.FindMapping(typeof(TimeSpan), storeTypeName: "time")), + // Casting a timestamptz to time (to get the time component) converts it to a local timestamp based on TimeZone. + // Convert to a timestamp without time zone at UTC to get the right values. + nameof(DateTime.TimeOfDay) when TryConvertAwayFromTimestampTz(instance!, out var convertedInstance) + => _sqlExpressionFactory.Convert( + convertedInstance, + typeof(TimeSpan), + _typeMappingSource.FindMapping(typeof(TimeSpan), storeTypeName: "time")), // TODO: Should be possible nameof(DateTime.Ticks) => null, @@ -154,16 +158,16 @@ SqlExpression LocalNow() => _sqlExpressionFactory.Convert(UtcNow(), returnType, _timestampMapping); } - private SqlExpression GetDatePartExpression( + private SqlExpression? DatePart( SqlExpression instance, string partName, bool floor = false) { - if (instance.Type == typeof(DateTimeOffset)) + // date_part exists only for timestamp without time zone, so if we pass in a timestamptz it gets converted to a local + // timestamp based on TimeZone. Convert to a timestamp without time zone at UTC to get the right values. + if (!TryConvertAwayFromTimestampTz(instance, out instance!)) { - // date_part exists only for timestamp without time zone, so if we pass in a timestamptz it gets converted to a local - // timestamp based on TimeZone. Convert to a timestamp without time zone at UTC to get the right values. - instance = _sqlExpressionFactory.AtUtc(instance); + return null; } var result = _sqlExpressionFactory.Function( @@ -196,10 +200,7 @@ private SqlExpression GetDatePartExpression( /// 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, - Type returnType) + public virtual 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 @@ -214,7 +215,9 @@ private SqlExpression GetDatePartExpression( // In PG, date_trunc over timestamptz looks at TimeZone, and returns timestamptz. .NET DateTimeOffset.Date just returns the // date part (no conversion), and returns an Unspecified DateTime. So we first convert the timestamptz argument to timestamp - // via AT TIME ZONE 'UTC" + // via AT TIME ZONE 'UTC". + // Note that we don't use the overload of date_trunc that accepts a timezone as its 3rd argument (like we do for DateTime.Date), + // since that returns a timestamptz, but DateTimeOffset.Date should return DateTime with Kind=Unspecified nameof(DateTimeOffset.Date) => _sqlExpressionFactory.Function( "date_trunc", @@ -222,8 +225,42 @@ private SqlExpression GetDatePartExpression( nullable: true, argumentsPropagateNullability: TrueArrays[2], typeof(DateTime), - _timestampTzMapping), + _timestampMapping), _ => 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). + // To avoid this, if we get a timestamptz, convert it to a timestamp without time zone (at UTC), which doesn't undergo any timezone + // conversions. + private bool TryConvertAwayFromTimestampTz(SqlExpression timestamp, [NotNullWhen(true)] out SqlExpression? result) + { + switch (timestamp) + { + // We're already dealing with a non-timestamptz mapping, no conversion needed. + case { TypeMapping: NpgsqlTimestampTypeMapping or NpgsqlDateTypeMapping or NpgsqlTimeTypeMapping }: + case { Type: var type } when type == typeof(DateOnly) || type == typeof(TimeOnly): + result = timestamp; + return true; + + // In these cases we know that the expression represents a timestamptz; it's safe to convert to a timestamp without time zone. + // Note that timestamptz AT TIME ZONE 'UTC' returns the same timestamp but as a timestamp (without time zone). + case { TypeMapping: NpgsqlTimestampTzTypeMapping }: + case { Type: var type } when type == typeof(DateTimeOffset): + result = _sqlExpressionFactory.AtUtc(timestamp); + return true; + + // If it's a DateTime who's type mapping isn't known (parameter), we cannot ensure that a timestamp without time zone + // is returned (note that applying AT TIME ZONE 'UTC' on a timestamp without time zone would yield a timestamptz, which would + // again undergo timestamp conversion) + case { Type: var type } when type == typeof(DateTime): + result = null; + return false; + + default: + throw new UnreachableException(); + } + } } diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs index bd958c243..0b64006a4 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlTest.cs @@ -128,14 +128,14 @@ public override async Task Where_datetimeoffset_date_component(bool async) await AssertQuery( async, ss => from m in ss.Set() - where m.Timeline.Date > new DateTime(1, DateTimeKind.Utc) + where m.Timeline.Date > new DateTime(1) select m); AssertSql( """ SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m -WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMPTZ '0001-01-01 00:00:00Z' +WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01 00:00:00' """); } @@ -213,6 +213,17 @@ public override Task DateTimeOffset_to_unix_time_milliseconds(bool async) public override Task DateTimeOffset_to_unix_time_seconds(bool async) => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_seconds(async)); + public override async Task Time_of_day_datetimeoffset(bool async) + { + await base.Time_of_day_datetimeoffset(async); + + AssertSql( +""" +SELECT CAST(m."Timeline" AT TIME ZONE 'UTC' AS time) +FROM "Missions" AS m +"""); + } + #endregion DateTimeOffset #region DateTime diff --git a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs index a16486c3a..5d04f2cc6 100644 --- a/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/PrimitiveCollectionsQueryNpgsqlTest.cs @@ -1040,7 +1040,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)::int <> 1 + 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 ORDER BY p."Id" NULLS FIRST """); @@ -1165,7 +1165,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)::int <> 1 + 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 LEFT JOIN LATERAL ( SELECT d0.value, d0.ordinality diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs index 8420ef9c6..a94b89848 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlTest.cs @@ -76,14 +76,14 @@ public override async Task Where_datetimeoffset_date_component(bool async) await AssertQuery( async, ss => from m in ss.Set() - where m.Timeline.Date > new DateTime(1, DateTimeKind.Utc) + where m.Timeline.Date > new DateTime(1) select m); AssertSql( """ SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m -WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMPTZ '0001-01-01 00:00:00Z' +WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01 00:00:00' """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs index a82933aa7..f7a42cb98 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlTest.cs @@ -80,14 +80,14 @@ public override async Task Where_datetimeoffset_date_component(bool async) await AssertQuery( async, ss => from m in ss.Set() - where m.Timeline.Date > new DateTime(1, DateTimeKind.Utc) + where m.Timeline.Date > new DateTime(1) select m); AssertSql( """ SELECT m."Id", m."CodeName", m."Date", m."Duration", m."Rating", m."Time", m."Timeline" FROM "Missions" AS m -WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMPTZ '0001-01-01 00:00:00Z' +WHERE date_trunc('day', m."Timeline" AT TIME ZONE 'UTC') > TIMESTAMP '0001-01-01 00:00:00' """); } diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index bab63eaa7..3f7afcd4e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -551,7 +551,7 @@ await AssertQuery( """ SELECT e."Id", e."TimestampDateTime", e."TimestampDateTimeArray", e."TimestampDateTimeOffset", e."TimestampDateTimeOffsetArray", e."TimestampDateTimeRange", e."TimestamptzDateTime", e."TimestamptzDateTimeArray", e."TimestamptzDateTimeRange" FROM "Entities" AS e -WHERE make_timestamptz(date_part('year', e."TimestamptzDateTime")::int, date_part('month', e."TimestamptzDateTime")::int, 1, 0, 0, 0::double precision, 'UTC') = TIMESTAMPTZ '1998-04-01 00:00:00Z' +WHERE make_timestamptz(date_part('year', e."TimestamptzDateTime" AT TIME ZONE 'UTC')::int, date_part('month', e."TimestamptzDateTime" AT TIME ZONE 'UTC')::int, 1, 0, 0, 0::double precision, 'UTC') = TIMESTAMPTZ '1998-04-01 00:00:00Z' """); } From 18d6a307b933d20c9b4bd17a6be344ed518db5c7 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 8 Oct 2023 17:48:14 +0200 Subject: [PATCH 04/10] Bump versions to Npgsql and EF rc2 --- Directory.Packages.props | 6 +++--- ...thwindAggregateOperatorsQueryNpgsqlTest.cs | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index e03ddcea0..74150e603 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,8 +1,8 @@ - 8.0.0-rc.2.23465.12 - 8.0.0-rc.2.23465.13 - 8.0.0-preview.4 + 8.0.0-rc.2.23480.1 + 8.0.0-rc.2.23479.6 + 8.0.0-rc.2 diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs index 5d62219b8..a07cce585 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs @@ -76,6 +76,26 @@ public override Task Contains_with_local_anonymous_type_array_closure(bool async public override Task Contains_with_local_tuple_array_closure(bool async) => Assert.ThrowsAsync(() => base.Contains_with_local_tuple_array_closure(async: true)); + public override async Task Contains_with_local_enumerable_inline(bool async) + { + // Issue #31776 + await Assert.ThrowsAsync( + async () => + await base.Contains_with_local_enumerable_inline(async)); + + AssertSql(); + } + + 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)); + + AssertSql(); + } + public override async Task Contains_with_local_non_primitive_list_closure_mix(bool async) { await base.Contains_with_local_non_primitive_list_closure_mix(async); From 6faf60695ced192f89a9d1795cb8592b2883b777 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Mon, 25 Sep 2023 01:49:47 +0200 Subject: [PATCH 05/10] React to exception type changes in Npgsql --- .../Mapping/NpgsqlTimestampTypeMapping.cs | 2 +- .../Mapping/NpgsqlTimestampTzTypeMapping.cs | 6 +++--- ...rthwindAggregateOperatorsQueryNpgsqlTest.cs | 2 +- .../Query/TimestampQueryTest.cs | 18 +++++++++--------- .../Storage/NpgsqlTypeMappingTest.cs | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs index 5f5d69a34..109e5af11 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTypeMapping.cs @@ -82,6 +82,6 @@ private string GenerateLiteralCore(object value) return NpgsqlTypeMappingSource.LegacyTimestampBehavior || dateTime.Kind != DateTimeKind.Utc ? dateTime.ToString("yyyy-MM-dd HH:mm:ss.FFFFFF", CultureInfo.InvariantCulture) - : throw new InvalidCastException("'timestamp without time zone' literal cannot be generated for a UTC DateTime"); + : throw new ArgumentException("'timestamp without time zone' literal cannot be generated for a UTC DateTime"); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs index d4fdd62e6..1181bf997 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlTimestampTzTypeMapping.cs @@ -88,15 +88,15 @@ private string GenerateLiteralCore(object value) DateTimeKind.Unspecified => NpgsqlTypeMappingSource.LegacyTimestampBehavior || dateTime == default ? dateTime.ToString("yyyy-MM-dd HH:mm:ss.FFFFFF", CultureInfo.InvariantCulture) + 'Z' - : throw new InvalidCastException( + : throw new ArgumentException( $"'timestamp with time zone' literal cannot be generated for {dateTime.Kind} DateTime: a UTC DateTime is required"), DateTimeKind.Local => NpgsqlTypeMappingSource.LegacyTimestampBehavior ? dateTime.ToString("yyyy-MM-dd HH:mm:ss.FFFFFFzzz", CultureInfo.InvariantCulture) - : throw new InvalidCastException( + : throw new ArgumentException( $"'timestamp with time zone' literal cannot be generated for {dateTime.Kind} DateTime: a UTC DateTime is required"), - _ => throw new ArgumentOutOfRangeException() + _ => throw new UnreachableException() }; case DateTimeOffset dateTimeOffset: diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs index a07cce585..b53250c2a 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindAggregateOperatorsQueryNpgsqlTest.cs @@ -74,7 +74,7 @@ public override Task Contains_with_local_anonymous_type_array_closure(bool async => AssertTranslationFailed(() => base.Contains_with_local_anonymous_type_array_closure(async)); public override Task Contains_with_local_tuple_array_closure(bool async) - => Assert.ThrowsAsync(() => base.Contains_with_local_tuple_array_closure(async: true)); + => Assert.ThrowsAsync(() => base.Contains_with_local_tuple_array_closure(async: true)); public override async Task Contains_with_local_enumerable_inline(bool async) { diff --git a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs index 3f7afcd4e..c1c272b3e 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TimestampQueryTest.cs @@ -60,7 +60,7 @@ public async Task Cannot_insert_utc_datetime_into_timestamp() ctx.Entities.Add(new() { TimestampDateTime = DateTime.UtcNow }); var exception = await Assert.ThrowsAsync(() => ctx.SaveChangesAsync()); - Assert.IsType(exception.InnerException); + Assert.IsType(exception.InnerException); } [ConditionalFact] @@ -70,7 +70,7 @@ public async Task Cannot_insert_unspecified_datetime_into_timestamptz() ctx.Entities.Add(new() { TimestamptzDateTime = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified) }); var exception = await Assert.ThrowsAsync(() => ctx.SaveChangesAsync()); - Assert.IsType(exception.InnerException); + Assert.IsType(exception.InnerException); } [ConditionalFact] @@ -80,7 +80,7 @@ public async Task Cannot_insert_local_datetime_into_timestamptz() ctx.Entities.Add(new() { TimestamptzDateTime = DateTime.Now }); var exception = await Assert.ThrowsAsync(() => ctx.SaveChangesAsync()); - Assert.IsType(exception.InnerException); + Assert.IsType(exception.InnerException); } #endregion Basic mapping @@ -154,7 +154,7 @@ public async Task Cannot_compare_timestamp_column_to_utc_DateTime_literal() { await using var ctx = CreateContext(); - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( () => ctx.Entities.Where(e => e.TimestampDateTime == new DateTime(1998, 4, 12, 13, 26, 38, DateTimeKind.Utc)) .ToListAsync()); } @@ -166,7 +166,7 @@ public async Task Cannot_compare_timestamp_column_to_utc_DateTime_parameter() var dateTime = new DateTime(1998, 4, 12, 13, 26, 38, DateTimeKind.Utc); - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( () => ctx.Entities.Where(e => e.TimestampDateTime == dateTime) .ToListAsync()); } @@ -214,7 +214,7 @@ public async Task Cannot_compare_timestamptz_column_to_local_DateTime_literal() { await using var ctx = CreateContext(); - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( () => ctx.Entities.Where(e => e.TimestamptzDateTime == new DateTime(1998, 4, 12, 13, 26, 38, DateTimeKind.Local)) .ToListAsync()); } @@ -226,7 +226,7 @@ public async Task Cannot_compare_timestamptz_column_to_local_DateTime_parameter( var dateTime = new DateTime(1998, 4, 12, 13, 26, 38, DateTimeKind.Local); - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( () => ctx.Entities.Where(e => e.TimestamptzDateTime == dateTime) .ToListAsync()); } @@ -236,7 +236,7 @@ public async Task Cannot_compare_timestamptz_column_to_unspecified_DateTime_lite { await using var ctx = CreateContext(); - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( () => ctx.Entities.Where(e => e.TimestamptzDateTime == new DateTime(1998, 4, 12, 13, 26, 38, DateTimeKind.Unspecified)) .ToListAsync()); } @@ -248,7 +248,7 @@ public async Task Cannot_compare_timestamptz_column_to_unspecified_DateTime_para var dateTime = new DateTime(1998, 4, 12, 13, 26, 38, DateTimeKind.Unspecified); - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( () => ctx.Entities.Where(e => e.TimestamptzDateTime == dateTime) .ToListAsync()); } diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs index a62b2e4e4..5f36d907d 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs @@ -113,7 +113,7 @@ public void GenerateSqlLiteral_returns_timestamp_infinity_literals() [Fact] public void GenerateSqlLiteral_timestamp_does_not_support_utc_datetime() - => Assert.Throws(() => GetMapping("timestamp without time zone").GenerateSqlLiteral(DateTime.UtcNow)); + => Assert.Throws(() => GetMapping("timestamp without time zone").GenerateSqlLiteral(DateTime.UtcNow)); [Fact] public void GenerateSqlLiteral_timestamp_does_not_support_datetimeoffset() @@ -164,11 +164,11 @@ public void GenerateSqlLiteral_returns_timestamptz_infinity_literals() [Fact] public void GenerateSqlLiteral_timestamptz_does_not_support_local_datetime() - => Assert.Throws(() => GetMapping("timestamp with time zone").GenerateSqlLiteral(DateTime.Now)); + => Assert.Throws(() => GetMapping("timestamp with time zone").GenerateSqlLiteral(DateTime.Now)); [Fact] public void GenerateSqlLiteral_timestamptz_does_not_support_unspecified_datetime() - => Assert.Throws( + => Assert.Throws( () => GetMapping("timestamp with time zone") .GenerateSqlLiteral(DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified))); From 4db3c3ca4846cc23814ee5b31a86f46916df78c7 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 13 Sep 2023 14:08:44 +0200 Subject: [PATCH 06/10] Align enum mappings to the new Npgsql hacky way --- .../Internal/NpgsqlTypeMappingSource.cs | 54 ++++++++++--------- .../BuiltInDataTypesNpgsqlTest.cs | 9 ++-- .../Query/EnumQueryTest.cs | 39 +++++--------- 3 files changed, 48 insertions(+), 54 deletions(-) diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index ffd115729..aa35bb5c9 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -11,7 +11,7 @@ using System.Text.Json; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; -using Npgsql.Internal.TypeMapping; +using Npgsql.Internal; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; @@ -373,38 +373,40 @@ public virtual void LoadUserDefinedTypeMappings( /// protected virtual void SetupEnumMappings(ISqlGenerationHelper sqlGenerationHelper, NpgsqlDataSource? dataSource) { - var adoEnumMappings = new List(); + List? adoEnumMappings = null; -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - if (NpgsqlConnection.GlobalTypeMapper.GetType().GetProperty("UserTypeMappings")?.GetMethod is { } globalTypeMappingsMethodInfo - && globalTypeMappingsMethodInfo.Invoke(NpgsqlConnection.GlobalTypeMapper, Array.Empty()) is - IDictionary globalUserMappings) - { - adoEnumMappings.AddRange(globalUserMappings.Values.OfType()); - } -#pragma warning restore CS0618 - - // TODO: Think about what to do here. We could just require users to do the mapping at the EF level, and then EF would take care - // of the ADO mapping. if (dataSource is not null - && typeof(NpgsqlDataSource).GetField("_userTypeMappings", BindingFlags.NonPublic | BindingFlags.Instance) is + && typeof(NpgsqlDataSource).GetField("_hackyEnumTypeMappings", BindingFlags.NonPublic | BindingFlags.Instance) is { } dataSourceTypeMappingsFieldInfo - && dataSourceTypeMappingsFieldInfo.GetValue(dataSource) is IDictionary dataSourceUserMappings) + && dataSourceTypeMappingsFieldInfo.GetValue(dataSource) is List dataSourceEnumMappings) { - adoEnumMappings.AddRange(dataSourceUserMappings.Values.OfType()); + // 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 - foreach (var adoUserTypeMapping in adoEnumMappings) + if (adoEnumMappings is not null) { - // TODO: update with schema per https://github.com/npgsql/npgsql/issues/2121 - var components = adoUserTypeMapping.PgTypeName.Split('.'); - var schema = components.Length > 1 ? components.First() : null; - var name = components.Length > 1 ? string.Join(null, components.Skip(1)) : adoUserTypeMapping.PgTypeName; - - var mapping = new NpgsqlEnumTypeMapping( - name, schema, adoUserTypeMapping.ClrType, sqlGenerationHelper, adoUserTypeMapping.NameTranslator); - ClrTypeMappings[adoUserTypeMapping.ClrType] = mapping; - StoreTypeMappings[mapping.StoreType] = new RelationalTypeMapping[] { mapping }; + 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( + name, schema, adoEnumMapping.EnumClrType, sqlGenerationHelper, adoEnumMapping.NameTranslator); + ClrTypeMappings[adoEnumMapping.EnumClrType] = mapping; + StoreTypeMappings[mapping.StoreType] = new RelationalTypeMapping[] { mapping }; + } } } diff --git a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs index 4117d3040..bbd5319f3 100644 --- a/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/BuiltInDataTypesNpgsqlTest.cs @@ -973,14 +973,17 @@ 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 + } protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); // TODO: Switch to using data source -#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete - NpgsqlConnection.GlobalTypeMapper.MapEnum(); -#pragma warning restore CS0618 ((NpgsqlTypeMappingSource)context.GetService()).LoadUserDefinedTypeMappings( context.GetService(), dataSource: null); diff --git a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs index 3dbaf7c17..640370974 100644 --- a/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/EnumQueryTest.cs @@ -231,16 +231,6 @@ public class EnumContext : PoolableDbContext // ReSharper disable once UnusedAutoPropertyAccessor.Global public DbSet SomeEntities { get; set; } - static EnumContext() - { -#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 - } - public EnumContext(DbContextOptions options) : base(options) {} protected override void OnModelCreating(ModelBuilder builder) @@ -253,9 +243,6 @@ protected override void OnModelCreating(ModelBuilder builder) public static void Seed(EnumContext context) { context.AddRange(EnumData.CreateSomeEnumEntities()); - - context.SomeEntities.AddRange( - ); context.SaveChanges(); } } @@ -316,6 +303,16 @@ public class EnumFixture : SharedStoreFixtureBase, IQueryFixtureBas protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; + static EnumFixture() + { +#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 + } + private EnumData _expectedData; protected override void Seed(EnumContext context) => EnumContext.Seed(context); @@ -358,21 +355,13 @@ public IReadOnlyDictionary EntityAsserters protected class EnumData : ISetSource { - public IReadOnlyList SomeEnumEntities { get; } - - public EnumData() - => SomeEnumEntities = CreateSomeEnumEntities(); + public IReadOnlyList SomeEnumEntities { get; } = CreateSomeEnumEntities(); public IQueryable Set() where TEntity : class - { - if (typeof(TEntity) == typeof(SomeEnumEntity)) - { - return (IQueryable)SomeEnumEntities.AsQueryable(); - } - - throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)); - } + => typeof(TEntity) == typeof(SomeEnumEntity) + ? (IQueryable)SomeEnumEntities.AsQueryable() + : throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)); public static IReadOnlyList CreateSomeEnumEntities() => new List From 1ea3c0bd940f352dbaa8bee1b92f289b51c2dfb4 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 28 Sep 2023 00:27:56 +0200 Subject: [PATCH 07/10] Make user-defined ranges work again They now require DataTypeName to be explicitly specified on NpgsqlParameter, instead of NpgsqlDbType. --- .../NpgsqlNodaTimeTypeMappingSourcePlugin.cs | 24 ++-- .../Mapping/NpgsqlRangeTypeMapping.cs | 125 ++++++++++-------- .../Internal/NpgsqlTypeMappingSource.cs | 34 +++-- 3 files changed, 106 insertions(+), 77 deletions(-) diff --git a/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs b/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs index 68caddc01..c3fe035bf 100644 --- a/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs +++ b/src/EFCore.PG.NodaTime/Storage/Internal/NpgsqlNodaTimeTypeMappingSourcePlugin.cs @@ -77,18 +77,18 @@ static NpgsqlNodaTimeTypeMappingSourcePlugin() /// public NpgsqlNodaTimeTypeMappingSourcePlugin(ISqlGenerationHelper sqlGenerationHelper) { - _timestampLocalDateTimeRange - = new NpgsqlRangeTypeMapping("tsrange", typeof(NpgsqlRange), _timestampLocalDateTime, sqlGenerationHelper); - _legacyTimestampInstantRange - = new NpgsqlRangeTypeMapping("tsrange", typeof(NpgsqlRange), _legacyTimestampInstant, sqlGenerationHelper); - _timestamptzInstantRange - = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange), _timestamptzInstant, sqlGenerationHelper); - _timestamptzZonedDateTimeRange - = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange), _timestamptzZonedDateTime, sqlGenerationHelper); - _timestamptzOffsetDateTimeRange - = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange), _timestamptzOffsetDateTime, sqlGenerationHelper); - _dateRange - = new NpgsqlRangeTypeMapping("daterange", typeof(NpgsqlRange), _date, sqlGenerationHelper); + _timestampLocalDateTimeRange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "tsrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampRange, _timestampLocalDateTime); + _legacyTimestampInstantRange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "tsrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampRange, _legacyTimestampInstant); + _timestamptzInstantRange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "tstzrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampTzRange, _timestamptzInstant); + _timestamptzZonedDateTimeRange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "tstzrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampTzRange, _timestamptzZonedDateTime); + _timestamptzOffsetDateTimeRange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "tstzrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampTzRange, _timestamptzOffsetDateTime); + _dateRange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "daterange", typeof(NpgsqlRange), NpgsqlDbType.DateRange, _date); var storeTypeMappings = new Dictionary(StringComparer.OrdinalIgnoreCase) { diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs index 053019c4d..007996d37 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; using System.Text; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; @@ -11,8 +12,6 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; /// public class NpgsqlRangeTypeMapping : NpgsqlTypeMapping { - private readonly ISqlGenerationHelper _sqlGenerationHelper; - private PropertyInfo? _isEmptyProperty; private PropertyInfo? _lowerProperty; private PropertyInfo? _upperProperty; @@ -32,40 +31,51 @@ public class NpgsqlRangeTypeMapping : NpgsqlTypeMapping public virtual RelationalTypeMapping SubtypeMapping { get; } /// - /// Constructs an instance of the class. + /// For user-defined ranges, we have no and so the PG type name is set on + /// instead. /// - /// The database type to map - /// The CLR type to map. + private string? PgDataTypeName { get; init; } + + /// + /// Constructs an instance of the class for a built-in range type which has a + /// defined. + /// + /// The database type to map + /// The CLR type to map. + /// The of the built-in range. /// The type mapping for the range subtype. - /// The SQL generation helper to delimit the store name. - public NpgsqlRangeTypeMapping( - string storeType, - Type clrType, - RelationalTypeMapping subtypeMapping, - ISqlGenerationHelper sqlGenerationHelper) - : this(storeType, storeTypeSchema: null, clrType, subtypeMapping, sqlGenerationHelper) {} + public static NpgsqlRangeTypeMapping CreatBuiltInRangeMapping( + string rangeStoreType, + Type rangeClrType, + NpgsqlDbType rangeNpgsqlDbType, + RelationalTypeMapping subtypeMapping) + => new(rangeStoreType, rangeClrType, rangeNpgsqlDbType, subtypeMapping); /// - /// Constructs an instance of the class. + /// Constructs an instance of the class for a user-defined range type which doesn't have a + /// defined. /// - /// The database type to map - /// The schema of the type. - /// The CLR type to map. + /// The database type to map, quoted. + /// The database type to map, unquoted. + /// The CLR type to map. /// The type mapping for the range subtype. - /// The SQL generation helper to delimit the store name. - public NpgsqlRangeTypeMapping( - string storeType, - string? storeTypeSchema, - Type clrType, - RelationalTypeMapping subtypeMapping, - ISqlGenerationHelper sqlGenerationHelper) - : base(sqlGenerationHelper.DelimitIdentifier(storeType, storeTypeSchema), clrType, GenerateNpgsqlDbType(subtypeMapping)) - { - Debug.Assert(clrType == typeof(NpgsqlRange<>).MakeGenericType(subtypeMapping.ClrType)); + public static NpgsqlRangeTypeMapping CreatUserDefinedRangeMapping( + string quotedRangeStoreType, + string unquotedRangeStoreType, + Type rangeClrType, + RelationalTypeMapping subtypeMapping) + => new(quotedRangeStoreType, rangeClrType, rangeNpgsqlDbType: NpgsqlDbType.Unknown, subtypeMapping) + { + PgDataTypeName = unquotedRangeStoreType + }; - SubtypeMapping = subtypeMapping; - _sqlGenerationHelper = sqlGenerationHelper; - } + private NpgsqlRangeTypeMapping( + string rangeStoreType, + Type rangeClrType, + NpgsqlDbType rangeNpgsqlDbType, + RelationalTypeMapping subtypeMapping) + : base(rangeStoreType, rangeClrType, rangeNpgsqlDbType) + => SubtypeMapping = subtypeMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -76,13 +86,9 @@ public NpgsqlRangeTypeMapping( protected NpgsqlRangeTypeMapping( RelationalTypeMappingParameters parameters, NpgsqlDbType npgsqlDbType, - RelationalTypeMapping subtypeMapping, - ISqlGenerationHelper sqlGenerationHelper) + RelationalTypeMapping subtypeMapping) : base(parameters, npgsqlDbType) - { - SubtypeMapping = subtypeMapping; - _sqlGenerationHelper = sqlGenerationHelper; - } + => SubtypeMapping = subtypeMapping; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -91,7 +97,33 @@ protected NpgsqlRangeTypeMapping( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) - => new NpgsqlRangeTypeMapping(parameters, NpgsqlDbType, SubtypeMapping, _sqlGenerationHelper); + => new NpgsqlRangeTypeMapping(parameters, NpgsqlDbType, SubtypeMapping); + + /// + /// 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 void ConfigureParameter(DbParameter parameter) + { + // Built-in range types have an NpgsqlDbType, so we just do the normal thing. + if (PgDataTypeName is null) + { + Check.DebugAssert(NpgsqlDbType is not NpgsqlDbType.Unknown, "NpgsqlDbType is Unknown but no PgDataTypeName is configured"); + base.ConfigureParameter(parameter); + return; + } + + Check.DebugAssert(NpgsqlDbType is NpgsqlDbType.Unknown, "PgDataTypeName is non-null, but NpgsqlDbType is " + NpgsqlDbType); + + 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 = PgDataTypeName; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -140,25 +172,6 @@ protected override string GenerateEmbeddedNonNullSqlLiteral(object value) return builder.ToString(); } - private static NpgsqlDbType GenerateNpgsqlDbType(RelationalTypeMapping subtypeMapping) - { - NpgsqlDbType subtypeNpgsqlDbType; - if (subtypeMapping is INpgsqlTypeMapping npgsqlTypeMapping) - { - subtypeNpgsqlDbType = npgsqlTypeMapping.NpgsqlDbType; - } - else - { - // We're using a built-in, non-Npgsql mapping such as IntTypeMapping. - // Infer the NpgsqlDbType from the DbType (somewhat hacky but why not). - Debug.Assert(subtypeMapping.DbType.HasValue); - var p = new NpgsqlParameter { DbType = subtypeMapping.DbType.Value }; - subtypeNpgsqlDbType = p.NpgsqlDbType; - } - - return NpgsqlDbType.Range | subtypeNpgsqlDbType; - } - /// /// 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/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index aa35bb5c9..3c19ca620 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -191,14 +191,21 @@ public NpgsqlTypeMappingSource( _sqlGenerationHelper = Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper)); - // Initialize some mappings which depend on other mappings - _int4range = new NpgsqlRangeTypeMapping("int4range", typeof(NpgsqlRange), _int4, sqlGenerationHelper); - _int8range = new NpgsqlRangeTypeMapping("int8range", typeof(NpgsqlRange), _int8, sqlGenerationHelper); - _numrange = new NpgsqlRangeTypeMapping("numrange", typeof(NpgsqlRange), _numeric, sqlGenerationHelper); - _tsrange = new NpgsqlRangeTypeMapping("tsrange", typeof(NpgsqlRange), _timestamp, sqlGenerationHelper); - _tstzrange = new NpgsqlRangeTypeMapping("tstzrange", typeof(NpgsqlRange), _timestamptz, sqlGenerationHelper); - _dateOnlyDaterange = new NpgsqlRangeTypeMapping("daterange", typeof(NpgsqlRange), _dateDateOnly, sqlGenerationHelper); - _dateTimeDaterange = new NpgsqlRangeTypeMapping("daterange", typeof(NpgsqlRange), _dateDateTime, sqlGenerationHelper); + // Initialize range mappings, which reference on other mappings + _int4range = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "int4range", typeof(NpgsqlRange), NpgsqlDbType.IntegerRange, _int4); + _int8range = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "int8range", typeof(NpgsqlRange), NpgsqlDbType.BigIntRange, _int8); + _numrange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "numrange", typeof(NpgsqlRange), NpgsqlDbType.NumericRange, _numeric); + _tsrange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "tsrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampRange, _timestamp); + _tstzrange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "tstzrange", typeof(NpgsqlRange), NpgsqlDbType.TimestampTzRange, _timestamptz); + _dateOnlyDaterange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "daterange", typeof(NpgsqlRange), NpgsqlDbType.DateRange, _dateDateOnly); + _dateTimeDaterange = NpgsqlRangeTypeMapping.CreatBuiltInRangeMapping( + "daterange", typeof(NpgsqlRange), NpgsqlDbType.DateRange, _dateDateTime); // ReSharper disable CoVariantArrayConversion // Note that PostgreSQL has aliases to some built-in type name aliases (e.g. int4 for integer), @@ -837,7 +844,16 @@ static bool IsMultirange(string multiRangeStoreType, [NotNullWhen(true)] out str throw new Exception($"Could not map range {rangeDefinition.RangeName}, no mapping was found its subtype"); } - return new NpgsqlRangeTypeMapping(rangeDefinition.RangeName, rangeDefinition.SchemaName, rangeClrType, subtypeMapping, _sqlGenerationHelper); + // 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; + + return NpgsqlRangeTypeMapping.CreatUserDefinedRangeMapping( + quotedRangeStoreType, unquotedRangeStoreType, rangeClrType, subtypeMapping); } /// From bd0537a091a15c47a64ca7ac5f33f0129ff901e0 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 8 Oct 2023 16:09:14 +0200 Subject: [PATCH 08/10] Add necessary new global Npgsql opt-ins Which aren't NativeAOT/trimming-compatible --- .../Query/GearsOfWarQueryNpgsqlFixture.cs | 8 ++++++++ .../EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs | 8 ++++++++ .../Query/NorthwindQueryNpgsqlFixture.cs | 9 +++++++++ .../Query/RangeQueryNpgsqlTest.cs | 8 ++++++++ .../Query/TPCGearsOfWarQueryNpgsqlFixture.cs | 8 ++++++++ .../Query/TPTGearsOfWarQueryNpgsqlFixture.cs | 8 ++++++++ 6 files changed, 49 insertions(+) diff --git a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs index 596e30919..595e3dc9d 100644 --- a/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/GearsOfWarQueryNpgsqlFixture.cs @@ -10,6 +10,14 @@ public class GearsOfWarQueryNpgsqlFixture : GearsOfWarQueryRelationalFixture private GearsOfWarData _expectedData; + static GearsOfWarQueryNpgsqlFixture() + { + // TODO: Switch to using NpgsqlDataSource +#pragma warning disable CS0618 // Type or member is obsolete + NpgsqlConnection.GlobalTypeMapper.EnableRecordsAsTuples(); +#pragma warning restore CS0618 // Type or member is obsolete + } + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); diff --git a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs index b214c09ee..2d66ed633 100644 --- a/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/JsonPocoQueryTest.cs @@ -819,6 +819,14 @@ public class JsonEntity public class JsonPocoQueryFixture : SharedStoreFixtureBase { + static JsonPocoQueryFixture() + { + // TODO: Switch to using NpgsqlDataSource +#pragma warning disable CS0618 // Type or member is obsolete + NpgsqlConnection.GlobalTypeMapper.EnableDynamicJsonMappings(); +#pragma warning restore CS0618 // Type or member is obsolete + } + protected override string StoreName => "JsonPocoQueryTest"; protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs index da3465a61..c74ed4633 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NorthwindQueryNpgsqlFixture.cs @@ -11,6 +11,15 @@ public class NorthwindQueryNpgsqlFixture : NorthwindQueryRelat protected override ITestStoreFactory TestStoreFactory => NpgsqlNorthwindTestStoreFactory.Instance; protected override Type ContextType => typeof(NorthwindNpgsqlContext); + static NorthwindQueryNpgsqlFixture() + { + // TODO: Switch to using NpgsqlDataSource +#pragma warning disable CS0618 // Type or member is obsolete + NpgsqlConnection.GlobalTypeMapper.EnableDynamicJsonMappings(); + NpgsqlConnection.GlobalTypeMapper.EnableRecordsAsTuples(); +#pragma warning restore CS0618 // Type or member is obsolete + } + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) { var optionsBuilder = base.AddOptions(builder); diff --git a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs index 7b2c9ccb3..7389c4293 100644 --- a/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/RangeQueryNpgsqlTest.cs @@ -609,6 +609,14 @@ LIMIT 2 public class RangeQueryNpgsqlFixture : SharedStoreFixtureBase { + static RangeQueryNpgsqlFixture() + { + // TODO: Switch to using NpgsqlDataSource +#pragma warning disable CS0618 // Type or member is obsolete + NpgsqlConnection.GlobalTypeMapper.EnableUnmappedTypes(); +#pragma warning restore CS0618 // Type or member is obsolete + } + protected override string StoreName => "RangeQueryTest"; protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs index bac8bfc9d..1975eb5e8 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPCGearsOfWarQueryNpgsqlFixture.cs @@ -5,6 +5,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; public class TPCGearsOfWarQueryNpgsqlFixture : TPCGearsOfWarQueryRelationalFixture { + static TPCGearsOfWarQueryNpgsqlFixture() + { + // TODO: Switch to using NpgsqlDataSource +#pragma warning disable CS0618 // Type or member is obsolete + NpgsqlConnection.GlobalTypeMapper.EnableRecordsAsTuples(); +#pragma warning restore CS0618 // Type or member is obsolete + } + protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; diff --git a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs index 2416eca8a..1b968527b 100644 --- a/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs +++ b/test/EFCore.PG.FunctionalTests/Query/TPTGearsOfWarQueryNpgsqlFixture.cs @@ -5,6 +5,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; public class TPTGearsOfWarQueryNpgsqlFixture : TPTGearsOfWarQueryRelationalFixture { + static TPTGearsOfWarQueryNpgsqlFixture() + { + // TODO: Switch to using NpgsqlDataSource +#pragma warning disable CS0618 // Type or member is obsolete + NpgsqlConnection.GlobalTypeMapper.EnableRecordsAsTuples(); +#pragma warning restore CS0618 // Type or member is obsolete + } + protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) From bf80079eca3f00419545f0562069097b0990af91 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 24 Sep 2023 21:28:33 +0200 Subject: [PATCH 09/10] React to network type changes in Npgsql --- .../NpgsqlNetworkDbFunctionsExtensions.cs | 872 ++++++++++-------- .../Internal/NpgsqlNetworkTranslator.cs | 207 ++++- .../NpgsqlSqlTranslatingExpressionVisitor.cs | 10 + .../Query/NpgsqlSqlExpressionFactory.cs | 112 ++- .../Mapping/NpgsqlNetworkTypeMappings.cs | 39 +- .../Internal/NpgsqlTypeMappingSource.cs | 10 +- .../Query/NetworkQueryNpgsqlTest.cs | 321 +++---- .../Storage/NpgsqlTypeMappingTest.cs | 6 +- 8 files changed, 885 insertions(+), 692 deletions(-) diff --git a/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlNetworkDbFunctionsExtensions.cs b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlNetworkDbFunctionsExtensions.cs index 74d98cb77..e84ae6446 100644 --- a/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlNetworkDbFunctionsExtensions.cs +++ b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlNetworkDbFunctionsExtensions.cs @@ -15,33 +15,18 @@ public static class NpgsqlNetworkDbFunctionsExtensions #region RelationalOperators /// - /// Determines whether an is less than another . + /// Determines whether an is less than another . /// /// The instance. /// The left-hand inet. /// The right-hand inet. /// - /// True if the is less than the other ; otherwise, false. + /// True if the is less than the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool LessThan(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(LessThan))); - - /// - /// Determines whether an (IPAddress Address, int Subnet) is less than another (IPAddress Address, int Subnet). - /// - /// The instance. - /// The left-hand cidr. - /// The right-hand cidr. - /// - /// True if the (IPAddress Address, int Subnet) is less than the other (IPAddress Address, int Subnet); otherwise, false. - /// - /// - /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. - /// - public static bool LessThan(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + public static bool LessThan(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(LessThan))); /// @@ -60,33 +45,18 @@ public static bool LessThan(this DbFunctions _, PhysicalAddress macaddr, Physica => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(LessThan))); /// - /// Determines whether an is less than or equal to another . + /// Determines whether an is less than or equal to another . /// /// The instance. /// The left-hand inet. /// The right-hand inet. /// - /// True if the is less than or equal to the other ; otherwise, false. - /// - /// - /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. - /// - public static bool LessThanOrEqual(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(LessThanOrEqual))); - - /// - /// Determines whether an (IPAddress Address, int Subnet) is less than or equal to another (IPAddress Address, int Subnet). - /// - /// The instance. - /// The left-hand cidr. - /// The right-hand cidr. - /// - /// True if the (IPAddress Address, int Subnet) is less than or equal to the other (IPAddress Address, int Subnet); otherwise, false. + /// True if the is less than or equal to the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool LessThanOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + public static bool LessThanOrEqual(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(LessThanOrEqual))); /// @@ -105,33 +75,18 @@ public static bool LessThanOrEqual(this DbFunctions _, PhysicalAddress macaddr, => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(LessThanOrEqual))); /// - /// Determines whether an is greater than or equal to another . + /// Determines whether an is greater than or equal to another . /// /// The instance. /// The left-hand inet. /// The right-hand inet. /// - /// True if the is greater than or equal to the other ; otherwise, false. - /// - /// - /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. - /// - public static bool GreaterThanOrEqual(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(GreaterThanOrEqual))); - - /// - /// Determines whether an (IPAddress Address, int Subnet) is greater than or equal to another (IPAddress Address, int Subnet). - /// - /// The instance. - /// The left-hand cidr. - /// The right-hand cidr. - /// - /// True if the (IPAddress Address, int Subnet) is greater than or equal to the other (IPAddress Address, int Subnet); otherwise, false. + /// True if the is greater than or equal to the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool GreaterThanOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + public static bool GreaterThanOrEqual(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(GreaterThanOrEqual))); /// @@ -150,33 +105,18 @@ public static bool GreaterThanOrEqual(this DbFunctions _, PhysicalAddress macadd => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(GreaterThanOrEqual))); /// - /// Determines whether an is greater than another . + /// Determines whether an is greater than another . /// /// The instance. /// The left-hand inet. /// The right-hand inet. /// - /// True if the is greater than the other ; otherwise, false. - /// - /// - /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. - /// - public static bool GreaterThan(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(GreaterThan))); - - /// - /// Determines whether an (IPAddress Address, int Subnet) is greater than another (IPAddress Address, int Subnet). - /// - /// The instance. - /// The left-hand cidr. - /// The right-hand cidr. - /// - /// True if the (IPAddress Address, int Subnet) is greater than the other (IPAddress Address, int Subnet); otherwise, false. + /// True if the is greater than the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool GreaterThan(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + public static bool GreaterThan(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(GreaterThan))); /// @@ -199,854 +139,980 @@ public static bool GreaterThan(this DbFunctions _, PhysicalAddress macaddr, Phys #region ContainmentOperators /// - /// Determines whether an is contained within another . + /// Determines whether an is contained within another . /// /// The instance. /// The inet to locate. /// The inet to search. /// - /// True if the is contained within the other ; otherwise, false. + /// True if the is contained within the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainedBy(this DbFunctions _, IPAddress inet, IPAddress other) + public static bool ContainedBy(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedBy))); /// - /// Determines whether an is contained within a network. + /// Determines whether an is contained within or equal to another . /// /// The instance. /// The inet to locate. - /// The cidr to search. + /// The inet to search. /// - /// True if the is contained within the network; otherwise, false. + /// True if the is contained within or equal to the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainedBy(this DbFunctions _, IPAddress inet, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedBy))); + public static bool ContainedByOrEqual(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedByOrEqual))); /// - /// Determines whether an (IPAddress Address, int Subnet) is contained within another (IPAddress Address, int Subnet). + /// Determines whether an contains another . /// /// The instance. - /// The cidr to locate. - /// The cidr to search. + /// The IP address to search. + /// The IP address to locate. /// - /// True if the (IPAddress Address, int Subnet) is contained within the other (IPAddress Address, int Subnet); otherwise, false. + /// True if the contains the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainedBy(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedBy))); + public static bool Contains(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains))); /// - /// Determines whether an is contained within or equal to another . + /// Determines whether an contains or is equal to another . /// /// The instance. - /// The inet to locate. - /// The inet to search. + /// The IP address to search. + /// The IP address to locate. /// - /// True if the is contained within or equal to the other ; otherwise, false. + /// True if the contains or is equal to the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainedByOrEqual(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedByOrEqual))); + public static bool ContainsOrEqual(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrEqual))); /// - /// Determines whether an is contained within or equal to a network. + /// Determines whether an contains or is contained by another . /// /// The instance. - /// The inet to locate. - /// The cidr to search. + /// The IP address to search. + /// The IP address to locate. /// - /// True if the is contained within or equal to the network; otherwise, false. + /// True if the contains or is contained by the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainedByOrEqual(this DbFunctions _, IPAddress inet, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedByOrEqual))); + public static bool ContainsOrContainedBy(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrContainedBy))); + + #endregion + + #region BitwiseOperators /// - /// Determines whether an (IPAddress Address, int Subnet) is contained within or equal to another (IPAddress Address, int Subnet). + /// Computes the bitwise NOT operation on an . /// /// The instance. - /// The cidr to locate. - /// The cidr to search. + /// The inet to negate. /// - /// True if the (IPAddress Address, int Subnet) is contained within or equal to the other (IPAddress Address, int Subnet); otherwise, false. + /// The result of the bitwise NOT operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainedByOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedByOrEqual))); + public static NpgsqlInet BitwiseNot(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseNot))); /// - /// Determines whether an contains another . + /// Computes the bitwise NOT operation on an . /// /// The instance. - /// The IP address to search. - /// The IP address to locate. + /// The macaddr to negate. /// - /// True if the contains the other ; otherwise, false. + /// The result of the bitwise NOT operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool Contains(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains))); + public static PhysicalAddress BitwiseNot(this DbFunctions _, PhysicalAddress macaddr) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseNot))); /// - /// Determines whether a network contains another . + /// Computes the bitwise AND of two instances. /// /// The instance. - /// The network to search. - /// The IP address to locate. + /// The left-hand inet. + /// The right-hand inet. /// - /// True if the network contains the other ; otherwise, false. + /// The result of the bitwise AND operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool Contains( - this DbFunctions _, (IPAddress Address, int Subnet) cidr, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains))); + public static NpgsqlInet BitwiseAnd(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseAnd))); /// - /// Determines whether an (IPAddress Address, int Subnet) contains another (IPAddress Address, int Subnet). + /// Computes the bitwise AND of two instances. /// /// The instance. - /// The cidr to search. - /// The cidr to locate. + /// The left-hand macaddr. + /// The right-hand macaddr. /// - /// True if the (IPAddress Address, int Subnet) contains the other (IPAddress Address, int Subnet); otherwise, false. + /// The result of the bitwise AND operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool Contains(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains))); + public static PhysicalAddress BitwiseAnd(this DbFunctions _, PhysicalAddress macaddr, PhysicalAddress other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseAnd))); /// - /// Determines whether an contains or is equal to another . + /// Computes the bitwise OR of two instances. /// /// The instance. - /// The IP address to search. - /// The IP address to locate. + /// The left-hand inet. + /// The right-hand inet. /// - /// True if the contains or is equal to the other ; otherwise, false. + /// The result of the bitwise OR operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainsOrEqual(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrEqual))); + public static NpgsqlInet BitwiseOr(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseOr))); /// - /// Determines whether a network contains or is equal to another . + /// Computes the bitwise OR of two instances. /// /// The instance. - /// The network to search. - /// The IP address to locate. + /// The left-hand macaddr. + /// The right-hand macaddr. /// - /// True if the network contains or is equal to the other ; otherwise, false. + /// The result of the bitwise OR operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainsOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrEqual))); + public static PhysicalAddress BitwiseOr(this DbFunctions _, PhysicalAddress macaddr, PhysicalAddress other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseOr))); + + #endregion + + #region ArithmeticOperators /// - /// Determines whether an (IPAddress Address, int Subnet) contains or is equal to another (IPAddress Address, int Subnet). + /// Adds the to the . /// /// The instance. - /// The cidr to search. - /// The cidr to locate. + /// The inet. + /// The value to add. /// - /// True if the (IPAddress Address, int Subnet) contains or is equal to the other (IPAddress Address, int Subnet); otherwise, false. + /// The augmented by the . /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainsOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrEqual))); + public static NpgsqlInet Add(this DbFunctions _, NpgsqlInet inet, int value) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Add))); /// - /// Determines whether an contains or is contained by another . + /// Subtracts the from the . /// /// The instance. - /// The IP address to search. - /// The IP address to locate. + /// The inet. + /// The value to subtract. /// - /// True if the contains or is contained by the other ; otherwise, false. + /// The augmented by the . /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainsOrContainedBy(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrContainedBy))); + public static NpgsqlInet Subtract(this DbFunctions _, NpgsqlInet inet, long value) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Subtract))); /// - /// Determines whether a network contains or is contained by an . + /// Subtracts one from another . /// /// The instance. - /// The network to search. - /// The IP address to locate. + /// The inet from which to subtract. + /// The inet to subtract. /// - /// True if the network contains or is contained by the ; otherwise, false. + /// The numeric difference between the two given addresses. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainsOrContainedBy(this DbFunctions _, (IPAddress Address, int Subnet) cidr, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrContainedBy))); + public static int Subtract(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Subtract))); + + #endregion + + #region Functions /// - /// Determines whether an contains or is contained by a network. + /// Returns the abbreviated display format as text. /// /// The instance. - /// The IP address to search. - /// The network to locate. + /// The inet to abbreviate. /// - /// True if the contains or is contained by the network; otherwise, false. + /// The abbreviated display format as text. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainsOrContainedBy(this DbFunctions _, IPAddress inet, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrContainedBy))); + public static string Abbreviate(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Abbreviate))); /// - /// Determines whether an (IPAddress Address, int Subnet) contains or is contained by another (IPAddress Address, int Subnet). + /// Returns the abbreviated display format as text. /// /// The instance. - /// The cidr to search. - /// The cidr to locate. + /// The cidr to abbreviate. /// - /// True if the (IPAddress Address, int Subnet) contains or is contained by the other (IPAddress Address, int Subnet); otherwise, false. + /// The abbreviated display format as text. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool ContainsOrContainedBy( - this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainsOrContainedBy))); - - #endregion - - #region BitwiseOperators + public static string Abbreviate(this DbFunctions _, NpgsqlCidr cidr) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Abbreviate))); /// - /// Computes the bitwise NOT operation on an . + /// Returns the broadcast address for a network. /// /// The instance. - /// The inet to negate. + /// The inet used to derive the broadcast address. /// - /// The result of the bitwise NOT operation. + /// The broadcast address for a network. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress BitwiseNot(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseNot))); + public static NpgsqlInet Broadcast(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Broadcast))); /// - /// Computes the bitwise NOT operation on an (IPAddress Address, int Subnet). + /// Extracts the family of an address; 4 for IPv4, 6 for IPv6. /// /// The instance. - /// The cidr to negate. + /// The inet used to derive the family. /// - /// The result of the bitwise NOT operation. + /// The family of an address; 4 for IPv4, 6 for IPv6. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) BitwiseNot(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseNot))); + public static int Family(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Family))); /// - /// Computes the bitwise NOT operation on an . + /// Extracts the host (i.e. the IP address) as text. /// /// The instance. - /// The macaddr to negate. + /// The inet from which to extract the host. /// - /// The result of the bitwise NOT operation. + /// The host (i.e. the IP address) as text. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static PhysicalAddress BitwiseNot(this DbFunctions _, PhysicalAddress macaddr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseNot))); + public static string Host(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Host))); /// - /// Computes the bitwise AND of two instances. + /// Constructs the host mask for the network. /// /// The instance. - /// The left-hand inet. - /// The right-hand inet. + /// The inet used to construct the host mask. /// - /// The result of the bitwise AND operation. + /// The constructed host mask. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress BitwiseAnd(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseAnd))); + public static NpgsqlInet HostMask(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(HostMask))); /// - /// Computes the bitwise AND of two (IPAddress Address, int Subnet) instances. + /// Extracts the length of the subnet mask. /// /// The instance. - /// The left-hand cidr. - /// The right-hand cidr. + /// The inet used to extract the subnet length. /// - /// The result of the bitwise AND operation. + /// The length of the subnet mask. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) BitwiseAnd( - this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseAnd))); + public static int MaskLength(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(MaskLength))); /// - /// Computes the bitwise AND of two instances. + /// Constructs the subnet mask for the network. /// /// The instance. - /// The left-hand macaddr. - /// The right-hand macaddr. + /// The inet used to construct the subnet mask. /// - /// The result of the bitwise AND operation. + /// The subnet mask for the network. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static PhysicalAddress BitwiseAnd(this DbFunctions _, PhysicalAddress macaddr, PhysicalAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseAnd))); + public static NpgsqlInet Netmask(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Netmask))); /// - /// Computes the bitwise OR of two instances. + /// Extracts the network part of the address. /// /// The instance. - /// The left-hand inet. - /// The right-hand inet. + /// The inet used to extract the network. /// - /// The result of the bitwise OR operation. + /// The network part of the address. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress BitwiseOr(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseOr))); + public static NpgsqlCidr Network(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Network))); /// - /// Computes the bitwise OR of two (IPAddress Address, int Subnet) instances. + /// Sets the length of the subnet mask. /// /// The instance. - /// The left-hand cidr. - /// The right-hand cidr. + /// The inet to modify. + /// The subnet mask length to set. /// - /// The result of the bitwise OR operation. + /// The network with a subnet mask of the specified length. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) BitwiseOr( - this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseOr))); + public static NpgsqlInet SetMaskLength(this DbFunctions _, NpgsqlInet inet, int length) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(SetMaskLength))); /// - /// Computes the bitwise OR of two instances. + /// Sets the length of the subnet mask. /// /// The instance. - /// The left-hand macaddr. - /// The right-hand macaddr. + /// The cidr to modify. + /// The subnet mask length to set. /// - /// The result of the bitwise OR operation. + /// The network with a subnet mask of the specified length. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static PhysicalAddress BitwiseOr(this DbFunctions _, PhysicalAddress macaddr, PhysicalAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(BitwiseOr))); + public static NpgsqlCidr SetMaskLength(this DbFunctions _, NpgsqlCidr cidr, int length) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(SetMaskLength))); - #endregion + /// + /// Extracts the IP address and subnet mask as text. + /// + /// The instance. + /// The inet to extract as text. + /// + /// The IP address and subnet mask as text. + /// + /// + /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. + /// + public static string Text(this DbFunctions _, NpgsqlInet inet) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Text))); - #region ArithmeticOperators + /// + /// Tests if the addresses are in the same family. + /// + /// The instance. + /// The primary inet. + /// The other inet. + /// + /// True if the addresses are in the same family; otherwise, false. + /// + /// + /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. + /// + public static bool SameFamily(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(SameFamily))); /// - /// Adds the to the . + /// Constructs the smallest network which includes both of the given networks. /// /// The instance. - /// The inet. - /// The value to add. + /// The first inet. + /// The second inet. /// - /// The augmented by the . + /// The smallest network which includes both of the given networks. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress Add(this DbFunctions _, IPAddress inet, int value) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Add))); + public static NpgsqlCidr Merge(this DbFunctions _, NpgsqlInet inet, NpgsqlInet other) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Merge))); /// - /// Adds the to the (IPAddress Address, int Subnet). + /// Sets the last 3 bytes of the MAC address to zero. For macaddr8, the last 5 bytes are set to zero. /// /// The instance. - /// The cidr. - /// The value to add. + /// The MAC address to truncate. /// - /// The (IPAddress Address, int Subnet) augmented by the . + /// The MAC address with the last 3 bytes set to zero. For macaddr8, the last 5 bytes are set to zero. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) Add(this DbFunctions _, (IPAddress Address, int Subnet) cidr, int value) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Add))); + public static PhysicalAddress Truncate(this DbFunctions _, PhysicalAddress macAddress) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Truncate))); /// - /// Subtracts the from the . + /// Sets the 7th bit to one, also known as modified EUI-64, for inclusion in an IPv6 address. /// /// The instance. - /// The inet. - /// The value to subtract. + /// The MAC address to modify. /// - /// The augmented by the . + /// The MAC address with the 7th bit set to one. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress Subtract(this DbFunctions _, IPAddress inet, int value) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Subtract))); + public static PhysicalAddress Set7BitMac8(this DbFunctions _, PhysicalAddress macAddress) + => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Set7BitMac8))); + + #endregion + + #region Obsolete /// - /// Subtracts the from the (IPAddress Address, int Subnet). + /// Determines whether an (IPAddress Address, int Subnet) is less than another (IPAddress Address, int Subnet). /// /// The instance. - /// The inet. - /// The value to subtract. + /// The left-hand cidr. + /// The right-hand cidr. /// - /// The (IPAddress Address, int Subnet) augmented by the . + /// True if the (IPAddress Address, int Subnet) is less than the other (IPAddress Address, int Subnet); otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) Subtract(this DbFunctions _, (IPAddress Address, int Subnet) cidr, int value) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Subtract))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool LessThan(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Subtracts one from another . + /// Determines whether an (IPAddress Address, int Subnet) is less than or equal to another (IPAddress Address, int Subnet). /// /// The instance. - /// The inet from which to subtract. - /// The inet to subtract. + /// The left-hand cidr. + /// The right-hand cidr. /// - /// The numeric difference between the two given addresses. + /// True if the (IPAddress Address, int Subnet) is less than or equal to the other (IPAddress Address, int Subnet); otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static int Subtract(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Subtract))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool LessThanOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Subtracts one (IPAddress Address, int Subnet) from another (IPAddress Address, int Subnet). + /// Determines whether an (IPAddress Address, int Subnet) is greater than or equal to another (IPAddress Address, int Subnet). /// /// The instance. - /// The cidr from which to subtract. - /// The cidr to subtract. + /// The left-hand cidr. + /// The right-hand cidr. /// - /// The difference between the two addresses. + /// True if the (IPAddress Address, int Subnet) is greater than or equal to the other (IPAddress Address, int Subnet); otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static int Subtract( - this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Subtract))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool GreaterThanOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); - #endregion + /// + /// Determines whether an (IPAddress Address, int Subnet) is greater than another (IPAddress Address, int Subnet). + /// + /// The instance. + /// The left-hand cidr. + /// The right-hand cidr. + /// + /// True if the (IPAddress Address, int Subnet) is greater than the other (IPAddress Address, int Subnet); otherwise, false. + /// + /// + /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. + /// + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool GreaterThan(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); - #region Functions + /// + /// Determines whether an is contained within a network. + /// + /// The instance. + /// The inet to locate. + /// The cidr to search. + /// + /// True if the is contained within the network; otherwise, false. + /// + /// + /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. + /// + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainedBy(this DbFunctions _, IPAddress inet, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Returns the abbreviated display format as text. + /// Determines whether a network contains or is equal to another . /// /// The instance. - /// The inet to abbreviate. + /// The network to search. + /// The IP address to locate. /// - /// The abbreviated display format as text. + /// True if the network contains or is equal to the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static string Abbreviate(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Abbreviate))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainsOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, IPAddress other) + => throw new NotSupportedException(); /// - /// Returns the abbreviated display format as text. + /// Determines whether an (IPAddress Address, int Subnet) is contained within another (IPAddress Address, int Subnet). /// /// The instance. - /// The cidr to abbreviate. + /// The cidr to locate. + /// The cidr to search. /// - /// The abbreviated display format as text. + /// True if the (IPAddress Address, int Subnet) is contained within the other (IPAddress Address, int Subnet); otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static string Abbreviate(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Abbreviate))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainedBy(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Returns the broadcast address for a network. + /// Determines whether an is contained within or equal to a network. /// /// The instance. - /// The inet used to derive the broadcast address. + /// The inet to locate. + /// The cidr to search. /// - /// The broadcast address for a network. + /// True if the is contained within or equal to the network; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress Broadcast(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Broadcast))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainedByOrEqual(this DbFunctions _, IPAddress inet, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Returns the broadcast address for a network. + /// Determines whether an (IPAddress Address, int Subnet) is contained within or equal to another (IPAddress Address, int Subnet). /// /// The instance. - /// The cidr used to derive the broadcast address. + /// The cidr to locate. + /// The cidr to search. /// - /// The broadcast address for a network. + /// True if the (IPAddress Address, int Subnet) is contained within or equal to the other (IPAddress Address, int Subnet); otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress Broadcast(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Broadcast))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainedByOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Extracts the family of an address; 4 for IPv4, 6 for IPv6. + /// Determines whether a network contains another . /// /// The instance. - /// The inet used to derive the family. + /// The network to search. + /// The IP address to locate. /// - /// The family of an address; 4 for IPv4, 6 for IPv6. + /// True if the network contains the other ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static int Family(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Family))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool Contains( + this DbFunctions _, (IPAddress Address, int Subnet) cidr, IPAddress other) + => throw new NotSupportedException(); /// - /// Extracts the family of an address; 4 for IPv4, 6 for IPv6. + /// Determines whether an (IPAddress Address, int Subnet) contains another (IPAddress Address, int Subnet). /// /// The instance. - /// The cidr used to derive the family. + /// The cidr to search. + /// The cidr to locate. /// - /// The family of an address; 4 for IPv4, 6 for IPv6. + /// True if the (IPAddress Address, int Subnet) contains the other (IPAddress Address, int Subnet); otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static int Family(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Family))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool Contains(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Extracts the host (i.e. the IP address) as text. + /// Determines whether an (IPAddress Address, int Subnet) contains or is equal to another (IPAddress Address, int Subnet). /// /// The instance. - /// The inet from which to extract the host. + /// The cidr to search. + /// The cidr to locate. /// - /// The host (i.e. the IP address) as text. + /// True if the (IPAddress Address, int Subnet) contains or is equal to the other (IPAddress Address, int Subnet); otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static string Host(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Host))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainsOrEqual(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Extracts the host (i.e. the IP address) as text. + /// Determines whether a network contains or is contained by an . /// /// The instance. - /// The cidr from which to extract the host. + /// The network to search. + /// The IP address to locate. /// - /// The host (i.e. the IP address) as text. + /// True if the network contains or is contained by the ; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static string Host(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Host))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainsOrContainedBy(this DbFunctions _, (IPAddress Address, int Subnet) cidr, IPAddress other) + => throw new NotSupportedException(); /// - /// Constructs the host mask for the network. + /// Determines whether an contains or is contained by a network. /// /// The instance. - /// The inet used to construct the host mask. + /// The IP address to search. + /// The network to locate. /// - /// The constructed host mask. + /// True if the contains or is contained by the network; otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress HostMask(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(HostMask))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainsOrContainedBy(this DbFunctions _, IPAddress inet, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Constructs the host mask for the network. + /// Determines whether an (IPAddress Address, int Subnet) contains or is contained by another (IPAddress Address, int Subnet). /// /// The instance. - /// The cidr used to construct the host mask. + /// The cidr to search. + /// The cidr to locate. /// - /// The constructed host mask. + /// True if the (IPAddress Address, int Subnet) contains or is contained by the other (IPAddress Address, int Subnet); otherwise, false. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress HostMask(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(HostMask))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool ContainsOrContainedBy( + this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Extracts the length of the subnet mask. + /// Computes the bitwise NOT operation on an (IPAddress Address, int Subnet). /// /// The instance. - /// The inet used to extract the subnet length. + /// The cidr to negate. /// - /// The length of the subnet mask. + /// The result of the bitwise NOT operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static int MaskLength(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(MaskLength))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static (IPAddress Address, int Subnet) BitwiseNot(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Extracts the length of the subnet mask. + /// Computes the bitwise AND of two (IPAddress Address, int Subnet) instances. /// /// The instance. - /// The cidr used to extract the subnet length. + /// The left-hand cidr. + /// The right-hand cidr. /// - /// The length of the subnet mask. + /// The result of the bitwise AND operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static int MaskLength(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(MaskLength))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static (IPAddress Address, int Subnet) BitwiseAnd( + this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Constructs the subnet mask for the network. + /// Computes the bitwise OR of two (IPAddress Address, int Subnet) instances. /// /// The instance. - /// The inet used to construct the subnet mask. + /// The left-hand cidr. + /// The right-hand cidr. /// - /// The subnet mask for the network. + /// The result of the bitwise OR operation. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress Netmask(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Netmask))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static (IPAddress Address, int Subnet) BitwiseOr( + this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Constructs the subnet mask for the network. + /// Adds the to the (IPAddress Address, int Subnet). /// /// The instance. - /// The cidr used to construct the subnet mask. + /// The cidr. + /// The value to add. /// - /// The subnet mask for the network. + /// The (IPAddress Address, int Subnet) augmented by the . /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress Netmask(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Netmask))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static (IPAddress Address, int Subnet) Add(this DbFunctions _, (IPAddress Address, int Subnet) cidr, int value) + => throw new NotSupportedException(); /// - /// Extracts the network part of the address. + /// Subtracts the from the (IPAddress Address, int Subnet). /// /// The instance. - /// The inet used to extract the network. + /// The inet. + /// The value to subtract. /// - /// The network part of the address. + /// The (IPAddress Address, int Subnet) augmented by the . /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) Network(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Network))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static (IPAddress Address, int Subnet) Subtract(this DbFunctions _, (IPAddress Address, int Subnet) cidr, int value) + => throw new NotSupportedException(); /// - /// Extracts the network part of the address. + /// Subtracts one (IPAddress Address, int Subnet) from another (IPAddress Address, int Subnet). /// /// The instance. - /// The cidr used to extract the network. + /// The cidr from which to subtract. + /// The cidr to subtract. /// - /// The network part of the address. + /// The difference between the two addresses. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) Network(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Network))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static int Subtract( + this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); /// - /// Sets the length of the subnet mask. + /// Returns the abbreviated display format as text. /// /// The instance. - /// The inet to modify. - /// The subnet mask length to set. + /// The cidr to abbreviate. /// - /// The network with a subnet mask of the specified length. + /// The abbreviated display format as text. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static IPAddress SetMaskLength(this DbFunctions _, IPAddress inet, int length) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(SetMaskLength))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static string Abbreviate(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Sets the length of the subnet mask. + /// Returns the broadcast address for a network. /// /// The instance. - /// The cidr to modify. - /// The subnet mask length to set. + /// The cidr used to derive the broadcast address. /// - /// The network with a subnet mask of the specified length. + /// The broadcast address for a network. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) SetMaskLength(this DbFunctions _, (IPAddress Address, int Subnet) cidr, int length) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(SetMaskLength))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static IPAddress Broadcast(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Extracts the IP address and subnet mask as text. + /// Extracts the family of an address; 4 for IPv4, 6 for IPv6. /// /// The instance. - /// The inet to extract as text. + /// The cidr used to derive the family. /// - /// The IP address and subnet mask as text. + /// The family of an address; 4 for IPv4, 6 for IPv6. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static string Text(this DbFunctions _, IPAddress inet) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Text))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static int Family(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Extracts the IP address and subnet mask as text. + /// Extracts the host (i.e. the IP address) as text. /// /// The instance. - /// The cidr to extract as text. + /// The cidr from which to extract the host. /// - /// The IP address and subnet mask as text. + /// The host (i.e. the IP address) as text. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static string Text(this DbFunctions _, (IPAddress Address, int Subnet) cidr) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Text))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static string Host(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Tests if the addresses are in the same family. + /// Constructs the host mask for the network. /// /// The instance. - /// The primary inet. - /// The other inet. + /// The cidr used to construct the host mask. /// - /// True if the addresses are in the same family; otherwise, false. + /// The constructed host mask. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool SameFamily(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(SameFamily))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static IPAddress HostMask(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Tests if the addresses are in the same family. + /// Extracts the length of the subnet mask. /// /// The instance. - /// The primary cidr. - /// The other cidr. + /// The cidr used to extract the subnet length. /// - /// True if the addresses are in the same family; otherwise, false. + /// The length of the subnet mask. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static bool SameFamily(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(SameFamily))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static int MaskLength(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Constructs the smallest network which includes both of the given networks. + /// Constructs the subnet mask for the network. /// /// The instance. - /// The first inet. - /// The second inet. + /// The cidr used to construct the subnet mask. /// - /// The smallest network which includes both of the given networks. + /// The subnet mask for the network. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) Merge(this DbFunctions _, IPAddress inet, IPAddress other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Merge))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static IPAddress Netmask(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Constructs the smallest network which includes both of the given networks. + /// Extracts the network part of the address. /// /// The instance. - /// The first cidr. - /// The second cidr. + /// The cidr used to extract the network. /// - /// The smallest network which includes both of the given networks. + /// The network part of the address. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static (IPAddress Address, int Subnet) Merge( - this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Merge))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static (IPAddress Address, int Subnet) Network(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); /// - /// Sets the last 3 bytes of the MAC address to zero. For macaddr8, the last 5 bytes are set to zero. + /// Sets the length of the subnet mask. /// /// The instance. - /// The MAC address to truncate. + /// The cidr to modify. + /// The subnet mask length to set. /// - /// The MAC address with the last 3 bytes set to zero. For macaddr8, the last 5 bytes are set to zero. + /// The network with a subnet mask of the specified length. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static PhysicalAddress Truncate(this DbFunctions _, PhysicalAddress macAddress) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Truncate))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static (IPAddress Address, int Subnet) SetMaskLength(this DbFunctions _, (IPAddress Address, int Subnet) cidr, int length) + => throw new NotSupportedException(); /// - /// Sets the 7th bit to one, also known as modified EUI-64, for inclusion in an IPv6 address. + /// Extracts the IP address and subnet mask as text. /// /// The instance. - /// The MAC address to modify. + /// The cidr to extract as text. /// - /// The MAC address with the 7th bit set to one. + /// The IP address and subnet mask as text. /// /// /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. /// - public static PhysicalAddress Set7BitMac8(this DbFunctions _, PhysicalAddress macAddress) - => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Set7BitMac8))); + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static string Text(this DbFunctions _, (IPAddress Address, int Subnet) cidr) + => throw new NotSupportedException(); - #endregion -} \ No newline at end of file + /// + /// Tests if the addresses are in the same family. + /// + /// The instance. + /// The primary cidr. + /// The other cidr. + /// + /// True if the addresses are in the same family; otherwise, false. + /// + /// + /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. + /// + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static bool SameFamily(this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); + + /// + /// Constructs the smallest network which includes both of the given networks. + /// + /// The instance. + /// The first cidr. + /// The second cidr. + /// + /// The smallest network which includes both of the given networks. + /// + /// + /// This method is only intended for use via SQL translation as part of an EF Core LINQ query. + /// + [Obsolete("Use the overload which accepts NpgsqlCidr", error: true)] + public static (IPAddress Address, int Subnet) Merge( + this DbFunctions _, (IPAddress Address, int Subnet) cidr, (IPAddress Address, int Subnet) other) + => throw new NotSupportedException(); + + #endregion Obsolete +} diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs index 4c76576f6..3f209035c 100644 --- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs +++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlNetworkTranslator.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.NetworkInformation; +using System.Runtime.CompilerServices; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics; using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions; @@ -62,97 +63,203 @@ public NpgsqlNetworkTranslator( return _sqlExpressionFactory.Convert(arguments[0], typeof(PhysicalAddress)); } - if (method.DeclaringType != typeof(NpgsqlNetworkDbFunctionsExtensions)) + if (method.DeclaringType == typeof(NpgsqlNetworkDbFunctionsExtensions)) { - return null; + var paramType = method.GetParameters()[1].ParameterType; + + if (paramType == typeof(NpgsqlInet)) + { + return TranslateInetExtensionMethod(method, arguments); + } + + if (paramType == typeof(NpgsqlCidr)) + { + return TranslateCidrExtensionMethod(method, arguments); + } + + if (paramType == typeof(PhysicalAddress)) + { + return TranslateMacaddrExtensionMethod(method, arguments); + } } - return method.Name switch + return null; + } + + private SqlExpression? TranslateInetExtensionMethod(MethodInfo method, IReadOnlyList arguments) + => method.Name switch { nameof(NpgsqlNetworkDbFunctionsExtensions.LessThan) - => _sqlExpressionFactory.LessThan(arguments[1], arguments[2]), + => new SqlBinaryExpression( + ExpressionType.LessThan, + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), + typeof(NpgsqlInet), + _inetMapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.LessThanOrEqual) - => _sqlExpressionFactory.LessThanOrEqual(arguments[1], arguments[2]), + => new SqlBinaryExpression( + ExpressionType.LessThanOrEqual, + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), + typeof(NpgsqlInet), + _inetMapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.GreaterThanOrEqual) - => _sqlExpressionFactory.GreaterThanOrEqual(arguments[1], arguments[2]), + => new SqlBinaryExpression( + ExpressionType.GreaterThanOrEqual, + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), + typeof(NpgsqlInet), + _inetMapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.GreaterThan) - => _sqlExpressionFactory.GreaterThan(arguments[1], arguments[2]), + => new SqlBinaryExpression( + ExpressionType.GreaterThan, + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), + typeof(NpgsqlInet), + _inetMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.ContainedBy) => _sqlExpressionFactory.ContainedBy(arguments[1], arguments[2]), nameof(NpgsqlNetworkDbFunctionsExtensions.ContainedByOrEqual) - => _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.NetworkContainedByOrEqual, arguments[1], arguments[2]), + => _sqlExpressionFactory.MakePostgresBinary( + PostgresExpressionType.NetworkContainedByOrEqual, arguments[1], arguments[2]), nameof(NpgsqlNetworkDbFunctionsExtensions.Contains) => _sqlExpressionFactory.Contains(arguments[1], arguments[2]), nameof(NpgsqlNetworkDbFunctionsExtensions.ContainsOrEqual) => _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.NetworkContainsOrEqual, arguments[1], arguments[2]), nameof(NpgsqlNetworkDbFunctionsExtensions.ContainsOrContainedBy) - => _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.NetworkContainsOrContainedBy, arguments[1], arguments[2]), + => _sqlExpressionFactory.MakePostgresBinary( + PostgresExpressionType.NetworkContainsOrContainedBy, arguments[1], arguments[2]), nameof(NpgsqlNetworkDbFunctionsExtensions.BitwiseNot) - => new SqlUnaryExpression(ExpressionType.Not, arguments[1], arguments[1].Type, arguments[1].TypeMapping), + => new SqlUnaryExpression( + ExpressionType.Not, + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), + typeof(NpgsqlInet), + _inetMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.BitwiseAnd) - => _sqlExpressionFactory.And(arguments[1], arguments[2]), + => new SqlBinaryExpression( + ExpressionType.And, + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), + typeof(NpgsqlInet), + _inetMapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.BitwiseOr) - => _sqlExpressionFactory.Or(arguments[1], arguments[2]), + => new SqlBinaryExpression( + ExpressionType.Or, + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), + typeof(NpgsqlInet), + _inetMapping), - // Add/Subtract accept inet + int, so we can't use the default type mapping inference logic which assumes - // same-typed operands nameof(NpgsqlNetworkDbFunctionsExtensions.Add) => new SqlBinaryExpression( ExpressionType.Add, _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), - arguments[1].Type, - arguments[1].TypeMapping), + typeof(NpgsqlInet), + _inetMapping), - nameof(NpgsqlNetworkDbFunctionsExtensions.Subtract) when arguments[2].Type == typeof(int) + nameof(NpgsqlNetworkDbFunctionsExtensions.Subtract) when arguments[2].Type == typeof(long) => new SqlBinaryExpression( ExpressionType.Subtract, _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), - arguments[1].Type, - arguments[1].TypeMapping), + typeof(NpgsqlInet), + _inetMapping), nameof(NpgsqlNetworkDbFunctionsExtensions.Subtract) - when arguments[2].Type == typeof(IPAddress) || arguments[2].Type == typeof((IPAddress, int)) => new SqlBinaryExpression( ExpressionType.Subtract, - _sqlExpressionFactory.ApplyTypeMapping(arguments[1], ExpressionExtensions.InferTypeMapping(arguments[1], arguments[2])), - _sqlExpressionFactory.ApplyTypeMapping(arguments[2], ExpressionExtensions.InferTypeMapping(arguments[1], arguments[2])), - arguments[1].Type, + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[1]), + _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[2]), + typeof(long), _longAddressMapping), - nameof(NpgsqlNetworkDbFunctionsExtensions.Abbreviate) => NullPropagatingFunction("abbrev", new[] { arguments[1] }, typeof(string)), - nameof(NpgsqlNetworkDbFunctionsExtensions.Broadcast) => NullPropagatingFunction("broadcast", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), - nameof(NpgsqlNetworkDbFunctionsExtensions.Family) => NullPropagatingFunction("family", new[] { arguments[1] }, typeof(int)), - nameof(NpgsqlNetworkDbFunctionsExtensions.Host) => NullPropagatingFunction("host", new[] { arguments[1] }, typeof(string)), - nameof(NpgsqlNetworkDbFunctionsExtensions.HostMask) => NullPropagatingFunction("hostmask", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), - nameof(NpgsqlNetworkDbFunctionsExtensions.MaskLength) => NullPropagatingFunction("masklen", new[] { arguments[1] }, typeof(int)), - nameof(NpgsqlNetworkDbFunctionsExtensions.Netmask) => NullPropagatingFunction("netmask", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), - nameof(NpgsqlNetworkDbFunctionsExtensions.Network) => NullPropagatingFunction("network", new[] { 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), - nameof(NpgsqlNetworkDbFunctionsExtensions.Text) => NullPropagatingFunction("text", new[] { arguments[1] }, typeof(string)), - nameof(NpgsqlNetworkDbFunctionsExtensions.SameFamily) => NullPropagatingFunction("inet_same_family", new[] { arguments[1], arguments[2] }, typeof(bool)), - nameof(NpgsqlNetworkDbFunctionsExtensions.Merge) => NullPropagatingFunction("inet_merge", new[] { arguments[1], arguments[2] }, typeof((IPAddress Address, int Subnet)), _cidrMapping), - nameof(NpgsqlNetworkDbFunctionsExtensions.Truncate) => NullPropagatingFunction("trunc", new[] { arguments[1] }, typeof(PhysicalAddress), arguments[1].TypeMapping), - nameof(NpgsqlNetworkDbFunctionsExtensions.Set7BitMac8) => NullPropagatingFunction("macaddr8_set7bit", new[] { arguments[1] }, typeof(PhysicalAddress), _macaddr8Mapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.Abbreviate) + => NullPropagatingFunction("abbrev", new[] { arguments[1] }, typeof(string)), + nameof(NpgsqlNetworkDbFunctionsExtensions.Broadcast) + => NullPropagatingFunction("broadcast", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.Family) + => NullPropagatingFunction("family", new[] { arguments[1] }, typeof(int)), + nameof(NpgsqlNetworkDbFunctionsExtensions.Host) + => NullPropagatingFunction("host", new[] { arguments[1] }, typeof(string)), + nameof(NpgsqlNetworkDbFunctionsExtensions.HostMask) + => NullPropagatingFunction("hostmask", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.MaskLength) + => NullPropagatingFunction("masklen", new[] { arguments[1] }, typeof(int)), + nameof(NpgsqlNetworkDbFunctionsExtensions.Netmask) + => NullPropagatingFunction("netmask", new[] { arguments[1] }, typeof(IPAddress), _inetMapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.Network) + => NullPropagatingFunction("network", new[] { 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), + nameof(NpgsqlNetworkDbFunctionsExtensions.Text) + => NullPropagatingFunction("text", new[] { arguments[1] }, typeof(string)), + nameof(NpgsqlNetworkDbFunctionsExtensions.SameFamily) + => NullPropagatingFunction("inet_same_family", new[] { arguments[1], arguments[2] }, typeof(bool)), + nameof(NpgsqlNetworkDbFunctionsExtensions.Merge) + => NullPropagatingFunction( + "inet_merge", new[] { arguments[1], arguments[2] }, typeof((IPAddress Address, int Subnet)), _cidrMapping), _ => null }; - SqlFunctionExpression NullPropagatingFunction( - string name, - SqlExpression[] arguments, - Type returnType, - RelationalTypeMapping? typeMapping = null) - => _sqlExpressionFactory.Function( - name, - arguments, - nullable: true, - argumentsPropagateNullability: TrueArrays[arguments.Length], - returnType, - typeMapping); - } + private SqlExpression? TranslateCidrExtensionMethod(MethodInfo method, IReadOnlyList arguments) + => method.Name switch + { + nameof(NpgsqlNetworkDbFunctionsExtensions.Abbreviate) + => NullPropagatingFunction("abbrev", new[] { arguments[1] }, typeof(string)), + nameof(NpgsqlNetworkDbFunctionsExtensions.SetMaskLength) + => NullPropagatingFunction( + "set_masklen", new[] { arguments[1], arguments[2] }, arguments[1].Type, arguments[1].TypeMapping), + + _ => null + }; + + private SqlExpression? TranslateMacaddrExtensionMethod(MethodInfo method, IReadOnlyList arguments) + => method.Name switch + { + nameof(NpgsqlNetworkDbFunctionsExtensions.LessThan) + => _sqlExpressionFactory.LessThan(arguments[1], arguments[2]), + nameof(NpgsqlNetworkDbFunctionsExtensions.LessThanOrEqual) + => _sqlExpressionFactory.LessThanOrEqual(arguments[1], arguments[2]), + nameof(NpgsqlNetworkDbFunctionsExtensions.GreaterThanOrEqual) + => _sqlExpressionFactory.GreaterThanOrEqual(arguments[1], arguments[2]), + nameof(NpgsqlNetworkDbFunctionsExtensions.GreaterThan) + => _sqlExpressionFactory.GreaterThan(arguments[1], arguments[2]), + + nameof(NpgsqlNetworkDbFunctionsExtensions.BitwiseNot) + => _sqlExpressionFactory.Not(arguments[1]), + nameof(NpgsqlNetworkDbFunctionsExtensions.BitwiseAnd) + => _sqlExpressionFactory.And(arguments[1], arguments[2]), + nameof(NpgsqlNetworkDbFunctionsExtensions.BitwiseOr) + => _sqlExpressionFactory.Or(arguments[1], arguments[2]), + + nameof(NpgsqlNetworkDbFunctionsExtensions.Truncate) => NullPropagatingFunction( + "trunc", new[] { arguments[1] }, typeof(PhysicalAddress), arguments[1].TypeMapping), + nameof(NpgsqlNetworkDbFunctionsExtensions.Set7BitMac8) => NullPropagatingFunction( + "macaddr8_set7bit", new[] { arguments[1] }, typeof(PhysicalAddress), _macaddr8Mapping), + + _ => null + }; + + private SqlFunctionExpression NullPropagatingFunction( + string name, + SqlExpression[] arguments, + Type returnType, + RelationalTypeMapping? typeMapping = null) + => _sqlExpressionFactory.Function( + name, + arguments, + nullable: true, + argumentsPropagateNullability: TrueArrays[arguments.Length], + returnType, + typeMapping); } diff --git a/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs b/src/EFCore.PG/Query/Internal/NpgsqlSqlTranslatingExpressionVisitor.cs index 3701e0213..89844d86a 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.Net; using System.Runtime.CompilerServices; using System.Text; using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; @@ -115,6 +116,15 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) case ExpressionType.Convert when unaryExpression.Type == typeof(ITuple) && unaryExpression.Operand.Type.IsAssignableTo(typeof(ITuple)): return Visit(unaryExpression.Operand); + + // We map both IPAddress and NpgsqlInet to PG inet, and translate many methods accepting NpgsqlInet, so ignore casts from + // IPAddress to NpgsqlInet. + // On the PostgreSQL side, cidr is also implicitly convertible to inet, and at the ADO.NET level NpgsqlCidr has a similar + // implicit conversion operator to NpgsqlInet. So remove that cast as well. + case ExpressionType.Convert + when unaryExpression.Type == typeof(NpgsqlInet) + && (unaryExpression.Operand.Type == typeof(IPAddress) || unaryExpression.Operand.Type == typeof(NpgsqlCidr)): + return Visit(unaryExpression.Operand); } return base.VisitUnary(unaryExpression); diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 93e5aad5d..40ff6e5f2 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.Net; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; @@ -231,39 +232,40 @@ public virtual PostgresNewArrayExpression NewArray( SqlExpression right, RelationalTypeMapping? typeMapping) { - Check.NotNull(left, nameof(left)); - Check.NotNull(right, nameof(right)); - - if (operatorType == ExpressionType.Subtract) + switch (operatorType) { - if (left.Type == typeof(DateTime) && right.Type == typeof(DateTime) || - left.Type == typeof(DateTimeOffset) && right.Type == typeof(DateTimeOffset) || - left.Type == typeof(TimeOnly) && right.Type == typeof(TimeOnly)) + case ExpressionType.Subtract + when left.Type == typeof(DateTime) && right.Type == typeof(DateTime) + || left.Type == typeof(DateTimeOffset) && right.Type == typeof(DateTimeOffset) + || left.Type == typeof(TimeOnly) && right.Type == typeof(TimeOnly): { return (SqlBinaryExpression)ApplyTypeMapping( - new SqlBinaryExpression(operatorType, left, right, typeof(TimeSpan), null), typeMapping); + new SqlBinaryExpression(ExpressionType.Subtract, left, right, typeof(TimeSpan), null), typeMapping); } - if (left.Type.FullName == "NodaTime.Instant" && right.Type.FullName == "NodaTime.Instant" || - left.Type.FullName == "NodaTime.ZonedDateTime" && right.Type.FullName == "NodaTime.ZonedDateTime") + case ExpressionType.Subtract + when left.Type.FullName == "NodaTime.Instant" && right.Type.FullName == "NodaTime.Instant" + || left.Type.FullName == "NodaTime.ZonedDateTime" && right.Type.FullName == "NodaTime.ZonedDateTime": { _nodaTimeDurationType ??= left.Type.Assembly.GetType("NodaTime.Duration"); return (SqlBinaryExpression)ApplyTypeMapping( - new SqlBinaryExpression(operatorType, left, right, _nodaTimeDurationType!, null), typeMapping); + new SqlBinaryExpression(ExpressionType.Subtract, left, right, _nodaTimeDurationType!, null), typeMapping); } - if (left.Type.FullName == "NodaTime.LocalDateTime" && right.Type.FullName == "NodaTime.LocalDateTime" || - left.Type.FullName == "NodaTime.LocalTime" && right.Type.FullName == "NodaTime.LocalTime") + case ExpressionType.Subtract + when left.Type.FullName == "NodaTime.LocalDateTime" && right.Type.FullName == "NodaTime.LocalDateTime" + || left.Type.FullName == "NodaTime.LocalTime" && right.Type.FullName == "NodaTime.LocalTime": { _nodaTimePeriodType ??= left.Type.Assembly.GetType("NodaTime.Period"); return (SqlBinaryExpression)ApplyTypeMapping( - new SqlBinaryExpression(operatorType, left, right, _nodaTimePeriodType!, null), typeMapping); + new SqlBinaryExpression(ExpressionType.Subtract, left, right, _nodaTimePeriodType!, null), typeMapping); } - if (left.Type.FullName == "NodaTime.LocalDate" && right.Type.FullName == "NodaTime.LocalDate") + case ExpressionType.Subtract + when left.Type.FullName == "NodaTime.LocalDate" && right.Type.FullName == "NodaTime.LocalDate": { return (SqlBinaryExpression)ApplyTypeMapping( - new SqlBinaryExpression(operatorType, left, right, typeof(int), null), typeMapping); + new SqlBinaryExpression(ExpressionType.Subtract, left, right, typeof(int), null), typeMapping); } } @@ -422,66 +424,56 @@ public virtual PostgresFunctionExpression AggregateFunction( private SqlBinaryExpression ApplyTypeMappingOnSqlBinary(SqlBinaryExpression binary, RelationalTypeMapping? typeMapping) { + var (left, right) = (binary.Left, binary.Right); + // The default SqlExpressionFactory behavior is to assume that the two added operands have the same type, // and so to infer one side's mapping from the other if needed. Here we take care of some heterogeneous // operand cases where this doesn't work: // * Period + Period (???) - if (binary.OperatorType is ExpressionType.Add or ExpressionType.Subtract) + switch (binary.OperatorType) { - var (left, right) = (binary.Left, binary.Right); - var leftType = left.Type.UnwrapNullableType(); - var rightType = right.Type.UnwrapNullableType(); - - // Note that we apply the given type mapping from above to the left operand (which has the same CLR type as - // the binary expression's) - // DateTime + TimeSpan => DateTime // DateTimeOffset + TimeSpan => DateTimeOffset // TimeOnly + TimeSpan => TimeOnly - if (rightType == typeof(TimeSpan) - && ( - leftType == typeof(DateTime) - || leftType == typeof(DateTimeOffset) - || leftType == typeof(TimeOnly) - ) - || rightType.FullName == "NodaTime.Period" - && leftType.FullName is "NodaTime.LocalDateTime" or "NodaTime.LocalDate" or "NodaTime.LocalTime" - || rightType.FullName == "NodaTime.Duration" - && leftType.FullName is "NodaTime.Instant" or "NodaTime.ZonedDateTime") + case ExpressionType.Add or ExpressionType.Subtract + when right.Type == typeof(TimeSpan) + && (left.Type == typeof(DateTime) || left.Type == typeof(DateTimeOffset) || left.Type == typeof(TimeOnly)) + || right.Type.FullName == "NodaTime.Period" + && left.Type.FullName is "NodaTime.LocalDateTime" or "NodaTime.LocalDate" or "NodaTime.LocalTime" + || right.Type.FullName == "NodaTime.Duration" + && left.Type.FullName is "NodaTime.Instant" or "NodaTime.ZonedDateTime": { var newLeft = ApplyTypeMapping(left, typeMapping); var newRight = ApplyDefaultTypeMapping(right); return new SqlBinaryExpression(binary.OperatorType, newLeft, newRight, binary.Type, newLeft.TypeMapping); } - if (binary.OperatorType == ExpressionType.Subtract) + // DateTime - DateTime => TimeSpan + // DateTimeOffset - DateTimeOffset => TimeSpan + // DateOnly - DateOnly => TimeSpan + // TimeOnly - TimeOnly => TimeSpan + // Instant - Instant => Duration + // LocalDateTime - LocalDateTime => int (days) + case ExpressionType.Subtract + when left.Type == typeof(DateTime) && right.Type == typeof(DateTime) + || left.Type == typeof(DateTimeOffset) && right.Type == typeof(DateTimeOffset) + || left.Type == typeof(DateOnly) && right.Type == typeof(DateOnly) + || left.Type == typeof(TimeOnly) && right.Type == typeof(TimeOnly) + || left.Type.FullName == "NodaTime.Instant" && right.Type.FullName == "NodaTime.Instant" + || left.Type.FullName == "NodaTime.LocalDateTime" && right.Type.FullName == "NodaTime.LocalDateTime" + || left.Type.FullName == "NodaTime.ZonedDateTime" && right.Type.FullName == "NodaTime.ZonedDateTime" + || left.Type.FullName == "NodaTime.LocalDate" && right.Type.FullName == "NodaTime.LocalDate" + || left.Type.FullName == "NodaTime.LocalTime" && right.Type.FullName == "NodaTime.LocalTime": { - // DateTime - DateTime => TimeSpan - // DateTimeOffset - DateTimeOffset => TimeSpan - // DateOnly - DateOnly => TimeSpan - // TimeOnly - TimeOnly => TimeSpan - // Instant - Instant => Duration - // LocalDateTime - LocalDateTime => int (days) - if (leftType == typeof(DateTime) && rightType == typeof(DateTime) - || leftType == typeof(DateTimeOffset) && rightType == typeof(DateTimeOffset) - || leftType == typeof(DateOnly) && rightType == typeof(DateOnly) - || leftType == typeof(TimeOnly) && rightType == typeof(TimeOnly) - || leftType.FullName == "NodaTime.Instant" && rightType.FullName == "NodaTime.Instant" - || leftType.FullName == "NodaTime.LocalDateTime" && rightType.FullName == "NodaTime.LocalDateTime" - || leftType.FullName == "NodaTime.ZonedDateTime" && rightType.FullName == "NodaTime.ZonedDateTime" - || leftType.FullName == "NodaTime.LocalDate" && rightType.FullName == "NodaTime.LocalDate" - || leftType.FullName == "NodaTime.LocalTime" && rightType.FullName == "NodaTime.LocalTime") - { - var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(left, right); - - return new SqlBinaryExpression( - ExpressionType.Subtract, - ApplyTypeMapping(left, inferredTypeMapping), - ApplyTypeMapping(right, inferredTypeMapping), - binary.Type, - typeMapping ?? _typeMappingSource.FindMapping(binary.Type, "interval")); - } + var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(left, right); + + return new SqlBinaryExpression( + ExpressionType.Subtract, + ApplyTypeMapping(left, inferredTypeMapping), + ApplyTypeMapping(right, inferredTypeMapping), + binary.Type, + typeMapping ?? _typeMappingSource.FindMapping(binary.Type, "interval")); } } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs index 0232b291a..a519c6bb0 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs @@ -127,7 +127,14 @@ public class NpgsqlInetTypeMapping : NpgsqlTypeMapping /// 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 NpgsqlInetTypeMapping() : base("inet", typeof(IPAddress), NpgsqlDbType.Inet) {} + public NpgsqlInetTypeMapping(Type clrType) + : base("inet", clrType, NpgsqlDbType.Inet) + { + if (clrType != typeof(IPAddress) && clrType != typeof(NpgsqlInet)) + { + throw new ArgumentException($"Only {nameof(IPAddress)} and {nameof(NpgsqlInet)} are supported", nameof(clrType)); + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -154,7 +161,7 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override string GenerateNonNullSqlLiteral(object value) - => $"INET '{(IPAddress)value}'"; + => $"INET '{value}'"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -163,9 +170,15 @@ protected override string GenerateNonNullSqlLiteral(object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override Expression GenerateCodeLiteral(object value) - => Expression.Call(ParseMethod, Expression.Constant(((IPAddress)value).ToString())); - - private static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", new[] { typeof(string) })!; + => value switch + { + IPAddress ip => Expression.Call(IPAddressParseMethod, Expression.Constant(ip.ToString())), + NpgsqlInet ip => Expression.New(NpgsqlInetConstructor, Expression.Constant(ip.ToString())), + _ => 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) })!; } /// @@ -182,7 +195,7 @@ public class NpgsqlCidrTypeMapping : NpgsqlTypeMapping /// 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 NpgsqlCidrTypeMapping() : base("cidr", typeof((IPAddress, int)), NpgsqlDbType.Cidr) {} + public NpgsqlCidrTypeMapping() : base("cidr", typeof(NpgsqlCidr), NpgsqlDbType.Cidr) {} /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -210,8 +223,8 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p /// protected override string GenerateNonNullSqlLiteral(object value) { - var cidr = ((IPAddress Address, int Subnet))value; - return $"CIDR '{cidr.Address}/{cidr.Subnet}'"; + var cidr = (NpgsqlCidr)value; + return $"CIDR '{cidr.Address}/{cidr.Netmask}'"; } /// @@ -222,15 +235,15 @@ protected override string GenerateNonNullSqlLiteral(object value) /// public override Expression GenerateCodeLiteral(object value) { - var cidr = ((IPAddress Address, int Subnet))value; + var cidr = (NpgsqlCidr)value; return Expression.New( - Constructor, + NpgsqlCidrConstructor, Expression.Call(ParseMethod, Expression.Constant(cidr.Address.ToString())), - Expression.Constant(cidr.Subnet)); + Expression.Constant(cidr.Netmask)); } private static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", new[] { typeof(string) })!; - private static readonly ConstructorInfo Constructor = - typeof((IPAddress, int)).GetConstructor(new[] { typeof(IPAddress), typeof(int) })!; + private static readonly ConstructorInfo NpgsqlCidrConstructor = + typeof(NpgsqlCidr).GetConstructor(new[] { typeof(IPAddress), typeof(byte) })!; } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 3c19ca620..d74bb9ade 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -107,7 +107,8 @@ static NpgsqlTypeMappingSource() // Network address types private readonly NpgsqlMacaddrTypeMapping _macaddr = new(); private readonly NpgsqlMacaddr8TypeMapping _macaddr8 = new(); - private readonly NpgsqlInetTypeMapping _inet = new(); + private readonly NpgsqlInetTypeMapping _inetAsIPAddress = new(typeof(IPAddress)); + private readonly NpgsqlInetTypeMapping _inetAsNpgsqlInet = new(typeof(NpgsqlInet)); private readonly NpgsqlCidrTypeMapping _cidr = new(); // Built-in geometric types @@ -255,7 +256,7 @@ public NpgsqlTypeMappingSource( { "macaddr", new[] { _macaddr } }, { "macaddr8", new[] { _macaddr8 } }, - { "inet", new[] { _inet } }, + { "inet", new RelationalTypeMapping[] { _inetAsIPAddress, _inetAsNpgsqlInet } }, { "cidr", new[] { _cidr } }, { "point", new[] { _point } }, @@ -327,8 +328,9 @@ public NpgsqlTypeMappingSource( { typeof(DateTimeOffset), _timestamptzDto }, { typeof(PhysicalAddress), _macaddr }, - { typeof(IPAddress), _inet }, - { typeof((IPAddress, int)), _cidr }, + { typeof(IPAddress), _inetAsIPAddress }, + { typeof(NpgsqlInet), _inetAsNpgsqlInet }, + { typeof(NpgsqlCidr), _cidr }, { typeof(BitArray), _varbit }, { typeof(ImmutableDictionary), _immutableHstore }, diff --git a/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs index 4df2eaa26..3763216df 100644 --- a/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/NetworkQueryNpgsqlTest.cs @@ -32,7 +32,7 @@ public NetworkQueryNpgsqlTest(NetworkAddressQueryNpgsqlFixture fixture, ITestOut public void Demonstrate_ValueTypeParametersAreDuplicated() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.ContainsOrEqual(x.Cidr, cidr)) @@ -41,11 +41,12 @@ public void Demonstrate_ValueTypeParametersAreDuplicated() AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__cidr_2='0.0.0.0/0' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) -SELECT n."Cidr" = @__cidr_1 +SELECT n."Cidr" = @__cidr_2 FROM "NetTestEntities" AS n -WHERE n."Cidr" >>= @__cidr_1 +WHERE n."Cidr" >>= @__p_1 """); } @@ -138,7 +139,7 @@ public void PhysicalAddress_macaddr_parse_parameter() #region RelationalOperatorTests [Fact] - public void IPAddress_inet_LessThan_inet() + public void LessThan_IPAddress() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.LessThan(x.Inet, IPAddress.Parse("192.168.1.7"))); @@ -153,26 +154,26 @@ WHERE n."Inet" < INET '192.168.1.7' } [Fact] - public void ValueTuple_cidr_LessThan_cidr() + public void LessThan_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.LessThan(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" < @__cidr_1 +WHERE n."Cidr" < @__p_1 """); } [Fact] - public void PhysicalAddress_macaddr_LessThan_macaddr() + public void LessThan_PhysicalAddress() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.LessThan(x.Macaddr, PhysicalAddress.Parse("12-34-56-00-00-07"))); @@ -188,7 +189,7 @@ WHERE n."Macaddr" < MACADDR '123456000007' [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_LessThan_macaddr8() + public void LessThan_PhysicalAddress_macaddr8() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.LessThan(x.Macaddr8, PhysicalAddress.Parse("08-00-2B-01-02-03-04-07"))); @@ -203,7 +204,7 @@ WHERE n."Macaddr8" < MACADDR8 '08002B0102030407' } [Fact] - public void IPAddress_inet_LessThanOrEqual_inet() + public void LessThanOrEqual_IPAddress() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.LessThanOrEqual(x.Inet, IPAddress.Parse("192.168.1.7"))); @@ -218,26 +219,26 @@ SELECT count(*)::int } [Fact] - public void ValueTuple_cidr_LessThanOrEqual_cidr() + public void LessThanOrEqual_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.LessThanOrEqual(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" <= @__cidr_1 +WHERE n."Cidr" <= @__p_1 """); } [Fact] - public void PhysicalAddress_macaddr_LessThanOrEqual_macaddr() + public void LessThanOrEqual_PhysicalAddress() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.LessThanOrEqual(x.Macaddr, PhysicalAddress.Parse("12-34-56-00-00-07"))); @@ -253,7 +254,7 @@ SELECT count(*)::int [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_LessThanOrEqual_macaddr8() + public void LessThanOrEqual_PhysicalAddress_macaddr8() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.LessThanOrEqual(x.Macaddr8, PhysicalAddress.Parse("08-00-2B-01-02-03-04-07"))); @@ -268,7 +269,7 @@ SELECT count(*)::int } [Fact] - public void IPAddress_inet_GreaterThanOrEqual_inet() + public void GreaterThanOrEqual_IPAddress() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.GreaterThanOrEqual(x.Inet, IPAddress.Parse("192.168.1.7"))); @@ -283,26 +284,26 @@ SELECT count(*)::int } [Fact] - public void ValueTuple_cidr_GreaterThanOrEqual_cidr() + public void GreaterThanOrEqual_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.GreaterThanOrEqual(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" >= @__cidr_1 +WHERE n."Cidr" >= @__p_1 """); } [Fact] - public void PhysicalAddress_macaddr_GreaterThanOrEqual_macaddr() + public void GreaterThanOrEqual_PhysicalAddress() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.GreaterThanOrEqual(x.Macaddr, PhysicalAddress.Parse("12-34-56-00-00-07"))); @@ -318,7 +319,7 @@ SELECT count(*)::int [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_GreaterThanOrEqual_macaddr8() + public void GreaterThanOrEqual_PhysicalAddress_macaddr8() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.GreaterThanOrEqual(x.Macaddr8, PhysicalAddress.Parse("08-00-2B-01-02-03-04-07"))); @@ -333,7 +334,7 @@ SELECT count(*)::int } [Fact] - public void IPAddress_inet_GreaterThan_inet() + public void GreaterThan_IPAddress() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.GreaterThan(x.Inet, IPAddress.Parse("192.168.1.7"))); @@ -348,26 +349,26 @@ WHERE n."Inet" > INET '192.168.1.7' } [Fact] - public void ValueTuple_cidr_GreaterThan_cidr() + public void GreaterThan_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.GreaterThan(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" > @__cidr_1 +WHERE n."Cidr" > @__p_1 """); } [Fact] - public void PhysicalAddress_macaddr_GreaterThan_macaddr() + public void GreaterThan_PhysicalAddress() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.GreaterThan(x.Macaddr, PhysicalAddress.Parse("12-34-56-00-00-07"))); @@ -383,7 +384,7 @@ WHERE n."Macaddr" > MACADDR '123456000007' [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_GreaterThan_macaddr8() + public void GreaterThan_PhysicalAddress_macaddr8() { using var context = CreateContext(); var count = context.NetTestEntities.Count(x => EF.Functions.GreaterThan(x.Macaddr8, PhysicalAddress.Parse("08-00-2B-01-02-03-04-07"))); @@ -402,7 +403,7 @@ WHERE n."Macaddr8" > MACADDR8 '08002B0102030407' #region ContainmentOperatorTests [Fact] - public void IPAddress_inet_ContainedBy_inet() + public void ContainedBy_IPAddress_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -412,54 +413,54 @@ public void IPAddress_inet_ContainedBy_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Inet" << @__inet_1 +WHERE n."Inet" << @__p_1 """); } [Fact] - public void IPAddress_inet_ContainedBy_cidr() + public void ContainedBy_IPAddress_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.ContainedBy(x.Inet, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Inet" << @__cidr_1 +WHERE n."Inet" << @__p_1 """); } [Fact] - public void ValueTuple_cidr_ContainedBy_cidr() + public void ContainedBy_NpgsqlCidr_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.ContainedBy(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" << @__cidr_1 +WHERE n."Cidr" << @__p_1 """); } [Fact] - public void IPAddress_inet_ContainedByOrEqual_inet() + public void ContainedByOrEqual_IPAddress_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -469,54 +470,54 @@ public void IPAddress_inet_ContainedByOrEqual_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Inet" <<= @__inet_1 +WHERE n."Inet" <<= @__p_1 """); } [Fact] - public void IPAddress_inet_ContainedByOrEqual_cidr() + public void ContainedByOrEqual_IPAddress_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.ContainedByOrEqual(x.Inet, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Inet" <<= @__cidr_1 +WHERE n."Inet" <<= @__p_1 """); } [Fact] - public void ValueTuple_cidr_ContainedByOrEqual_cidr() + public void ContainedByOrEqual_NpgsqlCidr_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.ContainedByOrEqual(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" <<= @__cidr_1 +WHERE n."Cidr" <<= @__p_1 """); } [Fact] - public void IPAddress_inet_Contains_inet() + public void Contains_IPAddress_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -526,16 +527,16 @@ public void IPAddress_inet_Contains_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Inet" >> @__inet_1 +WHERE n."Inet" >> @__p_1 """); } [Fact] - public void ValueTuple_cidr_Contains_inet() + public void Contains_NpgsqlCidr_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -545,35 +546,35 @@ public void ValueTuple_cidr_Contains_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" >> @__inet_1 +WHERE n."Cidr" >> @__p_1 """); } [Fact] - public void ValueTuple_cidr_Contains_cidr() + public void Contains_NpgsqlCidr_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.Contains(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" >> @__cidr_1 +WHERE n."Cidr" >> @__p_1 """); } [Fact] - public void IPAddress_inet_ContainsOrEqual_inet() + public void ContainsOrEqual_IPAddress_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -583,16 +584,16 @@ public void IPAddress_inet_ContainsOrEqual_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Inet" >>= @__inet_1 +WHERE n."Inet" >>= @__p_1 """); } [Fact] - public void ValueTuple_cidr_ContainsOrEqual_inet() + public void ContainsOrEqual_NpgsqlCidr_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -602,35 +603,35 @@ public void ValueTuple_cidr_ContainsOrEqual_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" >>= @__inet_1 +WHERE n."Cidr" >>= @__p_1 """); } [Fact] - public void ValueTuple_cidr_ContainsOrEqual_cidr() + public void ContainsOrEqual_NpgsqlCidr_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.ContainsOrEqual(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" >>= @__cidr_1 +WHERE n."Cidr" >>= @__p_1 """); } [Fact] - public void IPAddress_inet_ContainsOrContainedBy_inet() + public void ContainsOrContainedBy_IPAddress_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -640,35 +641,35 @@ public void IPAddress_inet_ContainsOrContainedBy_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Inet" && @__inet_1 +WHERE n."Inet" && @__p_1 """); } [Fact] - public void IPAddress_inet_ContainsOrContainedBy_cidr() + public void ContainsOrContainedBy_IPAddress_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.ContainsOrContainedBy(x.Inet, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Inet" && @__cidr_1 +WHERE n."Inet" && @__p_1 """); } [Fact] - public void ValueTuple_cidr_ContainsOrContainedBy_inet() + public void ContainsOrContainedBy_NpgsqlCidr_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -678,30 +679,30 @@ public void ValueTuple_cidr_ContainsOrContainedBy_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" && @__inet_1 +WHERE n."Cidr" && @__p_1 """); } [Fact] - public void ValueTuple_cidr_ContainsOrContainedBy_cidr() + public void ContainsOrContainedBy_NpgsqlCidr_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Where(x => EF.Functions.ContainsOrContainedBy(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) SELECT n."Id", n."Cidr", n."Inet", n."Macaddr", n."Macaddr8", n."TextInet", n."TextMacaddr" FROM "NetTestEntities" AS n -WHERE n."Cidr" && @__cidr_1 +WHERE n."Cidr" && @__p_1 """); } @@ -710,7 +711,7 @@ WHERE n."Cidr" && @__cidr_1 #region BitwiseOperatorTests [Fact] - public void IPAddress_inet_BitwiseNot() + public void BitwiseNot_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -725,7 +726,7 @@ public void IPAddress_inet_BitwiseNot() } [Fact] - public void ValueTuple_cidr_BitwiseNot() + public void BitwiseNot_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -740,7 +741,7 @@ public void ValueTuple_cidr_BitwiseNot() } [Fact] - public void PhysicalAddress_macaddr_BitwiseNot() + public void BitwiseNot_PhysicalAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -756,7 +757,7 @@ public void PhysicalAddress_macaddr_BitwiseNot() [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_BitwiseNot() + public void BitwiseNot_PhysicalAddress_macaddr8() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -771,7 +772,7 @@ public void PhysicalAddress_macaddr8_BitwiseNot() } [Fact] - public void IPAddress_inet_BitwiseAnd_inet() + public void BitwiseAnd_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -780,34 +781,34 @@ public void IPAddress_inet_BitwiseAnd_inet() Assert.Equal(0, count); AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) SELECT count(*)::int FROM "NetTestEntities" AS n -WHERE n."Inet" = n."Inet" & @__inet_1 OR n."Inet" IS NULL +WHERE n."Inet" = n."Inet" & @__p_1 OR n."Inet" IS NULL """); } [Fact] - public void ValueTuple_cidr_BitwiseAnd_cidr() + public void BitwiseAnd_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Select(x => EF.Functions.BitwiseAnd(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) -SELECT n."Cidr" & @__cidr_1 +SELECT n."Cidr" & @__p_1 FROM "NetTestEntities" AS n """); } [Fact] - public void PhysicalAddress_macaddr_BitwiseAnd_macaddr() + public void BitwiseAnd_PhysicalAddress() { using var context = CreateContext(); var macaddr = new PhysicalAddress(new byte[6]); @@ -826,7 +827,7 @@ SELECT n."Macaddr" & @__macaddr_1 [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_BitwiseAnd_macaddr8() + public void BitwiseAnd_PhysicalAddress_macaddr8() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -841,7 +842,7 @@ SELECT n."Macaddr8" & n."Macaddr8" } [Fact] - public void IPAddress_inet_BitwiseOr_inet() + public void BitwiseOr_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -851,33 +852,33 @@ public void IPAddress_inet_BitwiseOr_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) -SELECT n."Inet" | @__inet_1 +SELECT n."Inet" | @__p_1 FROM "NetTestEntities" AS n """); } [Fact] - public void ValueTuple_cidr_BitwiseOr_cidr() + public void BitwiseOr_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Select(x => EF.Functions.BitwiseOr(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) -SELECT n."Cidr" | @__cidr_1 +SELECT n."Cidr" | @__p_1 FROM "NetTestEntities" AS n """); } [Fact] - public void PhysicalAddress_macaddr_BitwiseOr_macaddr() + public void BitwiseOr_PhysicalAddress() { using var context = CreateContext(); var macaddr = new PhysicalAddress(new byte[6]); @@ -896,7 +897,7 @@ SELECT n."Macaddr" | @__macaddr_1 [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_BitwiseOr_macaddr8() + public void BitwiseOr_PhysicalAddress_macaddr8() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -915,7 +916,7 @@ SELECT n."Macaddr8" | n."Macaddr8" #region ArithmeticOperatorTests [Fact] - public void IPAddress_inet_Add_int() + public void Add_IPAddress_and_int() { using var context = CreateContext(); var actual = context.NetTestEntities.Single(x => EF.Functions.Add(x.Inet, 1) == IPAddress.Parse("192.168.1.2")).Inet; @@ -931,7 +932,7 @@ LIMIT 2 } [Fact] - public void ValueTuple_cidr_Add_int() + public void Add_NpgsqlCidr_and_int() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -946,7 +947,7 @@ SELECT n."Cidr" + 1 } [Fact] - public void IPAddress_inet_Subtract_int() + public void Subtract_IPAddress_and_int() { using var context = CreateContext(); var actual = context.NetTestEntities.Single(x => EF.Functions.Subtract(x.Inet, 1) == IPAddress.Parse("192.168.1.1")).Inet; @@ -962,7 +963,7 @@ LIMIT 2 } [Fact] - public void ValueTuple_cidr_Subtract_int() + public void Subtract_NpgsqlCidr_and_int() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -977,7 +978,7 @@ SELECT n."Cidr" - 1 } [Fact] - public void IPAddress_inet_Subtract_inet() + public void Subtract_IPAddress_and_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -987,27 +988,27 @@ public void IPAddress_inet_Subtract_inet() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) -SELECT n."Inet" - @__inet_1 +SELECT n."Inet" - @__p_1 FROM "NetTestEntities" AS n """); } [Fact] - public void ValueTuple_cidr_Subtract_cidr() + public void Subtract_NpgsqlCidr_and_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Select(x => EF.Functions.Subtract(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) -SELECT n."Cidr" - @__cidr_1 +SELECT n."Cidr" - @__p_1 FROM "NetTestEntities" AS n """); } @@ -1017,7 +1018,7 @@ SELECT n."Cidr" - @__cidr_1 #region FunctionTests [Fact] - public void IPAddress_inet_Abbreviate() + public void Abbreviate_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1032,7 +1033,7 @@ SELECT abbrev(n."Inet") } [Fact] - public void ValueTuple_cidr_Abbreviate() + public void Abbreviate_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1047,7 +1048,7 @@ SELECT abbrev(n."Cidr") } [Fact] - public void IPAddress_inet_Broadcast() + public void Broadcast_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1062,7 +1063,7 @@ SELECT broadcast(n."Inet") } [Fact] - public void ValueTuple_cidr_Broadcast() + public void Broadcast_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1077,7 +1078,7 @@ SELECT broadcast(n."Cidr") } [Fact] - public void IPAddress_inet_Family() + public void Family_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1092,7 +1093,7 @@ SELECT family(n."Inet") } [Fact] - public void ValueTuple_cidr_Family() + public void Family_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1107,7 +1108,7 @@ SELECT family(n."Cidr") } [Fact] - public void IPAddress_inet_Host() + public void Host_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1122,7 +1123,7 @@ SELECT host(n."Inet") } [Fact] - public void ValueTuple_cidr_Host() + public void Host_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1137,7 +1138,7 @@ SELECT host(n."Cidr") } [Fact] - public void IPAddress_inet_HostMask() + public void HostMask_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1152,7 +1153,7 @@ SELECT hostmask(n."Inet") } [Fact] - public void ValueTuple_cidr_HostMask() + public void HostMask_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1167,7 +1168,7 @@ SELECT hostmask(n."Cidr") } [Fact] - public void IPAddress_inet_MaskLength() + public void MaskLength_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1182,7 +1183,7 @@ SELECT masklen(n."Inet") } [Fact] - public void ValueTuple_cidr_MaskLength() + public void MaskLength_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1197,7 +1198,7 @@ SELECT masklen(n."Cidr") } [Fact] - public void IPAddress_inet_Netmask() + public void Netmask_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1212,7 +1213,7 @@ SELECT netmask(n."Inet") } [Fact] - public void ValueTuple_cidr_Netmask() + public void Netmask_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1227,7 +1228,7 @@ SELECT netmask(n."Cidr") } [Fact] - public void IPAddress_inet_Network() + public void Network_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1242,7 +1243,7 @@ SELECT network(n."Inet") } [Fact] - public void ValueTuple_cidr_Network() + public void Network_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1257,7 +1258,7 @@ SELECT network(n."Cidr") } [Fact] - public void IPAddress_inet_SetMaskLength() + public void SetMaskLength_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1272,7 +1273,7 @@ SELECT set_masklen(n."Inet", 0) } [Fact] - public void ValueTuple_cidr_SetMaskLength() + public void SetMaskLength_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1287,7 +1288,7 @@ SELECT set_masklen(n."Cidr", 0) } [Fact] - public void IPAddress_inet_Text() + public void Text_IPAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1302,7 +1303,7 @@ SELECT text(n."Inet") } [Fact] - public void ValueTuple_cidr_Text() + public void Text_NpgsqlCidr() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1317,7 +1318,7 @@ SELECT text(n."Cidr") } [Fact] - public void IPAddress_inet_SameFamily() + public void SameFamily_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -1327,33 +1328,33 @@ public void IPAddress_inet_SameFamily() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) -SELECT inet_same_family(n."Inet", @__inet_1) +SELECT inet_same_family(n."Inet", @__p_1) FROM "NetTestEntities" AS n """); } [Fact] - public void ValueTuple_cidr_SameFamily() + public void SameFamily_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Select(x => EF.Functions.SameFamily(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) -SELECT inet_same_family(n."Cidr", @__cidr_1) +SELECT inet_same_family(n."Cidr", @__p_1) FROM "NetTestEntities" AS n """); } [Fact] - public void IPAddress_inet_Merge() + public void Merge_IPAddress() { using var context = CreateContext(); var inet = IPAddress.Any; @@ -1363,33 +1364,33 @@ public void IPAddress_inet_Merge() AssertSql( """ -@__inet_1='0.0.0.0' (DbType = Object) +@__p_1='0.0.0.0' (DbType = Object) -SELECT inet_merge(n."Inet", @__inet_1) +SELECT inet_merge(n."Inet", @__p_1) FROM "NetTestEntities" AS n """); } [Fact] - public void ValueTuple_cidr_Merge() + public void Merge_NpgsqlCidr() { using var context = CreateContext(); - (IPAddress Address, int Subnet) cidr = (IPAddress.Any, default); + var cidr = new NpgsqlCidr(IPAddress.Any, default); var _ = context.NetTestEntities .Select(x => EF.Functions.Merge(x.Cidr, cidr)) .ToArray(); AssertSql( """ -@__cidr_1='(0.0.0.0, 0)' (DbType = Object) +@__p_1='0.0.0.0/0' (DbType = Object) -SELECT inet_merge(n."Cidr", @__cidr_1) +SELECT inet_merge(n."Cidr", @__p_1) FROM "NetTestEntities" AS n """); } [Fact] - public void PhysicalAddress_macaddr_Truncate() + public void Truncate_PhysicalAddress() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1405,7 +1406,7 @@ SELECT trunc(n."Macaddr") [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_Truncate() + public void Truncate_PhysicalAddress_macaddr8() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1421,7 +1422,7 @@ SELECT trunc(n."Macaddr8") [ConditionalFact] [MinimumPostgresVersion(10, 0)] - public void PhysicalAddress_macaddr8_Set7BitMac8() + public void Set7BitMac8_PhysicalAddress_macaddr8() { using var context = CreateContext(); var _ = context.NetTestEntities @@ -1470,7 +1471,7 @@ public class NetTestEntity /// /// The network address. /// - public (IPAddress Address, int Subnet) Cidr { get; set; } + public NpgsqlCidr Cidr { get; set; } /// /// The MAC address. @@ -1534,7 +1535,7 @@ public static void Seed(NetContext context) { Id = i, Inet = ip, - Cidr = (Address: IPAddress.Parse("192.168.1.0"), Subnet: 24), + Cidr = new NpgsqlCidr(IPAddress.Parse("192.168.1.0"), 24), Macaddr = macaddr, Macaddr8 = macaddr8, TextInet = ip.ToString(), diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs index 5f36d907d..2555e99b8 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs @@ -256,11 +256,13 @@ public void GenerateCodeLiteral_returns_inet_literal() [Fact] public void GenerateSqlLiteral_returns_cidr_literal() - => Assert.Equal("CIDR '192.168.1.0/24'", GetMapping("cidr").GenerateSqlLiteral((IPAddress.Parse("192.168.1.0"), 24))); + => Assert.Equal("CIDR '192.168.1.0/24'", GetMapping("cidr").GenerateSqlLiteral(new NpgsqlCidr(IPAddress.Parse("192.168.1.0"), 24))); [Fact] public void GenerateCodeLiteral_returns_cidr_literal() - => Assert.Equal(@"(System.Net.IPAddress.Parse(""192.168.1.0""), 24)", CodeLiteral((IPAddress.Parse("192.168.1.0"), 24))); + => Assert.Equal( + @"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 From 706ca88aabd523a8bedcaf021df23c1932708e62 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 11 Oct 2023 13:16:28 +0200 Subject: [PATCH 10/10] Fix array inference and parameter logic --- .../Query/NpgsqlSqlExpressionFactory.cs | 2 +- .../Internal/Mapping/NpgsqlArrayTypeMapping.cs | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs index 40ff6e5f2..d84ee50ce 100644 --- a/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs +++ b/src/EFCore.PG/Query/NpgsqlSqlExpressionFactory.cs @@ -630,7 +630,7 @@ private SqlExpression ApplyTypeMappingOnAll(PostgresAllExpression postgresAllExp // Special-case arrays of objects, not taking the array CLR type into account in the lookup (it would never succeed). // Note that we provide both the array CLR type *and* an array store type constructed from the element's store type. // If we use only the array CLR type, byte[] will yield bytea which we don't want. - arrayMapping = arrayExpression.Type == typeof(object[]) || arrayExpression.Type == typeof(List) + arrayMapping = arrayExpression.Type.TryGetSequenceType() == typeof(object) ? _typeMappingSource.FindMapping(itemMapping.StoreType + "[]") : _typeMappingSource.FindMapping(arrayExpression.Type, itemMapping.StoreType + "[]"); } diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs index 672a0d9ef..9323f0839 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs @@ -165,10 +165,8 @@ protected NpgsqlArrayTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { var clrType = parameters.CoreParameters.ClrType; - Type? arrayElementClrType; - if ((arrayElementClrType = clrType.TryGetElementType(typeof(IEnumerable<>))) == null - && (arrayElementClrType = clrType.GetElementType()) == null) + if (clrType.TryGetElementType(typeof(IEnumerable<>)) == null && clrType.GetElementType() == null) { throw new ArgumentException($"CLR type '{parameters.CoreParameters.ClrType}' isn't an IEnumerable"); } @@ -201,9 +199,18 @@ public override DbParameter CreateParameter( // 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. // TODO: Make Npgsql support IList<> instead of only arrays and List<> - if (value is IEnumerable elements && !(elements.GetType().IsArray || elements is List)) + if (value is not null && !value.GetType().IsArrayOrGenericList()) { - value = elements.ToList(); + switch (value) + { + case IEnumerable elements: + value = elements.ToList(); + break; + + case IEnumerable elements: + value = elements.Cast().ToList(); + break; + } } return base.CreateParameter(command, name, value, nullable, direction);