@@ -7,97 +7,104 @@ namespace DuckDB.NET.Data.Internal.Writer;
7
7
8
8
internal sealed unsafe class EnumVectorDataWriter : VectorDataWriterBase
9
9
{
10
+ private readonly DuckDBLogicalType logicalType ;
11
+
10
12
private readonly DuckDBType enumType ;
11
13
12
- private readonly Dictionary < string , long > enumValueIndexDictionary ;
14
+ private readonly uint enumDictionarySize ;
15
+
16
+ private Dictionary < string , uint > ? enumValues ;
13
17
14
18
public EnumVectorDataWriter ( IntPtr vector , void * vectorData , DuckDBLogicalType logicalType , DuckDBType columnType ) : base ( vector , vectorData , columnType )
15
19
{
20
+ this . logicalType = logicalType ;
16
21
enumType = NativeMethods . LogicalType . DuckDBEnumInternalType ( logicalType ) ;
17
- uint size = NativeMethods . LogicalType . DuckDBEnumDictionarySize ( logicalType ) ;
18
- enumValueIndexDictionary = [ ] ;
19
- for ( long index = 0 ; index < size ; index ++ )
22
+ enumDictionarySize = NativeMethods . LogicalType . DuckDBEnumDictionarySize ( logicalType ) ;
23
+
24
+ uint maxEnumDictionarySize = enumType switch
25
+ {
26
+ DuckDBType . UnsignedTinyInt => byte . MaxValue ,
27
+ DuckDBType . UnsignedSmallInt => ushort . MaxValue ,
28
+ DuckDBType . UnsignedInteger => uint . MaxValue ,
29
+ _ => throw new NotSupportedException ( $ "The internal enum type must be utinyint, usmallint, or uinteger.") ,
30
+ } ;
31
+ if ( enumDictionarySize > maxEnumDictionarySize )
20
32
{
21
- string enumValue = NativeMethods . LogicalType . DuckDBEnumDictionaryValue ( logicalType , index ) . ToManagedString ( ) ;
22
- enumValueIndexDictionary . Add ( enumValue , index ) ;
33
+ // This exception should only be thrown if the DuckDB library has a bug.
34
+ throw new InvalidOperationException ( $ "The internal enum type is \" { enumType } \" but the enum dictionary size is greater than { maxEnumDictionarySize } ." ) ;
23
35
}
36
+
37
+ enumValues = null ;
24
38
}
25
39
26
40
internal override bool AppendString ( string value , int rowIndex )
27
41
{
28
- switch ( enumType )
42
+ // lazy initialization
43
+ if ( enumValues == null )
29
44
{
30
- case DuckDBType . UnsignedTinyInt :
31
- {
32
- if ( enumValueIndexDictionary . TryGetValue ( value , out long enumValueIndex ) &&
33
- enumValueIndex >= byte . MinValue && enumValueIndex <= byte . MaxValue )
34
- {
35
- return AppendValueInternal ( ( byte ) enumValueIndex , rowIndex ) ;
36
- }
37
-
38
- return false ;
39
- }
40
- case DuckDBType . UnsignedSmallInt :
41
- {
42
- if ( enumValueIndexDictionary . TryGetValue ( value , out long enumValueIndex ) &&
43
- enumValueIndex >= ushort . MinValue && enumValueIndex <= ushort . MaxValue )
44
- {
45
- return AppendValueInternal ( ( ushort ) enumValueIndex , rowIndex ) ;
46
- }
47
-
48
- return false ;
49
- }
50
- case DuckDBType . UnsignedInteger :
51
- {
52
- if ( enumValueIndexDictionary . TryGetValue ( value , out long enumValueIndex ) &&
53
- enumValueIndex >= uint . MinValue && enumValueIndex <= uint . MaxValue )
54
- {
55
- return AppendValueInternal ( ( uint ) enumValueIndex , rowIndex ) ;
56
- }
45
+ enumValues = [ ] ;
46
+ for ( uint index = 0 ; index < enumDictionarySize ; index ++ )
47
+ {
48
+ string enumValueName = NativeMethods . LogicalType . DuckDBEnumDictionaryValue ( logicalType , index ) . ToManagedString ( ) ;
49
+ enumValues . Add ( enumValueName , index ) ;
50
+ }
51
+ }
57
52
58
- return false ;
59
- }
60
- default :
61
- return false ;
53
+ if ( enumValues . TryGetValue ( value , out uint enumValue ) )
54
+ {
55
+ // The following casts to byte and ushort are safe because we ensure in the constructor that the value enumDictionarySize is not too high.
56
+ return enumType switch
57
+ {
58
+ DuckDBType . UnsignedTinyInt => AppendValueInternal ( ( byte ) enumValue , rowIndex ) ,
59
+ DuckDBType . UnsignedSmallInt => AppendValueInternal ( ( ushort ) enumValue , rowIndex ) ,
60
+ DuckDBType . UnsignedInteger => AppendValueInternal ( enumValue , rowIndex ) ,
61
+ _ => throw new InvalidOperationException ( $ "Failed to write Enum column because the internal enum type must be utinyint, usmallint, or uinteger.") ,
62
+ } ;
62
63
}
64
+
65
+ throw new InvalidOperationException ( $ "Failed to write Enum column because the value \" { value } \" is not valid.") ;
63
66
}
64
67
65
68
internal override bool AppendEnum < TEnum > ( TEnum value , int rowIndex )
66
69
{
67
- switch ( enumType )
70
+ ulong enumValue = ConvertEnumValueToUInt64 ( value ) ;
71
+ if ( enumValue <= enumDictionarySize )
68
72
{
69
- case DuckDBType . UnsignedTinyInt :
70
- {
71
- long enumValueIndex = Convert . ToInt64 ( value ) ;
72
- if ( enumValueIndex >= byte . MinValue && enumValueIndex <= byte . MaxValue )
73
- {
74
- return AppendValueInternal ( ( byte ) enumValueIndex , rowIndex ) ;
75
- }
73
+ // The following casts to byte, ushort and uint are safe because we ensure in the constructor that the value enumDictionarySize is not too high.
74
+ return enumType switch
75
+ {
76
+ DuckDBType . UnsignedTinyInt => AppendValueInternal ( ( byte ) enumValue , rowIndex ) ,
77
+ DuckDBType . UnsignedSmallInt => AppendValueInternal ( ( ushort ) enumValue , rowIndex ) ,
78
+ DuckDBType . UnsignedInteger => AppendValueInternal ( ( uint ) enumValue , rowIndex ) ,
79
+ _ => throw new InvalidOperationException ( $ "Failed to write Enum column because the internal enum type must be utinyint, usmallint, or uinteger.") ,
80
+ } ;
81
+ }
76
82
77
- return false ;
78
- }
79
- case DuckDBType . UnsignedSmallInt :
80
- {
81
- long enumValueIndex = Convert . ToInt64 ( value ) ;
82
- if ( enumValueIndex >= ushort . MinValue && enumValueIndex <= ushort . MaxValue )
83
- {
84
- return AppendValueInternal ( ( ushort ) enumValueIndex , rowIndex ) ;
85
- }
83
+ throw new InvalidOperationException ( $ "Failed to write Enum column because the value is outside the range (0-{ enumDictionarySize } ).") ;
84
+ }
86
85
87
- return false ;
88
- }
89
- case DuckDBType . UnsignedInteger :
90
- {
91
- long enumValueIndex = Convert . ToInt64 ( value ) ;
92
- if ( enumValueIndex >= uint . MinValue && enumValueIndex <= uint . MaxValue )
93
- {
94
- return AppendValueInternal ( ( uint ) enumValueIndex , rowIndex ) ;
95
- }
86
+ private static ulong ConvertEnumValueToUInt64 < TEnum > ( TEnum value ) where TEnum : Enum
87
+ {
88
+ switch ( Convert . GetTypeCode ( value ) )
89
+ {
90
+ case TypeCode . SByte :
91
+ return ( ulong ) Convert . ToSByte ( value ) ;
92
+ case TypeCode . Byte :
93
+ return Convert . ToByte ( value ) ;
94
+ case TypeCode . Int16 :
95
+ return ( ulong ) Convert . ToInt16 ( value ) ;
96
+ case TypeCode . UInt16 :
97
+ return Convert . ToUInt16 ( value ) ;
98
+ case TypeCode . Int32 :
99
+ return ( ulong ) Convert . ToInt32 ( value ) ;
100
+ case TypeCode . UInt32 :
101
+ return Convert . ToUInt32 ( value ) ;
102
+ case TypeCode . Int64 :
103
+ return ( ulong ) Convert . ToInt64 ( value ) ;
104
+ case TypeCode . UInt64 :
105
+ return Convert . ToUInt64 ( value ) ;
106
+ } ;
96
107
97
- return false ;
98
- }
99
- default :
100
- return false ;
101
- }
108
+ throw new InvalidOperationException ( $ "Failed to convert the enum value { value } to ulong.") ;
102
109
}
103
110
}
0 commit comments