• 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.27
/DuckDB.NET.Data/DuckDBConnection.TableFunction.cs
1
using DuckDB.NET.Data.Common;
2
using DuckDB.NET.Data.Connection;
3
using DuckDB.NET.Data.DataChunk.Writer;
4
using System.Runtime.CompilerServices;
5
using System.Runtime.InteropServices;
6

7
namespace DuckDB.NET.Data;
8

9
public record ColumnInfo(string Name, Type Type);
807✔
10

11
public record TableFunction(IReadOnlyList<ColumnInfo> Columns, IEnumerable Data);
408✔
12

13
partial class DuckDBConnection
14
{
15
    public void RegisterTableFunction(string name, Func<TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
16
    {
17
        RegisterTableFunctionInternal(name, (_) => resultCallback(), mapperCallback, Array.Empty<Type>());
18✔
18
    }
9✔
19

20
    public void RegisterTableFunction<T>(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
21
    {
22
        RegisterTableFunctionInternal(name, resultCallback, mapperCallback, typeof(T));
24✔
23
    }
24✔
24

25
    public void RegisterTableFunction<T1, T2>(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
26
    {
27
        RegisterTableFunctionInternal(name, resultCallback, mapperCallback, typeof(T1), typeof(T2));
9✔
28
    }
9✔
29

30
    public void RegisterTableFunction<T1, T2, T3>(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
31
    {
32
        RegisterTableFunctionInternal(name, resultCallback, mapperCallback, typeof(T1), typeof(T2), typeof(T3));
3✔
33
    }
3✔
34

35
    public void RegisterTableFunction<T1, T2, T3, T4>(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
36
    {
37
        RegisterTableFunctionInternal(name, resultCallback, mapperCallback, typeof(T1), typeof(T2), typeof(T3), typeof(T4));
3✔
38
    }
3✔
39

40
    public void RegisterTableFunction<T1, T2, T3, T4, T5>(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
41
    {
42
        RegisterTableFunctionInternal(name, resultCallback, mapperCallback, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5));
3✔
43
    }
3✔
44

45
    public void RegisterTableFunction<T1, T2, T3, T4, T5, T6>(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
46
    {
47
        RegisterTableFunctionInternal(name, resultCallback, mapperCallback, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6));
×
48
    }
×
49

50
    public void RegisterTableFunction<T1, T2, T3, T4, T5, T6, T7>(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
51
    {
52
        RegisterTableFunctionInternal(name, resultCallback, mapperCallback, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7));
×
53
    }
×
54

55
    public void RegisterTableFunction<T1, T2, T3, T4, T5, T6, T7, T8>(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback)
56
    {
57
        RegisterTableFunctionInternal(name, resultCallback, mapperCallback, typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8));
×
58
    }
×
59

60
    public void RegisterTableFunction(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback, params DuckDBType[] parameterTypes)
61
    {
62
        var logicalTypes = Array.ConvertAll(parameterTypes, NativeMethods.LogicalType.DuckDBCreateLogicalType);
6✔
63
        RegisterTableFunctionInternal(name, (positional, _) => resultCallback(positional), mapperCallback, logicalTypes, []);
12✔
64
    }
6✔
65

66
    private void RegisterTableFunctionInternal(string name, Func<IReadOnlyList<IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback, params Type[] parameterTypes)
67
    {
68
        var logicalTypes = Array.ConvertAll(parameterTypes, TypeExtensions.GetLogicalType);
51✔
69
        RegisterTableFunctionInternal(name, (positional, _) => resultCallback(positional), mapperCallback, logicalTypes, []);
102✔
70
    }
51✔
71

72
    internal unsafe void RegisterTableFunctionInternal(string name, Func<IReadOnlyList<IDuckDBValueReader>, IReadOnlyDictionary<string, IDuckDBValueReader>, TableFunction> resultCallback, Action<object?, IDuckDBDataWriter[], ulong> mapperCallback, DuckDBLogicalType[] positionalLogicalTypes, NamedParameterDefinition[] namedParameters)
73
    {
74
        var function = NativeMethods.TableFunction.DuckDBCreateTableFunction();
102✔
75
        NativeMethods.TableFunction.DuckDBTableFunctionSetName(function, name);
102✔
76

77
        foreach (var logicalType in positionalLogicalTypes)
516✔
78
        {
79
            NativeMethods.TableFunction.DuckDBTableFunctionAddParameter(function, logicalType);
156✔
80
            logicalType.Dispose();
156✔
81
        }
82

83
        foreach (var param in namedParameters)
234✔
84
        {
85
            using var logicalType = param.Type.GetLogicalType();
15✔
86
            NativeMethods.TableFunction.DuckDBTableFunctionAddNamedParameter(function, param.Name, logicalType);
15✔
87
        }
88

89
        var tableFunctionInfo = new TableFunctionInfo(resultCallback, mapperCallback, Array.ConvertAll(namedParameters, p => p.Name));
117✔
90

91
        NativeMethods.TableFunction.DuckDBTableFunctionSetBind(function, &Bind);
102✔
92
        NativeMethods.TableFunction.DuckDBTableFunctionSetInit(function, &Init);
102✔
93
        NativeMethods.TableFunction.DuckDBTableFunctionSetFunction(function, &TableFunction);
102✔
94
        NativeMethods.TableFunction.DuckDBTableFunctionSetExtraInfo(function, tableFunctionInfo.ToHandle(), &DestroyExtraInfo);
102✔
95

96
        var state = NativeMethods.TableFunction.DuckDBRegisterTableFunction(NativeConnection, function);
102✔
97

98
        NativeMethods.TableFunction.DuckDBDestroyTableFunction(ref function);
102✔
99

100
        if (!state.IsSuccess())
102!
101
        {
102
            throw new InvalidOperationException($"Error registering user defined table function: {name}");
×
103
        }
104
    }
102✔
105

106
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
107
    private static unsafe void Bind(IntPtr info)
108
    {
109
        IDuckDBValueReader[] parameters = [];
114✔
110
        Dictionary<string, IDuckDBValueReader> named = [];
114✔
111
        try
112
        {
113
            var handle = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBBindGetExtraInfo(info));
114✔
114

115
            if (handle.Target is not TableFunctionInfo functionInfo)
114!
116
            {
117
                throw new InvalidOperationException("User defined table function bind failed. Bind extra info is null");
×
118
            }
119

120
            parameters = new IDuckDBValueReader[NativeMethods.TableFunction.DuckDBBindGetParameterCount(info)];
114✔
121

122
            for (var i = 0; i < parameters.Length; i++)
564✔
123
            {
124
                var value = NativeMethods.TableFunction.DuckDBBindGetParameter(info, (ulong)i);
168✔
125
                parameters[i] = value;
168✔
126
            }
127

128
            // When a named parameter is omitted in SQL, duckdb_bind_get_named_parameter returns a null pointer.
129
            // We substitute it with NullValueReader so CompileValueReader's IsNull() check
130
            // correctly handles it (returns default for nullable, throws for non-nullable).
131
            foreach (var paramName in functionInfo.NamedParameterNames)
282✔
132
            {
133
                var value = NativeMethods.TableFunction.DuckDBBindGetNamedParameter(info, paramName);
27✔
134
                named[paramName] = value.IsInvalid ? NullValueReader.Instance : value;
27✔
135
            }
136

137
            var tableFunctionData = functionInfo.Bind(parameters, named);
114✔
138

139
            foreach (var columnInfo in tableFunctionData.Columns)
534✔
140
            {
141
                using var logicalType = columnInfo.Type.GetLogicalType();
165✔
142
                NativeMethods.TableFunction.DuckDBBindAddResultColumn(info, columnInfo.Name, logicalType);
165✔
143
            }
144

145
            var connectionId = UdfExceptionStore.GetTableFunctionBindConnectionId(info);
102✔
146
            var bindData = new TableFunctionBindData(tableFunctionData.Columns, tableFunctionData.Data.GetEnumerator(), connectionId);
102✔
147

148
            NativeMethods.TableFunction.DuckDBBindSetBindData(info, bindData.ToHandle(), &DestroyExtraInfo);
102✔
149
        }
102✔
150
        catch (Exception ex)
12✔
151
        {
152
            try
153
            {
154
                var connectionId = UdfExceptionStore.GetTableFunctionBindConnectionId(info);
12✔
155
                UdfExceptionStore.Store(connectionId, ex);
12✔
156
            }
12✔
157
            catch
×
158
            {
159
                // If we can't get the connection ID, we still report the error message
160
            }
×
161

162
            NativeMethods.TableFunction.DuckDBBindSetError(info, ex.Message);
12✔
163
        }
12✔
164
        finally
165
        {
166
            foreach (var parameter in parameters)
564✔
167
            {
168
                (parameter as IDisposable)?.Dispose();
168!
169
            }
170

171
            foreach (var namedParam in named.Values)
282✔
172
            {
173
                (namedParam as IDisposable)?.Dispose();
27✔
174
            }
175
        }
114✔
176
    }
114✔
177

178
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
179
    private static void Init(IntPtr info) { }
102✔
180

181
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
182
    private static void TableFunction(IntPtr info, IntPtr chunk)
183
    {
184
        VectorDataWriterBase[] writers = [];
195✔
185
        DuckDBLogicalType[] logicalTypes = [];
195✔
186
        try
187
        {
188
            var bindData = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBFunctionGetBindData(info));
195✔
189
            var extraInfo = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBFunctionGetExtraInfo(info));
195✔
190

191
            if (bindData.Target is not TableFunctionBindData tableFunctionBindData)
195!
192
            {
193
                throw new InvalidOperationException("User defined table function failed. Function bind data is null");
×
194
            }
195

196
            if (extraInfo.Target is not TableFunctionInfo tableFunctionInfo)
195!
197
            {
198
                throw new InvalidOperationException("User defined table function failed. Function extra info is null");
×
199
            }
200

201
            var dataChunk = new DuckDBDataChunk(chunk);
195✔
202

203
            writers = new VectorDataWriterBase[tableFunctionBindData.Columns.Count];
195✔
204
            logicalTypes = new DuckDBLogicalType[tableFunctionBindData.Columns.Count];
195✔
205

206
            for (var columnIndex = 0; columnIndex < tableFunctionBindData.Columns.Count; columnIndex++)
1,044✔
207
            {
208
                var column = tableFunctionBindData.Columns[columnIndex];
327✔
209
                var vector = NativeMethods.DataChunks.DuckDBDataChunkGetVector(dataChunk, columnIndex);
327✔
210

211
                logicalTypes[columnIndex] = column.Type.GetLogicalType();
327✔
212
                writers[columnIndex] = VectorDataWriterFactory.CreateWriter(vector, logicalTypes[columnIndex]);
327✔
213
            }
214

215
            ulong size = 0;
195✔
216

217
            for (; size < DuckDBGlobalData.VectorSize; size++)
31,275✔
218
            {
219
                if (tableFunctionBindData.DataEnumerator.MoveNext())
15,729✔
220
                {
221
                    tableFunctionInfo.Mapper(tableFunctionBindData.DataEnumerator.Current, writers, size);
15,546✔
222
                }
223
                else
224
                {
225
                    break;
226
                }
227
            }
228

229
            NativeMethods.DataChunks.DuckDBDataChunkSetSize(dataChunk, size);
189✔
230
        }
189✔
231
        catch (Exception ex)
6✔
232
        {
233
            try
234
            {
235
                var bindData = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBFunctionGetBindData(info));
6✔
236
                if (bindData.Target is TableFunctionBindData tableFunctionBindData)
6✔
237
                {
238
                    UdfExceptionStore.Store(tableFunctionBindData.ConnectionId, ex);
6✔
239
                }
240
            }
6✔
241
            catch
×
242
            {
243
                // If we can't get the connection ID, we still report the error message
244
            }
×
245

246
            NativeMethods.TableFunction.DuckDBFunctionSetError(info, ex.Message);
6✔
247
        }
6✔
248
        finally
249
        {
250
            foreach (var writer in writers)
1,044✔
251
            {
252
                writer.Dispose();
327✔
253
            }
254

255
            foreach (var logicalType in logicalTypes)
1,044✔
256
            {
257
                logicalType?.Dispose();
327!
258
            }
259
        }
195✔
260
    }
195✔
261

262
    private class NullValueReader : IDuckDBValueReader
263
    {
264
        public static readonly NullValueReader Instance = new();
3✔
265
        public bool IsNull() => true;
9✔
266
        public T GetValue<T>() => throw new InvalidOperationException("Cannot read value from a null parameter.");
×
267
    }
268
}
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