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

Giorgi / DuckDB.NET / 22921736195

10 Mar 2026 07:24PM UTC coverage: 89.526% (+0.08%) from 89.45%
22921736195

push

github

Giorgi
Update global.json

1255 of 1463 branches covered (85.78%)

Branch coverage included in aggregate %.

2651 of 2900 relevant lines covered (91.41%)

447643.55 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,365✔
13

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

16

17
    public string ColumnName { get; }
162,903✔
18
    public DuckDBType DuckDBType { get; }
21,783,934✔
19
    private protected unsafe void* DataPointer { get; private set; }
21,749,182✔
20

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

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

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

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

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

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

48
    public T GetValue<T>(ulong offset) => GetValue<T>(offset, strict: false);
271,302✔
49

50
    internal T GetValueStrict<T>(ulong offset) => GetValue<T>(offset, strict: true);
5,491,858✔
51

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)
5,763,160✔
59
        {
60
            return NullableHandler<T>.Read(this, offset);
1,501,709✔
61
        }
62

63
        if (IsValid(offset))
4,261,451✔
64
        {
65
            return GetValidValue<T>(offset, typeof(T));
4,261,265✔
66
        }
67

68
        if (strict || !NullableHandler<T>.IsReferenceType)
186✔
69
        {
70
            throw new InvalidCastException($"Column '{ColumnName}' value is null");
183✔
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
    /// <param name="targetType">Type of the return value</param>
81
    /// <returns>Data at the specified offset</returns>
82
    protected virtual T GetValidValue<T>(ulong offset, Type targetType) => (T)GetValue(offset, targetType);
517,326✔
83

84
    public object GetValue(ulong offset)
85
    {
86
        if (!IsValid(offset)) return null!;
302,019!
87
        return GetValue(offset, ClrType);
302,019✔
88
    }
89

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

99
    internal object GetProviderSpecificValue(ulong offset) => GetValue(offset, ProviderSpecificClrType);
48✔
100

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

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

179
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
180
    protected unsafe T GetFieldData<T>(ulong offset) where T : unmanaged => *((T*)DataPointer + offset);
16,957,815✔
181

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

192
    public virtual void Dispose()
193
    {
194
    }
40,959✔
195

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

201
        static NullableHandler()
202
        {
203
            type = typeof(T);
432✔
204

205
            var allowsNullValue = type.AllowsNullValue(out IsNullableValueType, out underlyingType);
432✔
206

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

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

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

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

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

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

232
            var getValidValue = Expression.Call(reader, genericGetValidValue, offset, Expression.Constant(underlyingType));
69✔
233

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

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