diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs index ef2563253..08621113e 100644 --- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs +++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs @@ -460,13 +460,11 @@ protected virtual Expression VisitPostgresBinary(PostgresBinaryExpression binary .Append(binaryExpression.OperatorType switch { PostgresExpressionType.Contains - when binaryExpression.Left.TypeMapping is NpgsqlInetTypeMapping || - binaryExpression.Left.TypeMapping is NpgsqlCidrTypeMapping + when binaryExpression.Left.TypeMapping is NpgsqlInetTypeMapping => ">>", PostgresExpressionType.ContainedBy - when binaryExpression.Left.TypeMapping is NpgsqlInetTypeMapping || - binaryExpression.Left.TypeMapping is NpgsqlCidrTypeMapping + when binaryExpression.Left.TypeMapping is NpgsqlInetTypeMapping => "<<", PostgresExpressionType.Contains => "@>", diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs index 0232b291a..b2da33182 100644 --- a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs +++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs @@ -114,7 +114,7 @@ public override Expression GenerateCodeLiteral(object value) } /// -/// The type mapping for the PostgreSQL inet type. +/// The type mapping for the PostgreSQL inet and cidr types. /// /// /// See: https://www.postgresql.org/docs/current/static/datatype-net-types.html#DATATYPE-INET @@ -127,7 +127,7 @@ 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(string storeType, Type clrType, NpgsqlDbType npgsqlDbType) : base(storeType, clrType, npgsqlDbType) {} /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -135,8 +135,8 @@ public NpgsqlInetTypeMapping() : base("inet", typeof(IPAddress), NpgsqlDbType.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 NpgsqlInetTypeMapping(RelationalTypeMappingParameters parameters) - : base(parameters, NpgsqlDbType.Inet) {} + protected NpgsqlInetTypeMapping(RelationalTypeMappingParameters parameters, NpgsqlDbType npgsqlDbType) + : base(parameters, npgsqlDbType) {} /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -145,62 +145,7 @@ protected NpgsqlInetTypeMapping(RelationalTypeMappingParameters parameters) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) - => new NpgsqlInetTypeMapping(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) - => $"INET '{(IPAddress)value}'"; - - /// - /// 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) - => Expression.Call(ParseMethod, Expression.Constant(((IPAddress)value).ToString())); - - private static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", new[] { typeof(string) })!; -} - -/// -/// The type mapping for the PostgreSQL cidr type. -/// -/// -/// See: https://www.postgresql.org/docs/current/static/datatype-net-types.html#DATATYPE-CIDR -/// -public class NpgsqlCidrTypeMapping : 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 NpgsqlCidrTypeMapping() : base("cidr", typeof((IPAddress, int)), 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 NpgsqlCidrTypeMapping(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 NpgsqlCidrTypeMapping(parameters); + => new NpgsqlInetTypeMapping(parameters, NpgsqlDbType); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -210,8 +155,18 @@ 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}'"; + switch (value) + { + case (IPAddress address, int subnet): + return $"{StoreType.ToUpperInvariant()} '{address}/{subnet}'"; + + case IPAddress address: + return $"INET '{address}'"; + + default: + throw new InvalidCastException( + $"Attempted to generate {StoreType} literal for type {value.GetType()}"); + } } /// @@ -222,15 +177,18 @@ protected override string GenerateNonNullSqlLiteral(object value) /// public override Expression GenerateCodeLiteral(object value) { - var cidr = ((IPAddress Address, int Subnet))value; - return Expression.New( - Constructor, - Expression.Call(ParseMethod, Expression.Constant(cidr.Address.ToString())), - Expression.Constant(cidr.Subnet)); + if (value is (IPAddress address, int subnet)) + { + return Expression.New( + MaskConstructor, + Expression.Call(ParseMethod, Expression.Constant(address.ToString())), + Expression.Constant(subnet)); + } + + return Expression.Call(ParseMethod, Expression.Constant(((IPAddress)value).ToString())); } private static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", new[] { typeof(string) })!; - - private static readonly ConstructorInfo Constructor = + private static readonly ConstructorInfo MaskConstructor = typeof((IPAddress, int)).GetConstructor(new[] { typeof(IPAddress), typeof(int) })!; } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index b0cbf226a..0778f9cf9 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -117,8 +117,9 @@ static NpgsqlTypeMappingSource() // Network address types private readonly NpgsqlMacaddrTypeMapping _macaddr = new(); private readonly NpgsqlMacaddr8TypeMapping _macaddr8 = new(); - private readonly NpgsqlInetTypeMapping _inet = new(); - private readonly NpgsqlCidrTypeMapping _cidr = new(); + private readonly NpgsqlInetTypeMapping _inet = new("inet", typeof(IPAddress), NpgsqlDbType.Inet); + private readonly NpgsqlInetTypeMapping _inetMask = new("inet", typeof((IPAddress, int)), NpgsqlDbType.Inet); + private readonly NpgsqlInetTypeMapping _cidr = new("cidr", typeof((IPAddress, int)), NpgsqlDbType.Cidr); // Built-in geometric types private readonly NpgsqlPointTypeMapping _point = new(); @@ -316,7 +317,7 @@ public NpgsqlTypeMappingSource( { "macaddr", new[] { _macaddr } }, { "macaddr8", new[] { _macaddr8 } }, - { "inet", new[] { _inet } }, + { "inet", new[] { _inet, _inetMask } }, { "cidr", new[] { _cidr } }, { "point", new[] { _point } }, diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs index 854d88314..a9f67cf97 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs @@ -253,6 +253,14 @@ public void GenerateSqlLiteral_returns_inet_literal() public void GenerateCodeLiteral_returns_inet_literal() => Assert.Equal(@"System.Net.IPAddress.Parse(""192.168.1.1"")", CodeLiteral(IPAddress.Parse("192.168.1.1"))); + [Fact] + public void GenerateSqlLiteral_returns_inet_masked_literal() + => Assert.Equal("INET '192.168.1.1/24'", GetMapping(typeof((IPAddress, int)), "inet").GenerateSqlLiteral((IPAddress.Parse("192.168.1.1"), 24))); + + [Fact] + public void GenerateCodeLiteral_returns_inet_masked_literal() + => Assert.Equal(@"(System.Net.IPAddress.Parse(""192.168.1.1""), 24)", CodeLiteral((IPAddress.Parse("192.168.1.1"), 24))); + [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)));