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

Giorgi / DuckDB.NET / 42287341530

06 Nov 2026 08:19PM UTC coverage: 93.658% (+0.05%) from 93.605%
42287341530

push

github

Giorgi
Merge develop into nightly-builds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiK1sBYAZhYuVtwFvBQ6x

# Conflicts:
#	DuckDB.NET.Data/DuckDBConnection.cs

2885 of 3131 branches covered (92.14%)

Branch coverage included in aggregate %.

192 of 204 new or added lines in 6 files covered. (94.12%)

4587 of 4847 relevant lines covered (94.64%)

999292.87 hits per line

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

89.86
/DuckDB.NET.Data/PreparedStatement/PreparedStatementEntry.cs
1
using System.Threading;
2

3
namespace DuckDB.NET.Data.PreparedStatement;
4

5
/// <summary>
6
/// A statement prepared once by <see cref="DuckDBCommand.Prepare"/> and kept by its connection, which destroys it when
7
/// it closes. What the binding needs (the count, the type of each parameter, the index of each name) is read from
8
/// DuckDB once, before any value is bound, instead of on every execution. Once a value is bound, DuckDB reports a
9
/// parameter whose type it could not infer (as in <c>SELECT $1</c>) as the type of that value until the bindings are
10
/// cleared, so the types must not be read in between.
11
/// </summary>
12
internal sealed class PreparedStatementEntry : IDisposable
13
{
14
    private readonly DuckDBPreparedStatement statement;
15
    private readonly DuckDBLogicalType[] parameterLogicalTypes;
16
    private readonly DuckDBType[] parameterTypes;
17
    private Dictionary<string, long>? parameterIndexes;
18
    private int inUse;
19
    private int disposed;
20
    private volatile bool retired;
21

22
    public PreparedStatementEntry(DuckDBPreparedStatement statement)
117 ✔
23
    {
24
        this.statement = statement;
117 ✔
25
        StatementType = NativeMethods.PreparedStatements.DuckDBPreparedStatementType(statement);
117 ✔
26
        ParameterCount = NativeMethods.PreparedStatements.DuckDBParams(statement);
117 ✔
27

28
        parameterLogicalTypes = new DuckDBLogicalType[ParameterCount];
117 ✔
29
        parameterTypes = new DuckDBType[ParameterCount];
117 ✔
30

31
        try
32
        {
33
            for (var index = 0; index < ParameterCount; index++)
408 ✔
34
            {
35
                parameterLogicalTypes[index] = NativeMethods.PreparedStatements.DuckDBParamLogicalType(statement, index + 1);
87 ✔
36
                parameterTypes[index] = NativeMethods.LogicalType.DuckDBGetTypeId(parameterLogicalTypes[index]);
87 ✔
37
            }
38
        }
117 ✔
NEW
39
        catch
×
40
        {
NEW
41
            DisposeParameterTypes();
×
NEW
42
            throw;
×
43
        }
44
    }
117 ✔
45

46
    public DuckDBStatementType StatementType { get; }
1,272 ✔
47

48
    public long ParameterCount { get; }
1,713 ✔
49

50
    public bool IsDisposed => Volatile.Read(ref disposed) != 0;
2,821 ✔
51

52
    /// <summary>
53
    /// Destroyed, or replaced by a newer preparation of the same text: a command that holds it takes the current
54
    /// statement from the connection instead.
55
    /// </summary>
56
    public bool IsStale => IsDisposed || retired;
1,394 ✔
57

58
    /// <summary>How many times it was executed; tests use it to see which statement an execution took.</summary>
59
    public int ExecutionCount { get; private set; }
2,565 ✔
60

61
    public void MarkRetired() => retired = true;
33 ✔
62

63
    /// <summary>
64
    /// Takes the statement for one execution. False when another execution has it (two commands with the same text,
65
    /// on two threads of one connection): the caller then prepares a statement of its own, as an unprepared command
66
    /// does. DuckDB does not allow binding one statement from two threads.
67
    /// </summary>
68
    public bool TryRent() => !IsDisposed && Interlocked.CompareExchange(ref inUse, 1, 0) == 0;
1,418 ✔
69

70
    /// <summary>
71
    /// Gives the statement back after an execution. One that was replaced while it was executing is destroyed here:
72
    /// nobody else uses it any more, and the connection would otherwise keep it until it closes.
73
    /// </summary>
74
    public void Return()
75
    {
76
        // A full fence: the read of retired below must not move before this write, or a Retire() on another thread
77
        // could see the entry still taken while this thread sees it not yet retired, and nobody would destroy it.
78
        Interlocked.Exchange(ref inUse, 0);
1,284 ✔
79

80
        if (retired && TryRent())
1,284 ✔
81
        {
82
            Dispose();
5 ✔
83
        }
84
    }
1,284 ✔
85

86
    /// <summary>
87
    /// Binds the parameters and executes. The bindings of the previous execution are cleared first, so a named
88
    /// parameter that is no longer in the collection is reported as missing instead of keeping its old value.
89
    /// </summary>
90
    public DuckDBResult Execute(DuckDBParameterCollection parameters, bool useStreamingMode, DuckDBNativeConnection connection)
91
    {
92
        if (!NativeMethods.PreparedStatements.DuckDBClearBindings(statement).IsSuccess())
1,275 !
93
        {
NEW
94
            var errorMessage = NativeMethods.PreparedStatements.DuckDBPrepareError(statement);
×
NEW
95
            throw new InvalidOperationException($"Unable to clear the bindings of the prepared statement: {errorMessage}");
×
96
        }
97

98
        PreparedStatement.BindParameters(statement, parameters, this);
1,275 ✔
99
        ExecutionCount++;
1,275 ✔
100

101
        return PreparedStatement.Execute(statement, useStreamingMode, connection);
1,275 ✔
102
    }
103

104
    public (DuckDBLogicalType LogicalType, DuckDBType Type) GetParameterType(long index) =>
105
        (parameterLogicalTypes[index - 1], parameterTypes[index - 1]);
2,373 ✔
106

107
    public bool TryGetParameterIndex(string name, out long index)
108
    {
109
        parameterIndexes ??= new Dictionary<string, long>(StringComparer.Ordinal);
24 ✔
110

111
        if (!parameterIndexes.TryGetValue(name, out index))
24 ✔
112
        {
113
            // A name the statement does not declare is kept as 0, so it is not looked up again.
114
            index = NativeMethods.PreparedStatements.DuckDBBindParameterIndex(statement, out var found, name).IsSuccess() ? found : 0;
15 ✔
115
            parameterIndexes.Add(name, index);
15 ✔
116
        }
117

118
        return index > 0;
24 ✔
119
    }
120

121
    public void Dispose()
122
    {
123
        if (Interlocked.Exchange(ref disposed, 1) != 0)
122 ✔
124
        {
125
            return;
5 ✔
126
        }
127

128
        DisposeParameterTypes();
117 ✔
129
        statement.Dispose();
117 ✔
130
    }
117 ✔
131

132
    private void DisposeParameterTypes()
133
    {
134
        foreach (var logicalType in parameterLogicalTypes)
408 ✔
135
        {
136
            logicalType?.Dispose();
87 !
137
        }
138
    }
117 ✔
139
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc