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

Giorgi / DuckDB.NET / 23004948356

12 Mar 2026 01:42PM UTC coverage: 89.595% (+0.07%) from 89.526%
23004948356

push

github

Giorgi
Update GitVersion

1277 of 1485 branches covered (85.99%)

Branch coverage included in aggregate %.

2658 of 2907 relevant lines covered (91.43%)

451450.8 hits per line

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

87.95
/DuckDB.NET.Data/DataChunk/Reader/VectorDataReaderBase.cs
1
using System.IO;
2
using System.Linq.Expressions;
3
using System.Reflection;
4
using System.Runtime.CompilerServices;
5

6
namespace DuckDB.NET.Data.DataChunk.Reader;
7

8
internal class VectorDataReaderBase : IDisposable, IDuckDBDataReader
9
{
10
    private unsafe ulong* validityMaskPointer;
11

12
    public Type ClrType => field ??= GetColumnType();
322,389✔
13

14
    public Type ProviderSpecificClrType => field ??= GetColumnProviderSpecificType();
189✔
15

16

17
    public string ColumnName { get; }
162,892✔
18
    public DuckDBType DuckDBType { get; }
21,756,755✔
19
    private protected unsafe void* DataPointer { get; private set; }
21,722,003✔
20

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

26
        DuckDBType = columnType;
57,439✔
27
        ColumnName = columnName;
57,439✔
28
    }
57,439✔
29

30
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
31
    public unsafe bool IsValid(ulong offset)
32
    {
33
        if (validityMaskPointer == default)
32,019,284✔
34
        {
35
            return true;
19,231,752✔
36
        }
37

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

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

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

48
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
49
    public T GetValue<T>(ulong offset) => GetValue<T>(offset, strict: false);
271,302✔
50

51
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
52
    internal T GetValueStrict<T>(ulong offset) => GetValue<T>(offset, strict: true);
5,484,956✔
53

54
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
55
    internal T GetValue<T>(ulong offset, bool strict)
56
    {
57
        // When T is Nullable<TUnderlying> (e.g. int?), we can't call GetValidValue<int>() directly
58
        // because we only have T=int? at compile time. NullableHandler uses a pre-compiled expression
59
        // tree that calls GetValidValue<int>() and converts to int?, avoiding boxing through the
60
        // non-generic GetValue(offset, Type) path.
61
        if (NullableHandler<T>.IsNullableValueType)
5,756,258✔
62
        {
63
            return NullableHandler<T>.Read(this, offset);
1,496,553✔
64
        }
65

66
        if (IsValid(offset))
4,259,705✔
67
        {
68
            return GetValidValue<T>(offset);
4,259,519✔
69
        }
70

71
        if (strict || !NullableHandler<T>.IsReferenceType)
186✔
72
        {
73
            throw new InvalidCastException($"Column '{ColumnName}' value is null");
183✔
74
        }
75
        return default!;
3✔
76
    }
77

78
    /// <summary>
79
    /// Called when the value at specified <param name="offset">offset</param> is valid (isn't null)
80
    /// </summary>
81
    /// <typeparam name="T">Type of the return value</typeparam>
82
    /// <param name="offset">Position to read the data from</param>
83
    /// <returns>Data at the specified offset</returns>
84
    protected virtual T GetValidValue<T>(ulong offset) => (T)GetValue(offset, typeof(T));
517,325✔
85

86
    public object GetValue(ulong offset)
87
    {
88
        if (!IsValid(offset)) return null!;
302,044!
89
        return GetValue(offset, ClrType);
302,044✔
90
    }
91

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

101
    internal object GetProviderSpecificValue(ulong offset) => GetValue(offset, ProviderSpecificClrType);
48✔
102

103
    protected virtual Type GetColumnType()
104
    {
105
        return DuckDBType switch
39,556!
106
        {
39,556✔
107
            DuckDBType.Invalid => throw new DuckDBException($"Invalid type for column {ColumnName}"),
×
108
            DuckDBType.Boolean => typeof(bool),
72✔
109
            DuckDBType.TinyInt => typeof(sbyte),
39✔
110
            DuckDBType.SmallInt => typeof(short),
39✔
111
            DuckDBType.Integer => typeof(int),
288✔
112
            DuckDBType.BigInt => typeof(long),
216✔
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),
57✔
119
            DuckDBType.Timestamp => typeof(DateTime),
81✔
120
            DuckDBType.Interval => typeof(TimeSpan),
15✔
121
            DuckDBType.Date => typeof(DateOnly),
57✔
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),
444✔
127
            DuckDBType.Decimal => typeof(decimal),
207✔
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),
24✔
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,522✔
138
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
×
139
        };
39,556✔
140
    }
141

142
    protected virtual Type GetColumnProviderSpecificType()
143
    {
144
        return DuckDBType switch
123!
145
        {
123✔
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✔
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,953,151✔
183

184
    /// <summary>
185
    /// Updates the data and validity pointers for a new chunk without recreating the reader.
186
    /// Composite readers (Struct, List, Map, Decimal) override this to also reset nested readers.
187
    /// </summary>
188
    internal virtual unsafe void Reset(IntPtr vector)
189
    {
190
        DataPointer = NativeMethods.Vectors.DuckDBVectorGetData(vector);
1,839✔
191
        validityMaskPointer = NativeMethods.Vectors.DuckDBVectorGetValidity(vector);
1,839✔
192
    }
1,839✔
193

194
    public virtual void Dispose()
195
    {
196
    }
40,957✔
197

198
    private static class NullableHandler<T>
199
    {
200
        private static Type type;
201
        private static Type? underlyingType;
202

203
        static NullableHandler()
204
        {
205
            type = typeof(T);
432✔
206

207
            var allowsNullValue = type.AllowsNullValue(out IsNullableValueType, out underlyingType);
432✔
208

209
            Read = IsNullableValueType ? Compile() : null!;
432✔
210
            IsReferenceType = allowsNullValue && !IsNullableValueType;
432✔
211
        }
432✔
212

213
        public static readonly bool IsNullableValueType;
214
        public static readonly bool IsReferenceType;
215
        public static readonly Func<VectorDataReaderBase, ulong, T> Read;
216

217
        // For T = int?, builds a delegate equivalent to:
218
        //   (VectorDataReaderBase reader, ulong offset) =>
219
        //       reader.IsValid(offset)
220
        //           ? (int?)reader.GetValidValue<int>(offset)
221
        //           : default(int?)
222
        private static Func<VectorDataReaderBase, ulong, T> Compile()
223
        {
224
            if (underlyingType is null) return null!;
69!
225

226
            var reader = Expression.Parameter(typeof(VectorDataReaderBase));
69✔
227
            var offset = Expression.Parameter(typeof(ulong));
69✔
228

229
            var isValid = Expression.Call(reader, typeof(VectorDataReaderBase).GetMethod(nameof(IsValid))!, offset);
69✔
230

231
            var methodInfo = typeof(VectorDataReaderBase).GetMethod(nameof(GetValidValue), BindingFlags.Instance | BindingFlags.NonPublic)!;
69✔
232
            var genericGetValidValue = methodInfo.MakeGenericMethod(underlyingType);
69✔
233

234
            var getValidValue = Expression.Call(reader, genericGetValidValue, offset);
69✔
235

236
            var body = Expression.Condition(isValid, Expression.Convert(getValidValue, type), Expression.Default(type));
69✔
237

238
            return Expression.Lambda<Func<VectorDataReaderBase, ulong, T>>(body, reader, offset).Compile();
69✔
239
        }
240
    }
241
}
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