diff --git a/src/Weasel.Postgresql.Tests/PostgresqlProviderTests.cs b/src/Weasel.Postgresql.Tests/PostgresqlProviderTests.cs index 84a3d3b..51b1a71 100644 --- a/src/Weasel.Postgresql.Tests/PostgresqlProviderTests.cs +++ b/src/Weasel.Postgresql.Tests/PostgresqlProviderTests.cs @@ -85,6 +85,34 @@ public void ipnetwork_resolves_to_cidr() .ShouldBe("cidr"); } + [Fact] + public void ipnetwork_is_claimed_by_exactly_one_mapping() + { + // Guards the test above. IPNetwork used to be declared on both the cidr and the inet + // mapping, and GetTypeMapping breaks a tie with LastOrDefault over a Cache backed by + // an ImHashMap -- so "cidr" was winning on hash layout, not on anything declared. + // weasel#405. + NpgsqlTypeMapper.Mappings + .Count(mapping => mapping.ClrTypes.Contains(typeof(IPNetwork))) + .ShouldBe(1); + } + + [Fact] + public void no_clr_type_is_claimed_by_more_than_one_mapping() + { + // Any CLR type reachable from two mappings has an order-dependent, effectively + // arbitrary resolution. Keep that structurally impossible rather than relying on + // enumeration order. weasel#405. + var doubleClaimed = NpgsqlTypeMapper.Mappings + .SelectMany(mapping => mapping.ClrTypes.Select(clrType => (clrType, mapping))) + .GroupBy(x => x.clrType) + .Where(g => g.Count() > 1) + .Select(g => $"{g.Key.Name} <- {string.Join(", ", g.Select(x => x.mapping.NpgsqlDbType))}") + .ToArray(); + + doubleClaimed.ShouldBeEmpty(); + } + [Fact] public void canonicizesql_supports_tabs_as_whitespace() { diff --git a/src/Weasel.Postgresql/NpgsqlTypeMapping.cs b/src/Weasel.Postgresql/NpgsqlTypeMapping.cs index 3919c48..ae394c7 100644 --- a/src/Weasel.Postgresql/NpgsqlTypeMapping.cs +++ b/src/Weasel.Postgresql/NpgsqlTypeMapping.cs @@ -82,9 +82,12 @@ public class NpgsqlTypeMapper {NpgsqlDbType.Multirange | NpgsqlDbType.TimestampTz, new NpgsqlTypeMapping(NpgsqlDbType.Multirange | NpgsqlDbType.TimestampTz, DbType.Object, "tstzmultirange")}, // Network types + // IPNetwork belongs to cidr only. It used to be listed on inet as well, and which of + // the two won was decided by LastOrDefault over a hash-ordered ImHashMap -- i.e. by + // accident rather than by declaration. See weasel#405. {NpgsqlDbType.Cidr, new NpgsqlTypeMapping(NpgsqlDbType.Cidr, DbType.Object, "cidr", typeof(IPNetwork))}, {NpgsqlDbType.Inet, new NpgsqlTypeMapping(NpgsqlDbType.Inet, DbType.Object, "inet", typeof(IPAddress), - typeof((IPAddress Address, int Subnet)), typeof(NpgsqlInet), typeof(IPNetwork), IPAddress.Loopback.GetType())}, + typeof((IPAddress Address, int Subnet)), typeof(NpgsqlInet), IPAddress.Loopback.GetType())}, {NpgsqlDbType.MacAddr, new NpgsqlTypeMapping(NpgsqlDbType.MacAddr, DbType.Object, "macaddr", typeof(PhysicalAddress))}, {NpgsqlDbType.MacAddr8, new NpgsqlTypeMapping(NpgsqlDbType.MacAddr8, DbType.Object, "macaddr8")},