X Tutup
using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Npgsql.Internal; using Npgsql.Internal.Composites; using Npgsql.Internal.Converters; using Npgsql.Internal.Postgres; using Npgsql.NameTranslation; using Npgsql.PostgresTypes; using NpgsqlTypes; namespace Npgsql.TypeMapping; /// /// The base class for user type mappings. /// public abstract class UserTypeMapping { /// /// The name of the PostgreSQL type that this mapping is for. /// public string PgTypeName { get; } /// /// The CLR type that this mapping is for. /// public Type ClrType { get; } internal UserTypeMapping(string pgTypeName, Type type) => (PgTypeName, ClrType) = (pgTypeName, type); internal abstract void AddMapping(TypeInfoMappingCollection mappings); internal abstract void AddArrayMapping(TypeInfoMappingCollection mappings); } sealed class UserTypeMapper : PgTypeInfoResolverFactory { readonly List _mappings; public IList Items => _mappings; INpgsqlNameTranslator _defaultNameTranslator = NpgsqlSnakeCaseNameTranslator.Instance; public INpgsqlNameTranslator DefaultNameTranslator { get => _defaultNameTranslator; set { ArgumentNullException.ThrowIfNull(value); _defaultNameTranslator = value; } } UserTypeMapper(IEnumerable mappings) => _mappings = [..mappings]; public UserTypeMapper() => _mappings = []; public UserTypeMapper Clone() => new(_mappings) { DefaultNameTranslator = DefaultNameTranslator }; public UserTypeMapper MapEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where TEnum : struct, Enum { Unmap(typeof(TEnum), out var resolvedName, pgName, nameTranslator); Items.Add(new EnumMapping(resolvedName, nameTranslator ?? DefaultNameTranslator)); return this; } public bool UnmapEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>( string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where TEnum : struct, Enum => Unmap(typeof(TEnum), out _, pgName, nameTranslator ?? DefaultNameTranslator); [UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "MapEnum TEnum has less DAM annotations than clrType.")] [RequiresDynamicCode("Calling MapEnum with a Type can require creating new generic types or methods. This may not work when AOT compiling.")] public UserTypeMapper MapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) { if (!clrType.IsEnum || !clrType.IsValueType) throw new ArgumentException("Type must be a concrete Enum", nameof(clrType)); var openMethod = typeof(UserTypeMapper).GetMethod(nameof(MapEnum), [typeof(string), typeof(INpgsqlNameTranslator)])!; var method = openMethod.MakeGenericMethod(clrType); method.Invoke(this, [pgName, nameTranslator]); return this; } public bool UnmapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]Type clrType,string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) { if (!clrType.IsEnum || !clrType.IsValueType) throw new ArgumentException("Type must be a concrete Enum", nameof(clrType)); return Unmap(clrType, out _, pgName, nameTranslator ?? DefaultNameTranslator); } [RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")] public UserTypeMapper MapComposite<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] T>( string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where T : class { Unmap(typeof(T), out var resolvedName, pgName, nameTranslator); Items.Add(new CompositeMapping(resolvedName, nameTranslator ?? DefaultNameTranslator)); return this; } [RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")] public UserTypeMapper MapStructComposite<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] T>( string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where T : struct { Unmap(typeof(T), out var resolvedName, pgName, nameTranslator); Items.Add(new StructCompositeMapping(resolvedName, nameTranslator ?? DefaultNameTranslator)); return this; } [UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "MapStructComposite and MapComposite have identical DAM annotations to clrType.")] [RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")] public UserTypeMapper MapComposite([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) { if (clrType.IsConstructedGenericType && clrType.GetGenericTypeDefinition() == typeof(Nullable<>)) throw new ArgumentException("Cannot map nullable.", nameof(clrType)); var openMethod = typeof(UserTypeMapper).GetMethod( clrType.IsValueType ? nameof(MapStructComposite) : nameof(MapComposite), [typeof(string), typeof(INpgsqlNameTranslator)])!; var method = openMethod.MakeGenericMethod(clrType); method.Invoke(this, [pgName, nameTranslator]); return this; } public bool UnmapComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where T : class => UnmapComposite(typeof(T), pgName, nameTranslator); public bool UnmapStructComposite(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) where T : struct => UnmapComposite(typeof(T), pgName, nameTranslator); public bool UnmapComposite(Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) => Unmap(clrType, out _, pgName, nameTranslator); bool Unmap(Type type, out string resolvedName, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null) { if (pgName != null && pgName.Trim() == "") throw new ArgumentException("pgName can't be empty", nameof(pgName)); nameTranslator ??= DefaultNameTranslator; resolvedName = pgName ??= GetPgName(type, nameTranslator); UserTypeMapping? toRemove = null; foreach (var item in _mappings) if (item.PgTypeName == pgName) toRemove = item; return toRemove is not null && _mappings.Remove(toRemove); } static string GetPgName(Type type, INpgsqlNameTranslator nameTranslator) => type.GetCustomAttribute()?.PgName ?? nameTranslator.TranslateTypeName(type.Name); public override IPgTypeInfoResolver CreateResolver() => new Resolver([.._mappings]); public override IPgTypeInfoResolver CreateArrayResolver() => new ArrayResolver([.._mappings]); class Resolver(List userTypeMappings) : IPgTypeInfoResolver { protected readonly List _userTypeMappings = userTypeMappings; TypeInfoMappingCollection? _mappings; protected TypeInfoMappingCollection Mappings => _mappings ??= AddMappings(new()); PgTypeInfo? IPgTypeInfoResolver.GetTypeInfo(Type? type, DataTypeName? dataTypeName, PgSerializerOptions options) => Mappings.Find(type, dataTypeName, options); TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) { foreach (var userTypeMapping in _userTypeMappings) userTypeMapping.AddMapping(mappings); return mappings; } } sealed class ArrayResolver(List userTypeMappings) : Resolver(userTypeMappings), IPgTypeInfoResolver { TypeInfoMappingCollection? _mappings; new TypeInfoMappingCollection Mappings => _mappings ??= AddMappings(new(base.Mappings)); PgTypeInfo? IPgTypeInfoResolver.GetTypeInfo(Type? type, DataTypeName? dataTypeName, PgSerializerOptions options) => Mappings.Find(type, dataTypeName, options); TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection mappings) { foreach (var userTypeMapping in _userTypeMappings) userTypeMapping.AddArrayMapping(mappings); return mappings; } } [RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")] sealed class CompositeMapping< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] T>(string pgTypeName, INpgsqlNameTranslator nameTranslator) : UserTypeMapping(pgTypeName, typeof(T)) where T : class { internal override void AddMapping(TypeInfoMappingCollection mappings) => mappings.AddType(PgTypeName, (options, mapping, _) => { var pgType = mapping.GetPgType(options); if (pgType is not PostgresCompositeType compositeType) throw new InvalidOperationException("Composite mapping must be to a composite type"); return mapping.CreateInfo(options, new CompositeConverter( ReflectionCompositeInfoFactory.CreateCompositeInfo(compositeType, nameTranslator, options))); }, isDefault: true); internal override void AddArrayMapping(TypeInfoMappingCollection mappings) => mappings.AddArrayType(PgTypeName); } [RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")] sealed class StructCompositeMapping< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] T>(string pgTypeName, INpgsqlNameTranslator nameTranslator) : UserTypeMapping(pgTypeName, typeof(T)) where T : struct { internal override void AddMapping(TypeInfoMappingCollection mappings) => mappings.AddStructType(PgTypeName, (options, mapping, requiresDataTypeName) => { var pgType = mapping.GetPgType(options); if (pgType is not PostgresCompositeType compositeType) throw new InvalidOperationException("Composite mapping must be to a composite type"); return mapping.CreateInfo(options, new CompositeConverter( ReflectionCompositeInfoFactory.CreateCompositeInfo(compositeType, nameTranslator, options))); }, isDefault: true); internal override void AddArrayMapping(TypeInfoMappingCollection mappings) => mappings.AddStructArrayType(PgTypeName); } internal abstract class EnumMapping( string pgTypeName, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type enumClrType, INpgsqlNameTranslator nameTranslator) : UserTypeMapping(pgTypeName, enumClrType) { internal INpgsqlNameTranslator NameTranslator { get; } = nameTranslator; } sealed class EnumMapping<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum> : EnumMapping where TEnum : struct, Enum { readonly Dictionary _enumToLabel = new(); readonly Dictionary _labelToEnum = new(); public EnumMapping(string pgTypeName, INpgsqlNameTranslator nameTranslator) : base(pgTypeName, typeof(TEnum), nameTranslator) { foreach (var field in typeof(TEnum).GetFields(BindingFlags.Static | BindingFlags.Public)) { var attribute = (PgNameAttribute?)field.GetCustomAttribute(typeof(PgNameAttribute), false); var enumName = attribute is null ? nameTranslator.TranslateMemberName(field.Name) : attribute.PgName; var enumValue = (TEnum)field.GetValue(null)!; _enumToLabel[enumValue] = enumName; _labelToEnum[enumName] = enumValue; } } internal override void AddMapping(TypeInfoMappingCollection mappings) => mappings.AddStructType(PgTypeName, (options, mapping, _) => mapping.CreateInfo(options, new EnumConverter(_enumToLabel, _labelToEnum, options.TextEncoding), preferredFormat: DataFormat.Text), isDefault: true); internal override void AddArrayMapping(TypeInfoMappingCollection mappings) => mappings.AddStructArrayType(PgTypeName); } }
X Tutup