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

Giorgi / DuckDB.NET / 22500794591

27 Feb 2026 07:31PM UTC coverage: 89.868% (+0.2%) from 89.658%
22500794591

push

github

Giorgi
Add `object` type support for simplified scalar functions

Extract `ReadValue<T>` helper that dispatches to non-generic
`GetValue` when T is `object`, enabling dynamic type resolution
at runtime.

1267 of 1467 branches covered (86.37%)

Branch coverage included in aggregate %.

5 of 7 new or added lines in 1 file covered. (71.43%)

9 existing lines in 3 files now uncovered.

2609 of 2846 relevant lines covered (91.67%)

462743.29 hits per line

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

88.74
/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);
795✔
10

11
public record TableFunction(IReadOnlyList<ColumnInfo> Columns, IEnumerable Data);
396✔
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));
18✔
23
    }
18✔
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);
45✔
69
        RegisterTableFunctionInternal(name, (positional, _) => resultCallback(positional), mapperCallback, logicalTypes, []);
90✔
70
    }
45✔
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();
96✔
75
        NativeMethods.TableFunction.DuckDBTableFunctionSetName(function, name);
96✔
76

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

83
        foreach (var param in namedParameters)
222✔
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));
111✔
90

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

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

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

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

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

115
            if (handle.Target is not TableFunctionInfo functionInfo)
108!
116
            {
UNCOV
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)];
108✔
121

122
            for (var i = 0; i < parameters.Length; i++)
540✔
123
            {
124
                var value = NativeMethods.TableFunction.DuckDBBindGetParameter(info, (ulong)i);
162✔
125
                parameters[i] = value;
162✔
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)
270✔
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);
108✔
138

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

145
            var bindData = new TableFunctionBindData(tableFunctionData.Columns, tableFunctionData.Data.GetEnumerator());
99✔
146

147
            NativeMethods.TableFunction.DuckDBBindSetBindData(info, bindData.ToHandle(), &DestroyExtraInfo);
99✔
148
        }
99✔
149
        catch (Exception ex)
9✔
150
        {
151
            NativeMethods.TableFunction.DuckDBBindSetError(info, ex.Message);
9✔
152
        }
9✔
153
        finally
154
        {
155
            foreach (var parameter in parameters)
540✔
156
            {
157
                (parameter as IDisposable)?.Dispose();
162!
158
            }
159

160
            foreach (var namedParam in named.Values)
270✔
161
            {
162
                (namedParam as IDisposable)?.Dispose();
27✔
163
            }
164
        }
108✔
165
    }
108✔
166

167
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
168
    private static void Init(IntPtr info) { }
99✔
169

170
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
171
    private static void TableFunction(IntPtr info, IntPtr chunk)
172
    {
173
        VectorDataWriterBase[] writers = [];
192✔
174
        DuckDBLogicalType[] logicalTypes = [];
192✔
175
        try
176
        {
177
            var bindData = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBFunctionGetBindData(info));
192✔
178
            var extraInfo = GCHandle.FromIntPtr(NativeMethods.TableFunction.DuckDBFunctionGetExtraInfo(info));
192✔
179

180
            if (bindData.Target is not TableFunctionBindData tableFunctionBindData)
192!
181
            {
UNCOV
182
                throw new InvalidOperationException("User defined table function failed. Function bind data is null");
×
183
            }
184

185
            if (extraInfo.Target is not TableFunctionInfo tableFunctionInfo)
192!
186
            {
UNCOV
187
                throw new InvalidOperationException("User defined table function failed. Function extra info is null");
×
188
            }
189

190
            var dataChunk = new DuckDBDataChunk(chunk);
192✔
191

192
            writers = new VectorDataWriterBase[tableFunctionBindData.Columns.Count];
192✔
193
            logicalTypes = new DuckDBLogicalType[tableFunctionBindData.Columns.Count];
192✔
194

195
            for (var columnIndex = 0; columnIndex < tableFunctionBindData.Columns.Count; columnIndex++)
1,032✔
196
            {
197
                var column = tableFunctionBindData.Columns[columnIndex];
324✔
198
                var vector = NativeMethods.DataChunks.DuckDBDataChunkGetVector(dataChunk, columnIndex);
324✔
199

200
                logicalTypes[columnIndex] = column.Type.GetLogicalType();
324✔
201
                writers[columnIndex] = VectorDataWriterFactory.CreateWriter(vector, logicalTypes[columnIndex]);
324✔
202
            }
203

204
            ulong size = 0;
192✔
205

206
            for (; size < DuckDBGlobalData.VectorSize; size++)
31,272✔
207
            {
208
                if (tableFunctionBindData.DataEnumerator.MoveNext())
15,726✔
209
                {
210
                    tableFunctionInfo.Mapper(tableFunctionBindData.DataEnumerator.Current, writers, size);
15,543✔
211
                }
212
                else
213
                {
214
                    break;
215
                }
216
            }
217

218
            NativeMethods.DataChunks.DuckDBDataChunkSetSize(dataChunk, size);
189✔
219
        }
189✔
220
        catch (Exception ex)
3✔
221
        {
222
            NativeMethods.TableFunction.DuckDBFunctionSetError(info, ex.Message);
3✔
223
        }
3✔
224
        finally
225
        {
226
            foreach (var writer in writers)
1,032✔
227
            {
228
                writer.Dispose();
324✔
229
            }
230

231
            foreach (var logicalType in logicalTypes)
1,032✔
232
            {
233
                logicalType?.Dispose();
324!
234
            }
235
        }
192✔
236
    }
192✔
237

238
    private class NullValueReader : IDuckDBValueReader
239
    {
240
        public static readonly NullValueReader Instance = new();
3✔
241
        public bool IsNull() => true;
9✔
UNCOV
242
        public T GetValue<T>() => throw new InvalidOperationException("Cannot read value from a null parameter.");
×
243
    }
244
}
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