From 6e97138c681807191ce7e97fa9009f0f1144bf3d Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 14 Nov 2018 12:51:01 +0200 Subject: [PATCH 1/3] Fix user-defined range mappings We used to eagerly create mappings for user-defined ranges in the constructor of NpgsqlTypeMappingSource. This was too early, and did not have access to mappings coming from plugins, so creating a user-defined range over NodaTime types failed. We know create mappings for user-defined ranges late, when FindMapping() is called. Fixes #688 --- .../Internal/NpgsqlOptionsExtension.cs | 2 +- .../Internal/NpgsqlTypeMappingSource.cs | 88 ++++++++++++++----- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index f1db48908..9e700ee4d 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -194,7 +194,7 @@ public virtual NpgsqlOptionsExtension WithRemoteCertificateValidationCallback([C #endregion Authentication } - public readonly struct RangeMappingInfo + public class RangeMappingInfo { /// The name of the PostgreSQL range type to be mapped. public string RangeName { get; } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index b13983c32..0ed2f3bfb 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -22,6 +22,8 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource public ConcurrentDictionary StoreTypeMappings { get; } public ConcurrentDictionary ClrTypeMappings { get; } + readonly IReadOnlyList _userRangeMappings; + #region Mappings // Numeric types @@ -238,24 +240,7 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc LoadUserDefinedTypeMappings(); - if (npgsqlOptions == null) - return; - - foreach (var (rangeName, subtypeClrType, subtypeName) in npgsqlOptions.RangeMappings) - { - var subtypeMapping = subtypeName == null - ? ClrTypeMappings.TryGetValue(subtypeClrType, out var mapping) - ? mapping - : throw new Exception($"Could not map range {rangeName}, no mapping was found for subtype CLR type {subtypeClrType}") - : StoreTypeMappings.TryGetValue(subtypeName, out var mappings) - ? mappings[0] - : throw new Exception($"Could not map range {rangeName}, no mapping was found for subtype {subtypeName}"); - - var rangeClrType = typeof(NpgsqlRange<>).MakeGenericType(subtypeClrType); - var rangeMapping = new NpgsqlRangeTypeMapping(rangeName, rangeClrType, subtypeMapping); - StoreTypeMappings[rangeName] = new RelationalTypeMapping[] { rangeMapping }; - ClrTypeMappings[rangeClrType] = rangeMapping; - } + _userRangeMappings = npgsqlOptions?.RangeMappings ?? new RangeMappingInfo[0]; } /// @@ -300,8 +285,10 @@ protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInf base.FindMapping(mappingInfo) ?? // Then, any mappings that have already been set up FindExistingMapping(mappingInfo) ?? - // Finally, try any array mappings which have not yet been set up - FindArrayMapping(mappingInfo); + // Try any array mappings which have not yet been set up + FindArrayMapping(mappingInfo) ?? + // Try any user-defined range mappings which have not yet been set up + FindUserRangeMapping(mappingInfo); protected virtual RelationalTypeMapping FindExistingMapping(in RelationalTypeMappingInfo mappingInfo) { @@ -387,7 +374,7 @@ protected virtual RelationalTypeMapping FindExistingMapping(in RelationalTypeMap return mapping; } - RelationalTypeMapping FindArrayMapping(in RelationalTypeMappingInfo mappingInfo) + protected virtual RelationalTypeMapping FindArrayMapping(in RelationalTypeMappingInfo mappingInfo) { // PostgreSQL array type names are the element plus [] var storeType = mappingInfo.StoreTypeName; @@ -445,5 +432,64 @@ RelationalTypeMapping FindArrayMapping(in RelationalTypeMappingInfo mappingInfo) return null; } + + protected virtual RelationalTypeMapping FindUserRangeMapping(in RelationalTypeMappingInfo mappingInfo) + { + RangeMappingInfo rangeMappingInfo = null; + var rangeStoreType = mappingInfo.StoreTypeName; + var rangeClrType = mappingInfo.ClrType; + + // If the incoming MappingInfo contains a ClrType, make sure it's an NpgsqlRange, otherwise bail + if (rangeClrType != null && + (!rangeClrType.IsGenericType || rangeClrType.GetGenericTypeDefinition() != typeof(NpgsqlRange<>))) + { + return null; + } + + // Try to find a user range definition (defined by the user on their context options), based on the + // incoming MappingInfo's StoreType or ClrType + if (rangeStoreType != null) + { + rangeMappingInfo = _userRangeMappings.SingleOrDefault(m => m.RangeName == rangeStoreType); + + if (rangeMappingInfo == null) + return null; + + if (rangeClrType == null) + { + // The incoming MappingInfo does not contain a ClrType, only a StoreType (i.e. scaffolding). + // Construct the range ClrType from the range definition's subtype ClrType + rangeClrType = typeof(NpgsqlRange<>).MakeGenericType(rangeMappingInfo.SubtypeClrType); + } + else if (rangeClrType != typeof(NpgsqlRange<>).MakeGenericType(rangeMappingInfo.SubtypeClrType)) + { + // If the incoming MappingInfo also contains a ClrType (in addition to the StoreType), make sure it + // corresponds to the subtype ClrType on the range mapping + return null; + } + } + else if (rangeClrType != null) + rangeMappingInfo = _userRangeMappings.SingleOrDefault(m => m.SubtypeClrType == rangeClrType.GetGenericArguments()[0]); + + if (rangeMappingInfo == null) + return null; + + // We now have a user-defined range mapping from the context options. Use it to get the subtype's + // mapping + var subtypeMapping = (RelationalTypeMapping)(rangeMappingInfo.SubtypeName == null + ? FindMapping(rangeMappingInfo.SubtypeClrType) + : FindMapping(rangeMappingInfo.SubtypeName)); + + if (subtypeMapping == null) + throw new Exception($"Could not map range {rangeMappingInfo.RangeName}, no mapping was found its subtype"); + + // Finally, construct a range mapping and add it to our lookup dictionaries - next time it will be found as + // an existing mapping + var rangeMapping = new NpgsqlRangeTypeMapping(rangeMappingInfo.RangeName, rangeClrType, subtypeMapping); + StoreTypeMappings[rangeMappingInfo.RangeName] = new RelationalTypeMapping[] { rangeMapping }; + ClrTypeMappings[rangeClrType] = rangeMapping; + + return rangeMapping; + } } } From c20c1b9e568bc33c4e4b6b7c5e70836a72618330 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 14 Nov 2018 13:05:11 +0200 Subject: [PATCH 2/3] Renamed RangeMappingInfo -> UserRangeDefinition It was neither a MappingInfo nor a mapping in the narrow EF Core sense. --- .../Infrastructure/Internal/INpgsqlOptions.cs | 2 +- .../Internal/NpgsqlOptionsExtension.cs | 20 +++++------ .../NpgsqlDbContextOptionsBuilder.cs | 4 +-- src/EFCore.PG/Internal/NpgsqlOptions.cs | 6 ++-- .../Internal/NpgsqlTypeMappingSource.cs | 34 +++++++++---------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/EFCore.PG/Infrastructure/Internal/INpgsqlOptions.cs b/src/EFCore.PG/Infrastructure/Internal/INpgsqlOptions.cs index 680bbea9e..31ae49bcb 100644 --- a/src/EFCore.PG/Infrastructure/Internal/INpgsqlOptions.cs +++ b/src/EFCore.PG/Infrastructure/Internal/INpgsqlOptions.cs @@ -25,6 +25,6 @@ public interface INpgsqlOptions : ISingletonOptions /// The collection of range mappings. /// [NotNull] - IReadOnlyList RangeMappings { get; } + IReadOnlyList UserRangeDefinitions { get; } } } diff --git a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs index 9e700ee4d..472ad7c79 100644 --- a/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs +++ b/src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs @@ -14,7 +14,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal /// public class NpgsqlOptionsExtension : RelationalOptionsExtension { - [NotNull] readonly List _rangeMappings; + [NotNull] readonly List _userRangeDefinitions; /// /// The name of the database for administrative operations. @@ -32,7 +32,7 @@ public class NpgsqlOptionsExtension : RelationalOptionsExtension /// The list of range mappings specified by the user. /// [NotNull] - public IReadOnlyList RangeMappings => _rangeMappings; + public IReadOnlyList UserRangeDefinitions => _userRangeDefinitions; /// /// The specified . @@ -55,7 +55,7 @@ public class NpgsqlOptionsExtension : RelationalOptionsExtension /// Initializes an instance of with the default settings. /// public NpgsqlOptionsExtension() - => _rangeMappings = new List(); + => _userRangeDefinitions = new List(); // NB: When adding new options, make sure to update the copy ctor below. /// @@ -65,7 +65,7 @@ public NpgsqlOptionsExtension() public NpgsqlOptionsExtension([NotNull] NpgsqlOptionsExtension copyFrom) : base(copyFrom) { AdminDatabase = copyFrom.AdminDatabase; - _rangeMappings = new List(copyFrom._rangeMappings); + _userRangeDefinitions = new List(copyFrom._userRangeDefinitions); PostgresVersion = copyFrom.PostgresVersion; ProvideClientCertificatesCallback = copyFrom.ProvideClientCertificatesCallback; RemoteCertificateValidationCallback = copyFrom.RemoteCertificateValidationCallback; @@ -94,11 +94,11 @@ public override bool ApplyServices(IServiceCollection services) /// Returns a copy of the current instance configured with the specified range mapping. /// [NotNull] - public virtual NpgsqlOptionsExtension WithRangeMapping(string rangeName, string subtypeName) + public virtual NpgsqlOptionsExtension WithUserRangeDefinition(string rangeName, string subtypeName) { var clone = (NpgsqlOptionsExtension)Clone(); - clone._rangeMappings.Add(new RangeMappingInfo(rangeName, typeof(TSubtype), subtypeName)); + clone._userRangeDefinitions.Add(new UserRangeDefinition(rangeName, typeof(TSubtype), subtypeName)); return clone; } @@ -107,11 +107,11 @@ public virtual NpgsqlOptionsExtension WithRangeMapping(string rangeNam /// Returns a copy of the current instance configured with the specified range mapping. /// [NotNull] - public virtual NpgsqlOptionsExtension WithRangeMapping(string rangeName, Type subtypeClrType, string subtypeName) + public virtual NpgsqlOptionsExtension WithUserRangeDefinition(string rangeName, Type subtypeClrType, string subtypeName) { var clone = (NpgsqlOptionsExtension)Clone(); - clone._rangeMappings.Add(new RangeMappingInfo(rangeName, subtypeClrType, subtypeName)); + clone._userRangeDefinitions.Add(new UserRangeDefinition(rangeName, subtypeClrType, subtypeName)); return clone; } @@ -194,7 +194,7 @@ public virtual NpgsqlOptionsExtension WithRemoteCertificateValidationCallback([C #endregion Authentication } - public class RangeMappingInfo + public class UserRangeDefinition { /// The name of the PostgreSQL range type to be mapped. public string RangeName { get; } @@ -209,7 +209,7 @@ public class RangeMappingInfo /// public string SubtypeName { get; } - public RangeMappingInfo(string rangeName, Type subtypeClrType, string subtypeName) + public UserRangeDefinition(string rangeName, Type subtypeClrType, string subtypeName) { RangeName = rangeName; SubtypeClrType = subtypeClrType; diff --git a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs index 44f8fa768..96402428a 100644 --- a/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs +++ b/src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs @@ -54,7 +54,7 @@ public virtual void SetPostgresVersion([CanBeNull] Version postgresVersion) /// NpgsqlTypeMappingSource.MapRange{float}("floatrange"); /// public virtual void MapRange([NotNull] string rangeName, string subtypeName = null) - => WithOption(e => e.WithRangeMapping(rangeName, typeof(TSubtype), subtypeName)); + => WithOption(e => e.WithUserRangeDefinition(rangeName, typeof(TSubtype), subtypeName)); /// /// Maps a user-defined PostgreSQL range type for use. @@ -73,7 +73,7 @@ public virtual void MapRange([NotNull] string rangeName, string subtyp /// NpgsqlTypeMappingSource.MapRange("floatrange", typeof(float)); /// public virtual void MapRange([NotNull] string rangeName, [NotNull] Type subtypeClrType, string subtypeName = null) - => WithOption(e => e.WithRangeMapping(rangeName, subtypeClrType, subtypeName)); + => WithOption(e => e.WithUserRangeDefinition(rangeName, subtypeClrType, subtypeName)); /// /// Appends NULLS FIRST to all ORDER BY clauses. This is important for the tests which were written diff --git a/src/EFCore.PG/Internal/NpgsqlOptions.cs b/src/EFCore.PG/Internal/NpgsqlOptions.cs index b18f2fcea..b269a843b 100644 --- a/src/EFCore.PG/Internal/NpgsqlOptions.cs +++ b/src/EFCore.PG/Internal/NpgsqlOptions.cs @@ -20,10 +20,10 @@ public class NpgsqlOptions : INpgsqlOptions /// [NotNull] - public virtual IReadOnlyList RangeMappings { get; private set; } + public virtual IReadOnlyList UserRangeDefinitions { get; private set; } public NpgsqlOptions() - => RangeMappings = new RangeMappingInfo[0]; + => UserRangeDefinitions = new UserRangeDefinition[0]; /// public void Initialize(IDbContextOptions options) @@ -32,7 +32,7 @@ public void Initialize(IDbContextOptions options) PostgresVersion = npgsqlOptions.PostgresVersion; ReverseNullOrderingEnabled = npgsqlOptions.ReverseNullOrdering; - RangeMappings = npgsqlOptions.RangeMappings; + UserRangeDefinitions = npgsqlOptions.UserRangeDefinitions; } /// diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index 0ed2f3bfb..7275a697a 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -22,7 +22,7 @@ public class NpgsqlTypeMappingSource : RelationalTypeMappingSource public ConcurrentDictionary StoreTypeMappings { get; } public ConcurrentDictionary ClrTypeMappings { get; } - readonly IReadOnlyList _userRangeMappings; + readonly IReadOnlyList _userRangeDefinitions; #region Mappings @@ -240,7 +240,7 @@ public NpgsqlTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc LoadUserDefinedTypeMappings(); - _userRangeMappings = npgsqlOptions?.RangeMappings ?? new RangeMappingInfo[0]; + _userRangeDefinitions = npgsqlOptions?.UserRangeDefinitions ?? new UserRangeDefinition[0]; } /// @@ -435,7 +435,7 @@ protected virtual RelationalTypeMapping FindArrayMapping(in RelationalTypeMappin protected virtual RelationalTypeMapping FindUserRangeMapping(in RelationalTypeMappingInfo mappingInfo) { - RangeMappingInfo rangeMappingInfo = null; + UserRangeDefinition rangeDefinition = null; var rangeStoreType = mappingInfo.StoreTypeName; var rangeClrType = mappingInfo.ClrType; @@ -450,43 +450,43 @@ protected virtual RelationalTypeMapping FindUserRangeMapping(in RelationalTypeMa // incoming MappingInfo's StoreType or ClrType if (rangeStoreType != null) { - rangeMappingInfo = _userRangeMappings.SingleOrDefault(m => m.RangeName == rangeStoreType); + rangeDefinition = _userRangeDefinitions.SingleOrDefault(m => m.RangeName == rangeStoreType); - if (rangeMappingInfo == null) + if (rangeDefinition == null) return null; if (rangeClrType == null) { // The incoming MappingInfo does not contain a ClrType, only a StoreType (i.e. scaffolding). // Construct the range ClrType from the range definition's subtype ClrType - rangeClrType = typeof(NpgsqlRange<>).MakeGenericType(rangeMappingInfo.SubtypeClrType); + rangeClrType = typeof(NpgsqlRange<>).MakeGenericType(rangeDefinition.SubtypeClrType); } - else if (rangeClrType != typeof(NpgsqlRange<>).MakeGenericType(rangeMappingInfo.SubtypeClrType)) + else if (rangeClrType != typeof(NpgsqlRange<>).MakeGenericType(rangeDefinition.SubtypeClrType)) { // If the incoming MappingInfo also contains a ClrType (in addition to the StoreType), make sure it - // corresponds to the subtype ClrType on the range mapping + // corresponds to the subtype ClrType on the range definition return null; } } else if (rangeClrType != null) - rangeMappingInfo = _userRangeMappings.SingleOrDefault(m => m.SubtypeClrType == rangeClrType.GetGenericArguments()[0]); + rangeDefinition = _userRangeDefinitions.SingleOrDefault(m => m.SubtypeClrType == rangeClrType.GetGenericArguments()[0]); - if (rangeMappingInfo == null) + if (rangeDefinition == null) return null; - // We now have a user-defined range mapping from the context options. Use it to get the subtype's + // We now have a user-defined range definition from the context options. Use it to get the subtype's // mapping - var subtypeMapping = (RelationalTypeMapping)(rangeMappingInfo.SubtypeName == null - ? FindMapping(rangeMappingInfo.SubtypeClrType) - : FindMapping(rangeMappingInfo.SubtypeName)); + var subtypeMapping = (RelationalTypeMapping)(rangeDefinition.SubtypeName == null + ? FindMapping(rangeDefinition.SubtypeClrType) + : FindMapping(rangeDefinition.SubtypeName)); if (subtypeMapping == null) - throw new Exception($"Could not map range {rangeMappingInfo.RangeName}, no mapping was found its subtype"); + throw new Exception($"Could not map range {rangeDefinition.RangeName}, no mapping was found its subtype"); // Finally, construct a range mapping and add it to our lookup dictionaries - next time it will be found as // an existing mapping - var rangeMapping = new NpgsqlRangeTypeMapping(rangeMappingInfo.RangeName, rangeClrType, subtypeMapping); - StoreTypeMappings[rangeMappingInfo.RangeName] = new RelationalTypeMapping[] { rangeMapping }; + var rangeMapping = new NpgsqlRangeTypeMapping(rangeDefinition.RangeName, rangeClrType, subtypeMapping); + StoreTypeMappings[rangeDefinition.RangeName] = new RelationalTypeMapping[] { rangeMapping }; ClrTypeMappings[rangeClrType] = rangeMapping; return rangeMapping; From 2c81889712c71bcf5cc1e37e200b77de25b311cf Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 15 Nov 2018 16:40:39 +0200 Subject: [PATCH 3/3] Add tests for NpgsqlTypeMappingSource --- test/EFCore.PG.Tests/EFCore.PG.Tests.csproj | 1 + .../Storage/NpgsqlTypeMappingSourceTest.cs | 111 ++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs diff --git a/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj b/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj index c2c7eebce..9d27325c0 100644 --- a/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj +++ b/test/EFCore.PG.Tests/EFCore.PG.Tests.csproj @@ -5,6 +5,7 @@ netcoreapp2.1 Npgsql.EntityFrameworkCore.PostgreSQL.Tests Npgsql.EntityFrameworkCore.PostgreSQL + latest diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs new file mode 100644 index 000000000..1c1be96ec --- /dev/null +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -0,0 +1,111 @@ +using System; +using System.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using Npgsql.EntityFrameworkCore.PostgreSQL.Internal; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; +using NpgsqlTypes; +using Xunit; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage +{ + public class NpgsqlTypeMappingSourceTest + { + [Theory] + [InlineData("integer", typeof(int))] + [InlineData("integer[]", typeof(int[]))] + [InlineData("dummy", typeof(DummyType))] + [InlineData("int4range", typeof(NpgsqlRange))] + [InlineData("floatrange", typeof(NpgsqlRange))] + [InlineData("dummyrange", typeof(NpgsqlRange))] + public void By_StoreType(string storeType, Type expectedClrType) + => Assert.Same(expectedClrType, Source.FindMapping(storeType).ClrType); + + [Theory] + [InlineData(typeof(int), "integer")] + [InlineData(typeof(int[]), "integer[]")] + [InlineData(typeof(DummyType), "dummy")] + [InlineData(typeof(NpgsqlRange), "int4range")] + [InlineData(typeof(NpgsqlRange), "floatrange")] + [InlineData(typeof(NpgsqlRange), "dummyrange")] + public void By_ClrType(Type clrType, string expectedStoreType) + => Assert.Equal(expectedStoreType, ((RelationalTypeMapping)Source.FindMapping(clrType)).StoreType); + + [Theory] + [InlineData("integer", typeof(int))] + [InlineData("integer[]", typeof(int[]))] + [InlineData("dummy", typeof(DummyType))] + [InlineData("int4range", typeof(NpgsqlRange))] + [InlineData("floatrange", typeof(NpgsqlRange))] + [InlineData("dummyrange", typeof(NpgsqlRange))] + public void By_StoreType_with_ClrType(string storeType, Type clrType) + => Assert.Equal(storeType, Source.FindMapping(clrType, storeType).StoreType); + + [Theory] + [InlineData("integer", typeof(UnknownType))] + //[InlineData("integer[]", typeof(UnknownType))] TODO Implement + [InlineData("dummy", typeof(UnknownType))] + [InlineData("int4range", typeof(UnknownType))] + [InlineData("floatrange", typeof(UnknownType))] + [InlineData("dummyrange", typeof(UnknownType))] + public void By_StoreType_with_wrong_ClrType(string storeType, Type wrongClrType) + => Assert.Null(Source.FindMapping(wrongClrType, storeType)); + + // Happens when using domain/aliases: we don't know about the domain but continue with the mapping based on the ClrType + [Fact] + public void Unknown_StoreType_with_known_ClrType() + => Assert.Equal("integer", Source.FindMapping(typeof(int), "some_domain").StoreType); + + #region Support + + public NpgsqlTypeMappingSourceTest() + { + var builder = new DbContextOptionsBuilder(); + new NpgsqlDbContextOptionsBuilder(builder).MapRange("floatrange", typeof(float)); + new NpgsqlDbContextOptionsBuilder(builder).MapRange("dummyrange", typeof(DummyType), "dummy"); + var options = new NpgsqlOptions(); + options.Initialize(builder.Options); + + Source = new NpgsqlTypeMappingSource( + new TypeMappingSourceDependencies( + new ValueConverterSelector(new ValueConverterSelectorDependencies()), + Array.Empty()), + new RelationalTypeMappingSourceDependencies( + new[] { new DummyTypeMappingSourcePlugin() }), + options); + } + + NpgsqlTypeMappingSource Source { get; } + + class DummyTypeMappingSourcePlugin : IRelationalTypeMappingSourcePlugin + { + public RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo) + => mappingInfo.StoreTypeName != null + ? mappingInfo.StoreTypeName == "dummy" && (mappingInfo.ClrType == null || mappingInfo.ClrType == typeof(DummyType)) + ? _dummyMapping + : null + : mappingInfo.ClrType == typeof(DummyType) + ? _dummyMapping + : null; + + DummyMapping _dummyMapping = new DummyMapping(); + + class DummyMapping : RelationalTypeMapping + { + // TODO: The DbType is a hack, we currently require of range subtype mapping that they other expose an NpgsqlDbType + // or a DbType (from which NpgsqlDbType is computed), since RangeTypeMapping sends an NpgsqlDbType. + // This means we currently don't support ranges over types without NpgsqlDbType, which are accessible via + // NpgsqlParameter.DataTypeName + public DummyMapping() : base("dummy", typeof(DummyType), System.Data.DbType.Guid) {} + } + } + + class DummyType {} + + class UnknownType {} + + #endregion Support + } +}