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

Giorgi / DuckDB.NET / 26973994547

04 Jun 2026 07:17PM UTC coverage: 89.738% (-0.04%) from 89.775%
26973994547

push

github

Giorgi
Map DuckDB SQLNULL type to object in vector reader

DuckDB 1.6 reports untyped NULLs (e.g. an unbound null parameter in
SELECT ?) as the new SQLNULL type (id 36). GetColumnType and
GetColumnProviderSpecificType had no case for it, so GetFieldType threw
ArgumentException ("Unrecognised type SqlNull"). Map it to object, the
BCL-safe choice for a null/variant column (typeof(DBNull) cannot be used
as a DataColumn storage type). The null value itself is still surfaced
via IsDBNull/DBNull.Value at the value layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

1268 of 1469 branches covered (86.32%)

Branch coverage included in aggregate %.

1 of 2 new or added lines in 1 file covered. (50.0%)

2 existing lines in 1 file now uncovered.

2606 of 2848 relevant lines covered (91.5%)

308909.42 hits per line

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

86.7
/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 unsafe ulong* validityMaskPointer;
9

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

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

16

17
    public string ColumnName { get; }
108,786✔
18
    public DuckDBType DuckDBType { get; }
14,533,856✔
19
    private protected unsafe void* DataPointer { get; private set; }
14,511,124✔
20

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

26
        DuckDBType = columnType;
38,406✔
27
        ColumnName = columnName;
38,406✔
28
    }
38,406✔
29

30
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
31
    public unsafe bool IsValid(ulong offset)
32
    {
33
        if (validityMaskPointer == default)
21,194,616✔
34
        {
35
            return true;
12,648,629✔
36
        }
37

38
        var validityMaskEntryIndex = offset / 64;
8,545,987✔
39
        var validityBitIndex = (int)(offset % 64);
8,545,987✔
40

41
        var validityMaskEntryPtr = validityMaskPointer + validityMaskEntryIndex;
8,545,987✔
42
        var validityBit = 1ul << validityBitIndex;
8,545,987✔
43

44
        var isValid = (*validityMaskEntryPtr & validityBit) != 0;
8,545,987✔
45
        return isValid;
8,545,987✔
46
    }
47

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

52
        var isValid = IsValid(offset);
3,845,532✔
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)
3,845,532✔
56
        {
57
            return isValid
1,001,657✔
58
                ? (T)GetValue(offset, Nullable.GetUnderlyingType(targetType)!)
1,001,657✔
59
                : default!; //T is Nullable<> and we are returning null so suppress compiler warning.
1,001,657✔
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)
2,843,875✔
65
        {
66
            return GetValidValue<T>(offset, targetType);
2,843,753✔
67
        }
68
        
69
        throw new InvalidCastException($"Column '{ColumnName}' value is null");
122✔
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);
344,884✔
82
    }
83

84
    public object GetValue(ulong offset)
85
    {
86
        return GetValue(offset, ClrType);
201,284✔
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}"),
×
94
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
×
95
        };
×
96
    }
97

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

103
    protected virtual Type GetColumnType()
104
    {
105
        return DuckDBType switch
26,348!
106
        {
26,348✔
107
            DuckDBType.Invalid => throw new DuckDBException($"Invalid type for column {ColumnName}"),
×
108
            DuckDBType.Boolean => typeof(bool),
48✔
109
            DuckDBType.TinyInt => typeof(sbyte),
26✔
110
            DuckDBType.SmallInt => typeof(short),
26✔
111
            DuckDBType.Integer => typeof(int),
186✔
112
            DuckDBType.BigInt => typeof(long),
144✔
113
            DuckDBType.UnsignedTinyInt => typeof(byte),
28✔
114
            DuckDBType.UnsignedSmallInt => typeof(ushort),
26✔
115
            DuckDBType.UnsignedInteger => typeof(uint),
26✔
116
            DuckDBType.UnsignedBigInt => typeof(ulong),
38✔
117
            DuckDBType.Float => typeof(float),
18✔
118
            DuckDBType.Double => typeof(double),
38✔
119
            DuckDBType.Timestamp => typeof(DateTime),
54✔
120
            DuckDBType.Interval => typeof(TimeSpan),
10✔
121
            DuckDBType.Date => typeof(DateOnly),
38✔
122
            DuckDBType.Time => typeof(TimeOnly),
64✔
123
            DuckDBType.TimeTz => typeof(DateTimeOffset),
14✔
124
            DuckDBType.HugeInt => typeof(BigInteger),
10✔
125
            DuckDBType.UnsignedHugeInt => typeof(BigInteger),
2✔
126
            DuckDBType.Varchar => typeof(string),
272✔
127
            DuckDBType.Decimal => typeof(decimal),
138✔
128
            DuckDBType.TimestampS => typeof(DateTime),
14✔
129
            DuckDBType.TimestampMs => typeof(DateTime),
14✔
130
            DuckDBType.TimestampNs => typeof(DateTime),
16✔
131
            DuckDBType.Blob => typeof(Stream),
6✔
132
            DuckDBType.Enum => typeof(string),
28✔
133
            DuckDBType.Uuid => typeof(Guid),
16✔
134
            DuckDBType.Struct => typeof(Dictionary<string, object>),
6✔
135
            DuckDBType.Bit => typeof(string),
4✔
136
            DuckDBType.TimestampTz => typeof(DateTime),
16✔
137
            DuckDBType.VarInt => typeof(BigInteger),
25,016✔
138
            DuckDBType.SqlNull => typeof(object),
6✔
UNCOV
139
            _ => throw new ArgumentException($"Unrecognised type {DuckDBType} ({(int)DuckDBType}) for column {ColumnName}")
×
140
        };
26,348✔
141
    }
142

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

183
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
184
    protected unsafe T GetFieldData<T>(ulong offset) where T : unmanaged => *((T*)DataPointer + offset);
11,329,280✔
185

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

196
    public virtual void Dispose()
197
    {
198
    }
27,206✔
199
}
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