• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Giorgi / DuckDB.NET / 22237670288

20 Feb 2026 07:15PM UTC coverage: 89.875% (+0.2%) from 89.659%
22237670288

push

github

Giorgi
Fix decimal precision and support scale > 28

Replace lossy double-based Math.Pow(10, scale) with
pre-computed decimal and BigInteger lookup tables.

Use direct decimal constructor and mantissa extraction
instead of arithmetic reconstruction, avoiding
intermediate precision loss.

Handle HugeInt decimals with scale > 28 (beyond .NET
decimal's maximum) by truncating excess digits via
BigInteger division.

1236 of 1431 branches covered (86.37%)

Branch coverage included in aggregate %.

76 of 76 new or added lines in 5 files covered. (100.0%)

11 existing lines in 3 files now uncovered.

2510 of 2737 relevant lines covered (91.71%)

523497.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.24
/DuckDB.NET.Data/DataChunk/Reader/VectorDataReaderBase.cs
1
using System.IO;
2
using System.Runtime.CompilerServices;
3

4
namespace DuckDB.NET.Data.DataChunk.Reader;
5

6
internal class VectorDataReaderBase : IDisposable, IDuckDBDataReader
7
{
8
    private readonly unsafe ulong* validityMaskPointer;
9

10
    private Type? clrType;
11
    public Type ClrType => clrType ??= GetColumnType();
216,820✔
12

13
    private Type? providerSpecificClrType;
14
    public Type ProviderSpecificClrType => providerSpecificClrType ??= GetColumnProviderSpecificType();
189✔
15

16

17
    public string ColumnName { get; }
213,178✔
18
    public DuckDBType DuckDBType { get; }
21,580,155✔
19
    private protected unsafe void* DataPointer { get; }
21,485,986✔
20

21
    internal unsafe VectorDataReaderBase(void* dataPointer, ulong* validityMaskPointer, DuckDBType columnType, string columnName)
58,694✔
22
    {
23
        DataPointer = dataPointer;
58,694✔
24
        this.validityMaskPointer = validityMaskPointer;
58,694✔
25

26
        DuckDBType = columnType;
58,694✔
27
        ColumnName = columnName;
58,694✔
28
    }
58,694✔
29

30
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
31
    public unsafe bool IsValid(ulong offset)
32
    {
33
        if (validityMaskPointer == default)
31,552,217✔
34
        {
35
            return true;
18,721,317✔
36
        }
37

38
        var validityMaskEntryIndex = offset / 64;
12,830,900✔
39
        var validityBitIndex = (int)(offset % 64);
12,830,900✔
40

41
        var validityMaskEntryPtr = validityMaskPointer + validityMaskEntryIndex;
12,830,900✔
42
        var validityBit = 1ul << validityBitIndex;
12,830,900✔
43

44
        var isValid = (*validityMaskEntryPtr & validityBit) != 0;
12,830,900✔
45
        return isValid;
12,830,900✔
46
    }
47

48
    public virtual T GetValue<T>(ulong offset)
49
    {
50
        var (isNullableValueType, targetType) = TypeExtensions.IsNullableValueType<T>();
5,628,264✔
51

52
        var isValid = IsValid(offset);
5,628,264✔
53

54
        //If nullable we can't use Unsafe.As because we don't have the underlying type as T so use the non-generic GetValue method.
55
        if (isNullableValueType)
5,628,264✔
56
        {
57
            return isValid
1,507,247✔
58
                ? (T)GetValue(offset, Nullable.GetUnderlyingType(targetType)!)
1,507,247✔
59
                : default!; //T is Nullable<> and we are returning null so suppress compiler warning.
1,507,247✔
60
        }
61

62
        //If we are here, T isn't Nullable<>. It can be either a value type or a class.
63
        //In both cases if the data is null we should throw.
64
        if (isValid)
4,121,017✔
65
        {
66
            return GetValidValue<T>(offset, targetType);
4,120,834✔
67
        }
68
        
69
        throw new InvalidCastException($"Column '{ColumnName}' value is null");
183✔
70
    }
71

72
    /// <summary>
73
    /// Called when the value at specified <param name="offset">offset</param> is valid (isn't null)
74
    /// </summary>
75
    /// <typeparam name="T">Type of the return value</typeparam>
76
    /// <param name="offset">Position to read the data from</param>
77
    /// <param name="targetType">Type of the return value</param>
78
    /// <returns>Data at the specified offset</returns>
79
    protected virtual T GetValidValue<T>(ulong offset, Type targetType)
80
    {
81
        return (T)GetValue(offset, targetType);
517,324✔
82
    }
83

84
    public object GetValue(ulong offset)
85
    {
86
        return GetValue(offset, ClrType);
196,668✔
87
    }
88

89
    internal virtual object GetValue(ulong offset, Type targetType)
90
    {
91
        return DuckDBType switch
×
92
        {
×
93
            DuckDBType.Invalid => throw new DuckDBException($"Invalid type for column {ColumnName}"),
×
UNCOV
94
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
×
UNCOV
95
        };
×
96
    }
97

98
    internal object GetProviderSpecificValue(ulong offset)
99
    {
100
        return GetValue(offset, ProviderSpecificClrType);
48✔
101
    }
102

103
    protected virtual Type GetColumnType()
104
    {
105
        return DuckDBType switch
39,437!
106
        {
39,437✔
UNCOV
107
            DuckDBType.Invalid => throw new DuckDBException($"Invalid type for column {ColumnName}"),
×
108
            DuckDBType.Boolean => typeof(bool),
66✔
109
            DuckDBType.TinyInt => typeof(sbyte),
39✔
110
            DuckDBType.SmallInt => typeof(short),
39✔
111
            DuckDBType.Integer => typeof(int),
273✔
112
            DuckDBType.BigInt => typeof(long),
225✔
113
            DuckDBType.UnsignedTinyInt => typeof(byte),
42✔
114
            DuckDBType.UnsignedSmallInt => typeof(ushort),
39✔
115
            DuckDBType.UnsignedInteger => typeof(uint),
39✔
116
            DuckDBType.UnsignedBigInt => typeof(ulong),
57✔
117
            DuckDBType.Float => typeof(float),
27✔
118
            DuckDBType.Double => typeof(double),
51✔
119
            DuckDBType.Timestamp => typeof(DateTime),
87✔
120
            DuckDBType.Interval => typeof(TimeSpan),
15✔
121
            DuckDBType.Date => typeof(DateOnly),
48✔
122
            DuckDBType.Time => typeof(TimeOnly),
96✔
123
            DuckDBType.TimeTz => typeof(DateTimeOffset),
21✔
124
            DuckDBType.HugeInt => typeof(BigInteger),
15✔
125
            DuckDBType.UnsignedHugeInt => typeof(BigInteger),
3✔
126
            DuckDBType.Varchar => typeof(string),
351✔
127
            DuckDBType.Decimal => typeof(decimal),
198✔
128
            DuckDBType.TimestampS => typeof(DateTime),
21✔
129
            DuckDBType.TimestampMs => typeof(DateTime),
21✔
130
            DuckDBType.TimestampNs => typeof(DateTime),
24✔
131
            DuckDBType.Blob => typeof(Stream),
9✔
132
            DuckDBType.Enum => typeof(string),
42✔
133
            DuckDBType.Uuid => typeof(Guid),
30✔
134
            DuckDBType.Struct => typeof(Dictionary<string, object>),
9✔
135
            DuckDBType.Bit => typeof(string),
6✔
136
            DuckDBType.TimestampTz => typeof(DateTime),
24✔
137
            DuckDBType.VarInt => typeof(BigInteger),
37,520✔
UNCOV
138
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
×
139
        };
39,437✔
140
    }
141

142
    protected virtual Type GetColumnProviderSpecificType()
143
    {
144
        return DuckDBType switch
123!
145
        {
123✔
UNCOV
146
            DuckDBType.Invalid => throw new DuckDBException($"Invalid type for column {ColumnName}"),
×
147
            DuckDBType.Boolean => typeof(bool),
3✔
148
            DuckDBType.TinyInt => typeof(sbyte),
3✔
149
            DuckDBType.SmallInt => typeof(short),
3✔
150
            DuckDBType.Integer => typeof(int),
9✔
151
            DuckDBType.BigInt => typeof(long),
3✔
152
            DuckDBType.UnsignedTinyInt => typeof(byte),
3✔
153
            DuckDBType.UnsignedSmallInt => typeof(ushort),
3✔
154
            DuckDBType.UnsignedInteger => typeof(uint),
3✔
155
            DuckDBType.UnsignedBigInt => typeof(ulong),
3✔
156
            DuckDBType.Float => typeof(float),
3✔
157
            DuckDBType.Double => typeof(double),
6✔
158
            DuckDBType.Timestamp => typeof(DuckDBTimestamp),
6✔
159
            DuckDBType.Interval => typeof(DuckDBInterval),
3✔
160
            DuckDBType.Date => typeof(DuckDBDateOnly),
6✔
161
            DuckDBType.Time => typeof(DuckDBTimeOnly),
3✔
162
            DuckDBType.TimeTz => typeof(DuckDBTimeTz),
3✔
163
            DuckDBType.HugeInt => typeof(DuckDBHugeInt),
3✔
164
            DuckDBType.UnsignedHugeInt => typeof(DuckDBUHugeInt),
3✔
165
            DuckDBType.Varchar => typeof(string),
3✔
166
            DuckDBType.Decimal => typeof(decimal),
12✔
167
            DuckDBType.TimestampS => typeof(DuckDBTimestamp),
3✔
168
            DuckDBType.TimestampMs => typeof(DuckDBTimestamp),
3✔
169
            DuckDBType.TimestampNs => typeof(DuckDBTimestamp),
3✔
170
            DuckDBType.Blob => typeof(Stream),
3✔
171
            DuckDBType.Enum => typeof(string),
9✔
172
            DuckDBType.Uuid => typeof(Guid),
3✔
173
            DuckDBType.Struct => typeof(Dictionary<string, object>),
3✔
174
            DuckDBType.Bit => typeof(string),
3✔
175
            DuckDBType.TimestampTz => typeof(DuckDBTimestamp),
6✔
176
            DuckDBType.VarInt => typeof(BigInteger),
3✔
UNCOV
177
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
×
178
        };
123✔
179
    }
180

181
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
182
    protected unsafe T GetFieldData<T>(ulong offset) where T : unmanaged => *((T*)DataPointer + offset);
16,753,530✔
183

184
    public virtual void Dispose()
185
    {
186
    }
42,185✔
187
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc