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

Giorgi / DuckDB.NET / 40296324877

26 Oct 2026 09:00AM UTC coverage: 92.811% (+0.3%) from 92.531%
40296324877

push

github

Giorgi
Test the allocator grouping on whichever option the running DuckDB has

DuckDB's main branch replaced the block_allocator_memory setting with
enable_block_allocator (duckdb/duckdb#27520), so the nightly build failed to
find it. The grouping rule covers both, as both names have an allocator and a
block segment. The test now takes whichever option the running DuckDB has, so
it runs against DuckDB 2.0 and against the nightly builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F9b3CGWkCMzmhoxge1QiZ8

2599 of 2865 branches covered (90.72%)

Branch coverage included in aggregate %.

4205 of 4466 relevant lines covered (94.16%)

1056578.13 hits per line

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

93.28
/DuckDB.NET.Data/DataChunk/Reader/VectorDataReaderBase.cs
1
using System.Diagnostics.CodeAnalysis;
2
using System.IO;
3
using System.Runtime.CompilerServices;
4

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

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

11
    public Type ClrType => field ??= GetColumnType();
13,112,412 ✔
12

13
    public Type ProviderSpecificClrType => field ??= GetColumnProviderSpecificType();
288 ✔
14

15

16
    public string ColumnName { get; }
233,876 ✔
17
    public DuckDBType DuckDBType { get; }
88,937,657 ✔
18
    private protected unsafe void* DataPointer { get; private set; }
96,200,820 ✔
19

20
    // The .NET type whose memory layout is exactly what the vector holds, or null when every read needs a
21
    // conversion. A reader sets it once, and a read of that type then skips GetValidValue, which is a generic
22
    // virtual method and so is looked up at run time on every call.
23
    private protected Type? StorageType { get; init; }
76,808,136 ✔
24

25
    internal unsafe VectorDataReaderBase(void* dataPointer, ulong* validityMaskPointer, DuckDBType columnType, string columnName)
56,081 ✔
26
    {
27
        DataPointer = dataPointer;
56,081 ✔
28
        this.validityMaskPointer = validityMaskPointer;
56,081 ✔
29

30
        DuckDBType = columnType;
56,081 ✔
31
        ColumnName = columnName;
56,081 ✔
32
    }
56,081 ✔
33

34
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
35
    public unsafe bool IsValid(ulong offset)
36
    {
37
        if (validityMaskPointer == default)
116,189,864 ✔
38
        {
39
            return true;
101,301,983 ✔
40
        }
41

42
        return ValidityMask.IsValid(validityMaskPointer, offset);
14,887,881 ✔
43
    }
44

45
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
46
    public T GetValue<T>(ulong offset) => GetValue<T>(offset, strict: false);
333,093 ✔
47

48
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
49
    internal T GetValueStrict<T>(ulong offset) => GetValue<T>(offset, strict: true);
54,253,837 ✔
50

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

63
        if (IsValid(offset))
54,322,882 ✔
64
        {
65
            return ReadValidValue<T>(offset);
54,322,693 ✔
66
        }
67

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

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

83
    // One method for each type that DbDataReader has a getter for, and for DateTimeOffset, DateOnly and TimeOnly,
84
    // which are read with GetFieldValue. GetValidValue is a generic virtual method, which
85
    // is looked up at run time on every call. These are plain virtual methods, which are not, and ReadValidValue
86
    // calls them for their types. A reader overrides the ones it produces, and an override calls GetValidValue of
87
    // its own class, so a value is converted by the same code whichever way it is reached.
88
    internal virtual bool ReadBoolean(ulong offset) => GetValidValue<bool>(offset);
6 ✔
89
    internal virtual byte ReadByte(ulong offset) => GetValidValue<byte>(offset);
303 ✔
90
    internal virtual short ReadInt16(ulong offset) => GetValidValue<short>(offset);
300 ✔
91
    internal virtual int ReadInt32(ulong offset) => GetValidValue<int>(offset);
339 ✔
92
    internal virtual long ReadInt64(ulong offset) => GetValidValue<long>(offset);
306 ✔
93
    internal virtual float ReadFloat(ulong offset) => GetValidValue<float>(offset);
2,550 ✔
94
    internal virtual double ReadDouble(ulong offset) => GetValidValue<double>(offset);
2,559 ✔
95
    internal virtual decimal ReadDecimal(ulong offset) => GetValidValue<decimal>(offset);
1,296 ✔
96
    internal virtual DateTime ReadDateTime(ulong offset) => GetValidValue<DateTime>(offset);
6 ✔
97
    internal virtual DateTimeOffset ReadDateTimeOffset(ulong offset) => GetValidValue<DateTimeOffset>(offset);
×
98
    internal virtual DateOnly ReadDateOnly(ulong offset) => GetValidValue<DateOnly>(offset);
×
99
    internal virtual TimeOnly ReadTimeOnly(ulong offset) => GetValidValue<TimeOnly>(offset);
×
100
    internal virtual Guid ReadGuid(ulong offset) => GetValidValue<Guid>(offset);
9 ✔
101
    internal virtual string ReadString(ulong offset) => GetValidValue<string>(offset);
9,241 ✔
102
    internal virtual Stream ReadStream(ulong offset) => GetValidValue<Stream>(offset);
3 ✔
103

104
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
105
    internal unsafe T ReadValidValue<T>(ulong offset)
106
    {
107
        // typeof(T).IsValueType is a constant for the JIT, so a reference type does not keep this test.
108
        if (typeof(T).IsValueType && typeof(T) == StorageType)
84,312,276 ✔
109
        {
110
            return Unsafe.Read<T>((byte*)DataPointer + offset * (ulong)Unsafe.SizeOf<T>());
18,666,088 ✔
111
        }
112

113
        // A test of T against a type is a constant for the JIT too, so at most one of these lines is left.
114
        if (typeof(T) == typeof(bool))     return (T)(object)ReadBoolean(offset);
65,646,194 ✔
115
        if (typeof(T) == typeof(byte))     return (T)(object)ReadByte(offset);
65,658,477 ✔
116
        if (typeof(T) == typeof(short))    return (T)(object)ReadInt16(offset);
65,646,176 ✔
117
        if (typeof(T) == typeof(int))      return (T)(object)ReadInt32(offset);
65,668,315 ✔
118
        if (typeof(T) == typeof(long))     return (T)(object)ReadInt64(offset);
65,827,266 ✔
119
        if (typeof(T) == typeof(float))    return (T)(object)ReadFloat(offset);
65,328,037 ✔
120
        if (typeof(T) == typeof(double))   return (T)(object)ReadDouble(offset);
65,440,954 ✔
121
        if (typeof(T) == typeof(decimal))  return (T)(object)ReadDecimal(offset);
66,802,157 ✔
122
        if (typeof(T) == typeof(DateTime)) return (T)(object)ReadDateTime(offset);
88,215,968 ✔
123
        if (typeof(T) == typeof(DateTimeOffset)) return (T)(object)ReadDateTimeOffset(offset);
40,195,955 ✔
124
        if (typeof(T) == typeof(DateOnly)) return (T)(object)ReadDateOnly(offset);
48,821,987 ✔
125
        if (typeof(T) == typeof(TimeOnly)) return (T)(object)ReadTimeOnly(offset);
27,247,525 ✔
126
        if (typeof(T) == typeof(Guid))     return (T)(object)ReadGuid(offset);
27,317,406 ✔
127
        if (typeof(T) == typeof(string))   return (T)(object)ReadString(offset);
26,726,258 ✔
128
        if (typeof(T) == typeof(Stream))   return (T)(object)ReadStream(offset);
22,486,052 ✔
129
        if (typeof(T) == typeof(object))   return (T)GetValueCore(offset, ClrType);
22,289,327 ✔
130

131
        return GetValidValue<T>(offset);
22,286,561 ✔
132
    }
133

134
    public object GetValue(ulong offset)
135
    {
136
        if (!IsValid(offset)) return null!;
12,665,420 !
137
        return GetValueCore(offset, ClrType);
12,665,420 ✔
138
    }
139

140
    // The value as targetType, which is known only at run time: a list item, a map key or value, a struct field read
141
    // into a property without dynamic code. object means the column's own .NET type, as GetFieldValue<object> and
142
    // GetValue(ordinal) give it, for every reader; ReadValidValue<object> does the same on the typed path.
143
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
144
    internal object GetValue(ulong offset, Type targetType) => GetValueCore(offset, targetType == typeof(object) ? ClrType : targetType);
5,338,629 ✔
145

146
    // What each reader converts. Called with any type but object.
147
    internal virtual object GetValueCore(ulong offset, Type targetType)
148
    {
149
        return DuckDBType switch
3 !
150
        {
3 ✔
151
            DuckDBType.Invalid => throw new DuckDBException($"Invalid type for column {ColumnName}"),
×
152
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
3 ✔
153
        };
3 ✔
154
    }
155

156
    internal object GetProviderSpecificValue(ulong offset) => GetValueCore(offset, ProviderSpecificClrType);
99 ✔
157

158
    protected virtual Type GetColumnType()
159
    {
160
        return DuckDBType switch
20,771 !
161
        {
20,771 ✔
162
            DuckDBType.Invalid => throw new DuckDBException($"Invalid type for column {ColumnName}"),
×
163
            DuckDBType.Boolean => typeof(bool),
252 ✔
164
            DuckDBType.TinyInt => typeof(sbyte),
159 ✔
165
            DuckDBType.SmallInt => typeof(short),
186 ✔
166
            DuckDBType.Integer => typeof(int),
1,908 ✔
167
            DuckDBType.BigInt => typeof(long),
558 ✔
168
            DuckDBType.UnsignedTinyInt => typeof(byte),
129 ✔
169
            DuckDBType.UnsignedSmallInt => typeof(ushort),
141 ✔
170
            DuckDBType.UnsignedInteger => typeof(uint),
156 ✔
171
            DuckDBType.UnsignedBigInt => typeof(ulong),
210 ✔
172
            DuckDBType.Float => typeof(float),
219 ✔
173
            DuckDBType.Double => typeof(double),
273 ✔
174
            DuckDBType.Timestamp => typeof(DateTime),
129 ✔
175
            DuckDBType.Interval => typeof(TimeSpan),
48 ✔
176
            DuckDBType.Date => typeof(DateOnly),
117 ✔
177
            DuckDBType.Time => typeof(TimeOnly),
135 ✔
178
            DuckDBType.TimeTz => typeof(DateTimeOffset),
72 ✔
179
            DuckDBType.HugeInt => typeof(BigInteger),
231 ✔
180
            DuckDBType.UnsignedHugeInt => typeof(BigInteger),
162 ✔
181
            DuckDBType.Varchar => typeof(string),
1,038 ✔
182
            DuckDBType.Decimal => typeof(decimal),
840 ✔
183
            DuckDBType.TimestampS => typeof(DateTime),
48 ✔
184
            DuckDBType.TimestampMs => typeof(DateTime),
48 ✔
185
            DuckDBType.TimestampNs => typeof(DateTime),
93 ✔
186
            DuckDBType.Blob => typeof(Stream),
30 ✔
187
            DuckDBType.Enum => typeof(string),
192 ✔
188
            DuckDBType.Uuid => typeof(Guid),
48 ✔
189
            DuckDBType.Struct => typeof(Dictionary<string, object>),
51 ✔
190
            DuckDBType.Bit => typeof(string),
21 ✔
191
            DuckDBType.TimestampTz => typeof(DateTime),
48 ✔
192
            DuckDBType.VarInt => typeof(BigInteger),
13,214 ✔
193
            DuckDBType.SqlNull => typeof(object),
9 ✔
194
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
6 ✔
195
        };
20,771 ✔
196
    }
197

198
    protected virtual Type GetColumnProviderSpecificType()
199
    {
200
        return DuckDBType switch
180 !
201
        {
180 ✔
202
            DuckDBType.Invalid => throw new DuckDBException($"Invalid type for column {ColumnName}"),
×
203
            DuckDBType.Boolean => typeof(bool),
3 ✔
204
            DuckDBType.TinyInt => typeof(sbyte),
3 ✔
205
            DuckDBType.SmallInt => typeof(short),
3 ✔
206
            DuckDBType.Integer => typeof(int),
9 ✔
207
            DuckDBType.BigInt => typeof(long),
3 ✔
208
            DuckDBType.UnsignedTinyInt => typeof(byte),
3 ✔
209
            DuckDBType.UnsignedSmallInt => typeof(ushort),
3 ✔
210
            DuckDBType.UnsignedInteger => typeof(uint),
3 ✔
211
            DuckDBType.UnsignedBigInt => typeof(ulong),
3 ✔
212
            DuckDBType.Float => typeof(float),
3 ✔
213
            DuckDBType.Double => typeof(double),
6 ✔
214
            DuckDBType.Timestamp => typeof(DuckDBTimestamp),
9 ✔
215
            DuckDBType.Interval => typeof(DuckDBInterval),
3 ✔
216
            DuckDBType.Date => typeof(DuckDBDateOnly),
6 ✔
217
            DuckDBType.Time => typeof(DuckDBTimeOnly),
3 ✔
218
            DuckDBType.TimeTz => typeof(DuckDBTimeTz),
3 ✔
219
            DuckDBType.HugeInt => typeof(DuckDBHugeInt),
15 ✔
220
            DuckDBType.UnsignedHugeInt => typeof(DuckDBUHugeInt),
12 ✔
221
            DuckDBType.Varchar => typeof(string),
9 ✔
222
            DuckDBType.Decimal => typeof(DuckDBDecimal),
39 ✔
223
            DuckDBType.TimestampS => typeof(DuckDBTimestamp),
3 ✔
224
            DuckDBType.TimestampMs => typeof(DuckDBTimestamp),
3 ✔
225
            DuckDBType.TimestampNs => typeof(DuckDBTimestamp),
3 ✔
226
            DuckDBType.Blob => typeof(Stream),
3 ✔
227
            DuckDBType.Enum => typeof(string),
9 ✔
228
            DuckDBType.Uuid => typeof(Guid),
3 ✔
229
            DuckDBType.Struct => typeof(Dictionary<string, object>),
3 ✔
230
            DuckDBType.Bit => typeof(string),
3 ✔
231
            DuckDBType.TimestampTz => typeof(DuckDBTimestamp),
6 ✔
232
            DuckDBType.VarInt => typeof(BigInteger),
3 ✔
233
            DuckDBType.SqlNull => typeof(object),
×
234
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
×
235
        };
180 ✔
236
    }
237

238
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
239
    protected unsafe T GetFieldData<T>(ulong offset) where T : unmanaged => *((T*)DataPointer + offset);
70,982,027 ✔
240

241
    // Kept out of the callers: building the message inline puts its buffers in the stack frame of a method
242
    // that almost never throws, and that frame is cleared on every call.
243
    [MethodImpl(MethodImplOptions.NoInlining)]
244
    private protected InvalidCastException CannotCast(Type from, Type to) => CannotCast(from.Name, to);
23,556 ✔
245

246
    [MethodImpl(MethodImplOptions.NoInlining)]
247
    private protected InvalidCastException CannotCast(string from, Type to) => new($"Cannot cast from {from} to {to.Name} in column {ColumnName}");
123,939 ✔
248

249
    // A FLOAT, DOUBLE or DECIMAL with a fraction, read as an integer type (2.0 item 5).
250
    [MethodImpl(MethodImplOptions.NoInlining)]
251
    private protected InvalidCastException CannotCastFraction(Type to) => new($"Cannot cast a value with a non-zero fractional part to {to.Name} in column {ColumnName}");
8,985 ✔
252

253
    /// <summary>
254
    /// Updates the data and validity pointers for a new chunk without recreating the reader.
255
    /// Composite readers (Struct, List, Map, Decimal) override this to also reset nested readers.
256
    /// </summary>
257
    internal virtual unsafe void Reset(IntPtr vector)
258
    {
259
        DataPointer = NativeMethods.Vectors.DuckDBVectorGetData(vector);
16,569 ✔
260
        validityMaskPointer = NativeMethods.Vectors.DuckDBVectorGetValidity(vector);
16,569 ✔
261
    }
16,569 ✔
262

263
    public virtual void Dispose()
264
    {
265
    }
39,347 ✔
266

267
    private sealed class NullableReader<TValue> where TValue : struct
268
    {
269
        public TValue? Read(VectorDataReaderBase reader, ulong offset)
270
        {
271
            return reader.IsValid(offset) ? reader.ReadValidValue<TValue>(offset) : null;
264,048 ✔
272
        }
273
    }
274

275
    private static class NullableHandler<T>
276
    {
277
        private static readonly Type? underlyingType;
278

279
        static NullableHandler()
280
        {
281
            var type = typeof(T);
1,218 ✔
282

283
            var allowsNullValue = type.AllowsNullValue(out IsNullableValueType, out underlyingType);
1,218 ✔
284

285
            Read = IsNullableValueType ? Compile() : null!;
1,218 ✔
286
            IsReferenceType = allowsNullValue && !IsNullableValueType;
1,218 ✔
287
        }
1,218 ✔
288

289
        public static readonly bool IsNullableValueType;
290
        public static readonly bool IsReferenceType;
291
        public static readonly Func<VectorDataReaderBase, ulong, T> Read;
292

293
        // For T = int?, a delegate for the Read method of a NullableReader<int>. It is an ordinary method, so the
294
        // JIT compiles it in tiers and with profile data like any other; a delegate compiled from an expression
295
        // tree is compiled once. An instance method, because a delegate for a static method is slower to call.
296
        //
297
        // The reader is found in two ways, because of applications that are trimmed or compiled ahead of time:
298
        // - The common types, and the structs of this library, are named below. That is code a compiler can see,
299
        //   so an application compiled ahead of time has a NullableReader for each of them.
300
        // - Any other type, such as an enum of the caller, is found through reflection. The attribute keeps the
301
        //   trimmer from removing the constructor and the Read method, which nothing calls by name.
302
        // - An application compiled ahead of time has no code for such a type and cannot make any. There the
303
        //   value is read as an object and unboxed, which needs no code for the type. It is slower.
304
        [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods, typeof(NullableReader<>))]
305
        private static Func<VectorDataReaderBase, ulong, T> Compile()
306
        {
307
            if (underlyingType is null) return null!;
102 !
308

309
            var known = Known<bool>() ?? Known<byte>() ?? Known<sbyte>() ?? Known<short>() ?? Known<ushort>()
102 ✔
310
                        ?? Known<int>() ?? Known<uint>() ?? Known<long>() ?? Known<ulong>()
102 ✔
311
                        ?? Known<float>() ?? Known<double>() ?? Known<decimal>() ?? Known<BigInteger>()
102 ✔
312
                        ?? Known<DateTime>() ?? Known<DateOnly>() ?? Known<TimeOnly>() ?? Known<TimeSpan>() ?? Known<DateTimeOffset>()
102 ✔
313
                        ?? Known<Guid>()
102 ✔
314
                        ?? Known<DuckDBDateOnly>() ?? Known<DuckDBTimeOnly>() ?? Known<DuckDBTimeTz>() ?? Known<DuckDBTimestamp>() ?? Known<DuckDBInterval>()
102 ✔
315
                        ?? Known<DuckDBHugeInt>() ?? Known<DuckDBUHugeInt>() ?? Known<DuckDBDecimal>();
102 ✔
316

317
            if (known != null)
102 ✔
318
            {
319
                return known;
81 ✔
320
            }
321

322
            if (RuntimeFeature.IsDynamicCodeSupported)
21 !
323
            {
324
                var reader = Activator.CreateInstance(typeof(NullableReader<>).MakeGenericType(underlyingType))!;
21 ✔
325

326
                return (Func<VectorDataReaderBase, ulong, T>)Delegate.CreateDelegate(typeof(Func<VectorDataReaderBase, ulong, T>), reader, nameof(NullableReader<int>.Read));
21 ✔
327
            }
328

329
            var valueType = underlyingType;
×
330

331
            return (reader, offset) => reader.IsValid(offset) ? (T)reader.GetValue(offset, valueType) : default!;
×
332
        }
333

334
        private static Func<VectorDataReaderBase, ulong, T>? Known<TValue>() where TValue : struct
335
        {
336
            if (typeof(T) != typeof(TValue?))
1,701 ✔
337
            {
338
                return null;
1,620 ✔
339
            }
340

341
            return (Func<VectorDataReaderBase, ulong, T>)(object)new Func<VectorDataReaderBase, ulong, TValue?>(new NullableReader<TValue>().Read);
81 ✔
342
        }
343
    }
344
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc