Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow mapping inet as (IPAddress, int). #2698

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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