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

Giorgi / DuckDB.NET / 28017109290

23 Jun 2026 09:41AM UTC coverage: 89.361% (-0.4%) from 89.738%
28017109290

push

github

Giorgi
Merge branch 'main' into nightly-builds

1330 of 1545 branches covered (86.08%)

Branch coverage included in aggregate %.

362 of 413 new or added lines in 32 files covered. (87.65%)

4 existing lines in 3 files now uncovered.

2769 of 3042 relevant lines covered (91.03%)

434285.19 hits per line

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

93.14
/DuckDB.NET.Data/DuckDBConnection.ScalarFunction.Extensions.cs
1
using DuckDB.NET.Data.DataChunk.Reader;
2
using DuckDB.NET.Data.DataChunk.Writer;
3
using System.Diagnostics.CodeAnalysis;
4
using System.Linq;
5
using System.Reflection;
6
using System.Runtime.CompilerServices;
7

8
namespace DuckDB.NET.Data;
9

10
public static class DuckDBConnectionScalarFunctionExtensions
11
{
12
    extension(DuckDBConnection connection)
13
    {
14
        public void RegisterScalarFunction<TResult>(string name, Func<TResult> func)
15
        {
16
            connection.RegisterScalarFunction<TResult>(name, (writer, rowCount) =>
3✔
17
            {
3✔
18
                for (ulong index = 0; index < rowCount; index++)
36✔
19
                {
3✔
20
                    writer.WriteValue(func(), index);
15✔
21
                }
3✔
22
            });
6✔
23
        }
3✔
24

25
        public void RegisterScalarFunction<T, TResult>(string name, Func<T, TResult> func)
26
        {
27
            var (nullability, anyNullable) = InferParameterNullability(func);
27✔
28

29
            connection.RegisterScalarFunction<T, TResult>(name, WrapScalarFunction<TResult>((readers, index) =>
27✔
30
                func(ReadValue<T>(readers[0], index, nullability[0], anyNullable))), new() { HandlesNulls = anyNullable });
369✔
31
        }
27✔
32

33
        public void RegisterScalarFunction<T, TResult>(string name, Func<T[], TResult> func)
34
        {
35
            var elementNullable = InferArrayElementNullability(func);
21✔
36

37
            connection.RegisterScalarFunction<T, TResult>(name,
21✔
38
                WrapVarargsScalarFunction(func, elementNullable), new() { HandlesNulls = elementNullable }, @params: true);
21✔
39
        }
21✔
40

41
        public void RegisterScalarFunction<T1, T2, TResult>(string name, Func<T1, T2, TResult> func)
42
        {
43
            var (nullability, anyNullable) = InferParameterNullability(func);
18✔
44

45
            connection.RegisterScalarFunction<T1, T2, TResult>(name, WrapScalarFunction<TResult>((readers, index) =>
18✔
46
                func(ReadValue<T1>(readers[0], index, nullability[0], anyNullable), ReadValue<T2>(readers[1], index, nullability[1], anyNullable))),
27✔
47
                new() { HandlesNulls = anyNullable });
18✔
48
        }
18✔
49

50
        public void RegisterScalarFunction<T1, T2, T3, TResult>(string name, Func<T1, T2, T3, TResult> func)
51
        {
52
            var (nullability, anyNullable) = InferParameterNullability(func);
6✔
53

54
            connection.RegisterScalarFunction<T1, T2, T3, TResult>(name, WrapScalarFunction<TResult>((readers, index) =>
6✔
55
                func(ReadValue<T1>(readers[0], index, nullability[0], anyNullable), ReadValue<T2>(readers[1], index, nullability[1], anyNullable),
36✔
56
                     ReadValue<T3>(readers[2], index, nullability[2], anyNullable))),
36✔
57
                new() { HandlesNulls = anyNullable });
6✔
58
        }
6✔
59

60
        public void RegisterScalarFunction<T1, T2, T3, T4, TResult>(string name, Func<T1, T2, T3, T4, TResult> func)
61
        {
NEW
62
            var (nullability, anyNullable) = InferParameterNullability(func);
×
63

64
            connection.RegisterScalarFunction<T1, T2, T3, T4, TResult>(name, WrapScalarFunction<TResult>((readers, index) =>
×
NEW
65
                func(ReadValue<T1>(readers[0], index, nullability[0], anyNullable), ReadValue<T2>(readers[1], index, nullability[1], anyNullable),
×
NEW
66
                     ReadValue<T3>(readers[2], index, nullability[2], anyNullable), ReadValue<T4>(readers[3], index, nullability[3], anyNullable))),
×
NEW
67
                new() { HandlesNulls = anyNullable });
×
UNCOV
68
        }
×
69
    }
70

71
    // checksNulls: true when any parameter in the function is nullable (special handling active).
72
    // Needed because set_special_handling is function-level — DuckDB sends NULLs for ALL params,
73
    // so non-nullable params must also check and throw a descriptive error.
74
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
75
    private static T ReadValue<T>(IDuckDBDataReader reader, ulong index, bool isNullable, bool checksNulls)
76
    {
77
        if (checksNulls && !reader.IsValid(index))
30,588✔
78
        {
79
            if (isNullable) return default!;
42✔
80
            ThrowNullReceivedByNonNullableParam<T>();
12✔
81
        }
82

83
        return typeof(T) == typeof(object) ? (T)reader.GetValue(index) : reader.GetValue<T>(index);
30,561✔
84
    }
85

86
    [DoesNotReturn]
87
    private static void ThrowNullReceivedByNonNullableParam<T>()
88
    {
89
        throw new InvalidOperationException(
12✔
90
            $"Scalar function parameter of type '{typeof(T).Name}' received NULL. " +
12✔
91
            $"Use '{typeof(T).Name}?' to handle NULL values.");
12✔
92
    }
93

94
    private static Action<IReadOnlyList<IDuckDBDataReader>, IDuckDBDataWriter, ulong> WrapScalarFunction<TResult>(Func<IReadOnlyList<IDuckDBDataReader>, ulong, TResult> perRowFunc)
95
    {
96
        return (readers, writer, rowCount) =>
51✔
97
        {
51✔
98
            for (ulong index = 0; index < rowCount; index++)
918✔
99
            {
51✔
100
                var result = perRowFunc(readers, index);
405✔
101

51✔
102
                writer.WriteValue(result, index);
393✔
103
            }
51✔
104
        };
105✔
105
    }
106

107
    private static Action<IReadOnlyList<IDuckDBDataReader>, IDuckDBDataWriter, ulong> WrapVarargsScalarFunction<T, TResult>(Func<T[], TResult> func, bool elementNullable)
108
    {
109
        return (readers, writer, rowCount) =>
21✔
110
        {
21✔
111
            var args = new T[readers.Count];
48✔
112

21✔
113
            for (ulong index = 0; index < rowCount; index++)
30,174✔
114
            {
21✔
115
                for (int r = 0; r < readers.Count; r++)
90,258✔
116
                {
21✔
117
                    args[r] = ReadValue<T>(readers[r], index, elementNullable, elementNullable);
30,090✔
118
                }
21✔
119

21✔
120
                writer.WriteValue(func(args), index);
15,039✔
121
            }
21✔
122
        };
69✔
123
    }
124

125
    private static (bool[] perParam, bool anyNullable) InferParameterNullability(Delegate func)
126
    {
127
        var context = new NullabilityInfoContext();
51✔
128
        var parameters = func.Method.GetParameters();
51✔
129
        var result = parameters.Select(info => IsNullableParameter(context, info)).ToArray();
132✔
130

131
        return (result, result.Any(static x => x));
126✔
132
    }
133

134
    private static bool InferArrayElementNullability(Delegate func)
135
    {
136
        var parameter = func.Method.GetParameters()[0];
21✔
137
        var elementType = parameter.ParameterType.GetElementType();
21✔
138

139
        // Nullable<T> value types: detectable without attributes
140
        if (elementType != null && Nullable.GetUnderlyingType(elementType) != null)
21✔
141
            return true;
3✔
142

143
        // Reference types: check nullable annotation
144
        var context = new NullabilityInfoContext();
18✔
145
        var info = context.Create(parameter);
18✔
146
        return info.ElementType?.ReadState == NullabilityState.Nullable;
18!
147
    }
148

149
    private static bool IsNullableParameter(NullabilityInfoContext context, ParameterInfo parameter)
150
    {
151
        // Nullable<T> value types are always nullable
152
        if (Nullable.GetUnderlyingType(parameter.ParameterType) != null)
81✔
153
            return true;
12✔
154

155
        // Reference types: check nullable annotation
156
        var info = context.Create(parameter);
69✔
157
        return info.ReadState == NullabilityState.Nullable;
69✔
158
    }
159

160
}
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