diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrLegacyTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrLegacyTypeMapping.cs new file mode 100644 index 000000000..683cd54aa --- /dev/null +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrLegacyTypeMapping.cs @@ -0,0 +1,125 @@ +using System.Net; +using System.Text.Json; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; + +/// +/// The type mapping for the PostgreSQL cidr type. +/// +/// +/// See: https://www.postgresql.org/docs/current/static/datatype-net-types.html#DATATYPE-CIDR +/// +public class NpgsqlLegacyCidrTypeMapping : NpgsqlTypeMapping +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static NpgsqlLegacyCidrTypeMapping Default { get; } = new(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public NpgsqlLegacyCidrTypeMapping() + : base("cidr", typeof(NpgsqlCidr), NpgsqlDbType.Cidr, JsonCidrLegacyReaderWriter.Instance) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected NpgsqlLegacyCidrTypeMapping(RelationalTypeMappingParameters parameters) + : base(parameters, NpgsqlDbType.Cidr) + { + } + + /// + /// 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 RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new NpgsqlLegacyCidrTypeMapping(parameters); + + /// + /// 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 string GenerateNonNullSqlLiteral(object value) + { + var cidr = (NpgsqlCidr)value; + return $"CIDR '{cidr.Address}/{cidr.Netmask}'"; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override Expression GenerateCodeLiteral(object value) + { + var cidr = (NpgsqlCidr)value; + return Expression.New( + NpgsqlCidrConstructor, + Expression.Call(ParseMethod, Expression.Constant(cidr.Address.ToString())), + Expression.Constant(cidr.Netmask)); + } + + private static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", [typeof(string)])!; + + private static readonly ConstructorInfo NpgsqlCidrConstructor = + typeof(NpgsqlCidr).GetConstructor([typeof(IPAddress), typeof(byte)])!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public sealed class JsonCidrLegacyReaderWriter : JsonValueReaderWriter + { + private static readonly PropertyInfo InstanceProperty = typeof(JsonCidrLegacyReaderWriter).GetProperty(nameof(Instance))!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static JsonCidrLegacyReaderWriter Instance { get; } = new(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override NpgsqlCidr FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) + => new(manager.CurrentReader.GetString()!); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlCidr value) + => writer.WriteStringValue(value.ToString()); + + /// + public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); + } +} diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs index 7a943d887..86c4c6ef2 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCidrTypeMapping.cs @@ -27,7 +27,7 @@ public class NpgsqlCidrTypeMapping : NpgsqlTypeMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public NpgsqlCidrTypeMapping() - : base("cidr", typeof(NpgsqlCidr), NpgsqlDbType.Cidr, JsonCidrReaderWriter.Instance) + : base("cidr", typeof(IPNetwork), NpgsqlDbType.Cidr, JsonCidrReaderWriter.Instance) { } @@ -59,8 +59,8 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p /// protected override string GenerateNonNullSqlLiteral(object value) { - var cidr = (NpgsqlCidr)value; - return $"CIDR '{cidr.Address}/{cidr.Netmask}'"; + var ipNetwork = (IPNetwork)value; + return $"CIDR '{ipNetwork.BaseAddress}/{ipNetwork.PrefixLength}'"; } /// @@ -71,17 +71,17 @@ protected override string GenerateNonNullSqlLiteral(object value) /// public override Expression GenerateCodeLiteral(object value) { - var cidr = (NpgsqlCidr)value; + var cidr = (IPNetwork)value; return Expression.New( NpgsqlCidrConstructor, - Expression.Call(ParseMethod, Expression.Constant(cidr.Address.ToString())), - Expression.Constant(cidr.Netmask)); + Expression.Call(ParseMethod, Expression.Constant(cidr.BaseAddress.ToString())), + Expression.Constant(cidr.PrefixLength)); } private static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", [typeof(string)])!; private static readonly ConstructorInfo NpgsqlCidrConstructor = - typeof(NpgsqlCidr).GetConstructor([typeof(IPAddress), typeof(byte)])!; + typeof(IPNetwork).GetConstructor([typeof(IPAddress), typeof(int)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -89,7 +89,7 @@ public override Expression GenerateCodeLiteral(object value) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public sealed class JsonCidrReaderWriter : JsonValueReaderWriter + public sealed class JsonCidrReaderWriter : JsonValueReaderWriter { private static readonly PropertyInfo InstanceProperty = typeof(JsonCidrReaderWriter).GetProperty(nameof(Instance))!; @@ -107,8 +107,8 @@ public sealed class JsonCidrReaderWriter : JsonValueReaderWriter /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override NpgsqlCidr FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) - => new(manager.CurrentReader.GetString()!); + public override IPNetwork FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) + => IPNetwork.Parse(manager.CurrentReader.GetString()!); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -116,8 +116,8 @@ public override NpgsqlCidr FromJsonTyped(ref Utf8JsonReaderManager manager, obje /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override void ToJsonTyped(Utf8JsonWriter writer, NpgsqlCidr value) - => writer.WriteStringValue(value.ToString()); + public override void ToJsonTyped(Utf8JsonWriter writer, IPNetwork ipNetwork) + => writer.WriteStringValue(ipNetwork.ToString()); /// public override Expression ConstructorExpression => Expression.Property(null, InstanceProperty); diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index d1b922231..a259c2020 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -114,7 +114,8 @@ static NpgsqlTypeMappingSource() private readonly NpgsqlMacaddr8TypeMapping _macaddr8 = NpgsqlMacaddr8TypeMapping.Default; private readonly NpgsqlInetTypeMapping _inetAsIPAddress = NpgsqlInetTypeMapping.Default; private readonly NpgsqlInetTypeMapping _inetAsNpgsqlInet = new(typeof(NpgsqlInet)); - private readonly NpgsqlCidrTypeMapping _cidr = NpgsqlCidrTypeMapping.Default; + private readonly NpgsqlCidrTypeMapping _cidrAsIPNetwork = NpgsqlCidrTypeMapping.Default; + private readonly NpgsqlLegacyCidrTypeMapping _cidrAsNpgsqlCidr = NpgsqlLegacyCidrTypeMapping.Default; // Built-in geometric types private readonly NpgsqlPointTypeMapping _point = NpgsqlPointTypeMapping.Default; @@ -257,7 +258,7 @@ public NpgsqlTypeMappingSource( { "macaddr", [_macaddr] }, { "macaddr8", [_macaddr8] }, { "inet", [_inetAsIPAddress, _inetAsNpgsqlInet] }, - { "cidr", [_cidr] }, + { "cidr", [_cidrAsIPNetwork, _cidrAsNpgsqlCidr] }, { "point", [_point] }, { "box", [_box] }, { "line", [_line] }, @@ -320,7 +321,8 @@ public NpgsqlTypeMappingSource( { typeof(PhysicalAddress), _macaddr }, { typeof(IPAddress), _inetAsIPAddress }, { typeof(NpgsqlInet), _inetAsNpgsqlInet }, - { typeof(NpgsqlCidr), _cidr }, + { typeof(IPNetwork), _cidrAsIPNetwork }, + { typeof(NpgsqlCidr), _cidrAsNpgsqlCidr }, { typeof(BitArray), _varbit }, { typeof(ImmutableDictionary), _immutableHstore }, { typeof(Dictionary), _hstore }, diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs index e95bbdd19..622405946 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -1,3 +1,5 @@ +using System.Net; +using System.Net.NetworkInformation; using Microsoft.EntityFrameworkCore.Storage.Json; using NetTopologySuite.Geometries; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; @@ -41,6 +43,7 @@ public class NpgsqlTypeMappingSourceTest [InlineData("geometry(POLYGONM)", typeof(Polygon), null, null, null, false)] [InlineData("xid", typeof(uint), null, null, null, false)] [InlineData("xid8", typeof(ulong), null, null, null, false)] + [InlineData("cidr", typeof(IPNetwork), null, null, null, false)] public void By_StoreType(string typeName, Type type, int? size, int? precision, int? scale, bool fixedLength) { var mapping = CreateTypeMappingSource().FindMapping(typeName); @@ -114,6 +117,10 @@ public void Timestamp_without_time_zone_Array_5() [InlineData(typeof(List>), "int4multirange")] [InlineData(typeof(Geometry), "geometry")] [InlineData(typeof(Point), "geometry")] + [InlineData(typeof(IPAddress), "inet")] + [InlineData(typeof(IPNetwork), "cidr")] + [InlineData(typeof(NpgsqlCidr), "cidr")] // legacy + [InlineData(typeof(PhysicalAddress), "macaddr")] public void By_ClrType(Type clrType, string expectedStoreType) { var mapping = CreateTypeMappingSource().FindMapping(clrType); diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs index ea8f3663a..4db128a9c 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs @@ -282,10 +282,20 @@ public void GenerateCodeLiteral_returns_inet_literal() [Fact] public void GenerateSqlLiteral_returns_cidr_literal() + => Assert.Equal("CIDR '192.168.1.0/24'", GetMapping("cidr").GenerateSqlLiteral(new IPNetwork(IPAddress.Parse("192.168.1.0"), 24))); + + [Fact] + public void GenerateSqlLiteral_returns_legacy_cidr_literal() => 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( + """new System.Net.IPNetwork(System.Net.IPAddress.Parse("192.168.1.0"), 24)""", + CodeLiteral(new IPNetwork(IPAddress.Parse("192.168.1.0"), 24))); + + [Fact] + public void GenerateCodeLiteral_returns_legacy_cidr_literal() => 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)));