22using System . Collections . Concurrent ;
33using System . Collections . Immutable ;
44using System . Data ;
5+ using System . Data . Common ;
56using System . Diagnostics . CodeAnalysis ;
67using System . Net ;
78using System . Net . NetworkInformation ;
@@ -771,6 +772,8 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType)
771772 {
772773 var storeType = mappingInfo . StoreTypeName ;
773774 var clrType = mappingInfo . ClrType ;
775+ string ? schema ;
776+ string name ;
774777
775778 if ( clrType is not null and not { IsEnum : true , IsClass : false } )
776779 {
@@ -783,20 +786,31 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType)
783786 if ( storeType is null )
784787 {
785788 enumDefinition = _enumDefinitions . SingleOrDefault ( m => m . ClrType == clrType ) ;
789+
790+ if ( enumDefinition is null )
791+ {
792+ return null ;
793+ }
794+
795+ ( name , schema ) = ( enumDefinition . StoreTypeName , enumDefinition . StoreTypeSchema ) ;
786796 }
787797 else
788798 {
789- // TODO: Not sure what to do about quoting. Is the user expected to configure properties
790- // TODO: with a quoted (schema-qualified) store type or not?
791- var dot = storeType . IndexOf ( '.' ) ;
792- enumDefinition = dot is - 1
793- ? _enumDefinitions . SingleOrDefault ( m => m . StoreTypeName == storeType )
794- : _enumDefinitions . SingleOrDefault ( m => m . StoreTypeName == storeType [ ( dot + 1 ) ..] && m . StoreTypeSchema == storeType [ ..dot ] ) ;
795- }
796-
797- if ( enumDefinition is null )
798- {
799- return null ;
799+ // If the user is specifying the store type manually, they are not expected to have quotes in the name (e.g. because of upper-
800+ // case characters).
801+ // However, if we infer an enum array type mapping from an element (e.g. someEnums.Contains(b.SomeEnumColumn)), we get the
802+ // element's store type - which for enums is quoted - and add []; so we get e.g. "MyEnum"[]. So we need to support quoted
803+ // names here, by parsing the name and stripping the quotes.
804+ ParseStoreTypeName ( storeType , out name , out schema , out var size , out var precision , out var scale ) ;
805+
806+ enumDefinition = schema is null
807+ ? _enumDefinitions . SingleOrDefault ( m => m . StoreTypeName == name )
808+ : _enumDefinitions . SingleOrDefault ( m => m . StoreTypeName == name && m . StoreTypeSchema == schema ) ;
809+
810+ if ( enumDefinition is null )
811+ {
812+ return null ;
813+ }
800814 }
801815
802816 // We now have an enum definition from the context options.
@@ -805,7 +819,6 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType)
805819 // 1. The quoted type name is used in migrations, where quoting is needed
806820 // 2. The unquoted type name is set on NpgsqlParameter.DataTypeName
807821 // (though see https://github.com/npgsql/npgsql/issues/5710).
808- var ( name , schema ) = ( enumDefinition . StoreTypeName , enumDefinition . StoreTypeSchema ) ;
809822 return new NpgsqlEnumTypeMapping (
810823 _sqlGenerationHelper . DelimitIdentifier ( name , schema ) ,
811824 schema is null ? name : schema + "." + name ,
@@ -972,6 +985,8 @@ private static bool NameBasesUsesPrecision(ReadOnlySpan<char> span)
972985 ref int ? precision ,
973986 ref int ? scale )
974987 {
988+ // TODO: Reimplement over ParseStoreTypeName below
989+
975990 if ( storeTypeName is null )
976991 {
977992 return null ;
@@ -1056,4 +1071,155 @@ private static bool NameBasesUsesPrecision(ReadOnlySpan<char> span)
10561071
10571072 return new StringBuilder ( preParens . Length ) . Append ( preParens ) . Append ( postParens ) . ToString ( ) ;
10581073 }
1074+
1075+ internal static void ParseStoreTypeName (
1076+ string storeTypeName ,
1077+ out string name ,
1078+ out string ? schema ,
1079+ out int ? size ,
1080+ out int ? precision ,
1081+ out int ? scale )
1082+ {
1083+ var s = storeTypeName . AsSpan ( ) . Trim ( ) ;
1084+ var i = 0 ;
1085+ size = precision = scale = null ;
1086+
1087+ if ( s . EndsWith ( "[]" , StringComparison . Ordinal ) )
1088+ {
1089+ // If this is an array store type, any facets (size, precision...) apply to the element and not to the array (e.g. varchar(32)[]
1090+ // is an array mapping with Size=null over an element mapping of varchar with Size=32). So just add everything up to the end.
1091+ // Note that if there's a schema (e.g. foo.varchar(32)[]), we return name=varchar(32), schema=foo.
1092+ name = s . ToString ( ) ;
1093+ schema = null ;
1094+ return ;
1095+ }
1096+
1097+ name = ParseNameComponent ( s ) ;
1098+
1099+ if ( i < s . Length && s [ i ] == '.' )
1100+ {
1101+ i ++ ;
1102+ schema = name ;
1103+ name = ParseNameComponent ( s ) ;
1104+ }
1105+ else
1106+ {
1107+ schema = null ;
1108+ }
1109+
1110+ s = s [ i ..] ;
1111+
1112+ if ( s . Length == 0 || s [ 0 ] != '(' )
1113+ {
1114+ // No facets
1115+ return ;
1116+ }
1117+
1118+ s = s [ 1 ..] ;
1119+
1120+ var closeParen = s . IndexOf ( ")" , StringComparison . Ordinal ) ;
1121+ if ( closeParen == - 1 )
1122+ {
1123+ return ;
1124+ }
1125+
1126+ var inParens = s [ ..closeParen ] . Trim ( ) ;
1127+ // There may be stuff after the closing parentheses (e.g. timestamp(3) with time zone)
1128+ var postParens = s . Slice ( closeParen + 1 ) ;
1129+
1130+ switch ( s . IndexOf ( "," , StringComparison . Ordinal ) )
1131+ {
1132+ // No comma inside the parentheses, parse the value either as size or precision
1133+ case - 1 :
1134+ if ( ! int . TryParse ( inParens , out var p ) )
1135+ {
1136+ return ;
1137+ }
1138+
1139+ if ( NameBasesUsesPrecision ( name ) )
1140+ {
1141+ precision = p ;
1142+ // scale = 0;
1143+ }
1144+ else
1145+ {
1146+ size = p ;
1147+ }
1148+
1149+ break ;
1150+
1151+ case var comma :
1152+ if ( int . TryParse ( s [ ..comma ] . Trim ( ) , out var parsedPrecision ) )
1153+ {
1154+ precision = parsedPrecision ;
1155+ }
1156+ else
1157+ {
1158+ return ;
1159+ }
1160+
1161+ if ( int . TryParse ( s [ ( comma + 1 ) ..closeParen ] . Trim ( ) , out var parsedScale ) )
1162+ {
1163+ scale = parsedScale ;
1164+ }
1165+ else
1166+ {
1167+ return ;
1168+ }
1169+
1170+ break ;
1171+ }
1172+
1173+ if ( postParens . Length > 0 )
1174+ {
1175+ // There's stuff after the parentheses (e.g. time(3) with time zone), append to the name
1176+ name += postParens . ToString ( ) ;
1177+ }
1178+
1179+ string ParseNameComponent ( ReadOnlySpan < char > s )
1180+ {
1181+ var inQuotes = false ;
1182+ StringBuilder builder = new ( ) ;
1183+
1184+ if ( s [ i ] == '"' )
1185+ {
1186+ inQuotes = true ;
1187+ i ++ ;
1188+ }
1189+
1190+ var start = i ;
1191+
1192+ for ( ; i < s . Length ; i ++ )
1193+ {
1194+ var c = s [ i ] ;
1195+
1196+ if ( inQuotes )
1197+ {
1198+ if ( c == '"' )
1199+ {
1200+ if ( i + 1 < s . Length && s [ i + 1 ] == '"' )
1201+ {
1202+ builder . Append ( '"' ) ;
1203+ i ++ ;
1204+ continue ;
1205+ }
1206+
1207+ i ++ ;
1208+ break ;
1209+ }
1210+ }
1211+ else if ( ! char . IsWhiteSpace ( c ) && ! char . IsAsciiLetterOrDigit ( c ) && c != '_' )
1212+ {
1213+ break ;
1214+ }
1215+
1216+ builder . Append ( c ) ;
1217+ }
1218+
1219+ var length = i - start ;
1220+ return length == storeTypeName . Length
1221+ ? storeTypeName
1222+ : builder . ToString ( ) ;
1223+ }
1224+ }
10591225}
0 commit comments