Skip to content

Commit

Permalink
Allow mapping inet as (IPAddress, int).
Browse files Browse the repository at this point in the history
Fixes npgsql#1158

Unified cidr and inet handling code since they're similar.
  • Loading branch information
PJB3005 committed Mar 19, 2023
1 parent 5ae141a commit 14ddf0a
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 76 deletions.
6 changes: 2 additions & 4 deletions src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => "@>",
Expand Down
96 changes: 27 additions & 69 deletions src/EFCore.PG/Storage/Internal/Mapping/NpgsqlNetworkTypeMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public override Expression GenerateCodeLiteral(object value)
}

/// <summary>
/// The type mapping for the PostgreSQL inet type.
/// The type mapping for the PostgreSQL inet and cidr types.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/static/datatype-net-types.html#DATATYPE-INET
Expand All @@ -127,16 +127,16 @@ 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.
/// </summary>
public NpgsqlInetTypeMapping() : base("inet", typeof(IPAddress), NpgsqlDbType.Inet) {}
public NpgsqlInetTypeMapping(string storeType, Type clrType, NpgsqlDbType npgsqlDbType) : base(storeType, clrType, npgsqlDbType) {}

/// <summary>
/// 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.
/// </summary>
protected NpgsqlInetTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters, NpgsqlDbType.Inet) {}
protected NpgsqlInetTypeMapping(RelationalTypeMappingParameters parameters, NpgsqlDbType npgsqlDbType)
: base(parameters, npgsqlDbType) {}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -145,62 +145,7 @@ protected NpgsqlInetTypeMapping(RelationalTypeMappingParameters parameters)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlInetTypeMapping(parameters);

/// <summary>
/// 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.
/// </summary>
protected override string GenerateNonNullSqlLiteral(object value)
=> $"INET '{(IPAddress)value}'";

/// <summary>
/// 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.
/// </summary>
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) })!;
}

/// <summary>
/// The type mapping for the PostgreSQL cidr type.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/static/datatype-net-types.html#DATATYPE-CIDR
/// </remarks>
public class NpgsqlCidrTypeMapping : NpgsqlTypeMapping
{
/// <summary>
/// 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.
/// </summary>
public NpgsqlCidrTypeMapping() : base("cidr", typeof((IPAddress, int)), NpgsqlDbType.Cidr) {}

/// <summary>
/// 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.
/// </summary>
protected NpgsqlCidrTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters, NpgsqlDbType.Cidr) {}

/// <summary>
/// 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.
/// </summary>
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new NpgsqlCidrTypeMapping(parameters);
=> new NpgsqlInetTypeMapping(parameters, NpgsqlDbType);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -210,8 +155,18 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p
/// </summary>
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()}");
}
}

/// <summary>
Expand All @@ -222,15 +177,18 @@ protected override string GenerateNonNullSqlLiteral(object value)
/// </summary>
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) })!;
}
7 changes: 4 additions & 3 deletions src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 } },
Expand Down
8 changes: 8 additions & 0 deletions test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down

0 comments on commit 14ddf0a

Please sign in to comment.