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

Giorgi / DuckDB.NET / 28126041613

24 Jun 2026 08:00PM UTC coverage: 89.485% (+0.1%) from 89.339%
28126041613

push

github

Giorgi
Use duckdb_appender_error_data for appender error handling

Switch the appender from duckdb_appender_error (plain string) to
duckdb_appender_error_data, which exposes both the message and the
DuckDB error type. Route all appender error sites through the existing
DuckDBErrorData.ThrowOnError extension (message prefix now optional),
so appender exceptions carry ErrorType, consistent with the Arrow paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWKN1SKSTFDyeqSrA4dfYT

1362 of 1583 branches covered (86.04%)

Branch coverage included in aggregate %.

2 of 6 new or added lines in 4 files covered. (33.33%)

1 existing line in 1 file now uncovered.

2859 of 3134 relevant lines covered (91.23%)

421690.1 hits per line

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

92.5
/DuckDB.NET.Data/DuckDBConnection.cs
1
using DuckDB.NET.Data.Connection;
2
using System.ComponentModel;
3
using System.Diagnostics.CodeAnalysis;
4
using System.Runtime.CompilerServices;
5

6
namespace DuckDB.NET.Data;
7

8
public partial class DuckDBConnection : DbConnection
9
{
10
    private readonly ConnectionManager connectionManager = ConnectionManager.Default;
64,905✔
11
    private ConnectionState connectionState = ConnectionState.Closed;
12
    private DuckDBConnectionString? parsedConnection;
13
    private ConnectionReference? connectionReference;
14
    private bool inMemoryDuplication = false;
15
    
16
    private static readonly StateChangeEventArgs FromClosedToOpenEventArgs = new(ConnectionState.Closed, ConnectionState.Open);
3✔
17
    private static readonly StateChangeEventArgs FromOpenToClosedEventArgs = new(ConnectionState.Open, ConnectionState.Closed);
3✔
18

19
    #region Protected Properties
20

21
    protected override DbProviderFactory? DbProviderFactory => DuckDBClientFactory.Instance;
×
22

23
    #endregion
24

25
    internal DuckDBTransaction? Transaction { get; set; }
81,414✔
26

27
    internal DuckDBConnectionString ParsedConnection => parsedConnection ??= DuckDBConnectionStringBuilder.Parse(ConnectionString);
64,977✔
28

29
    public DuckDBConnection()
3✔
30
    {
31
        ConnectionString = string.Empty;
3✔
32
    }
3✔
33

34
    public DuckDBConnection(string connectionString)
64,902✔
35
    {
36
        ConnectionString = connectionString;
64,902✔
37
    }
64,902✔
38

39
    [AllowNull]
40
    [DefaultValue("")]
41
    public override string ConnectionString { get; set; }
190,362✔
42

43
    public override string Database
44
    {
45
        get
46
        {
47
            if (!string.IsNullOrEmpty(ConnectionString))
6✔
48
            {
49
                return ParsedConnection.DataSource;
3✔
50
            }
51

52
            throw new InvalidOperationException("Connection string must be specified.");
3✔
53
        }
54
    }
55

56
    public override string DataSource
57
    {
58
        get
59
        {
60
            if (!string.IsNullOrEmpty(ConnectionString))
6✔
61
            {
62
                return ParsedConnection!.DataSource;
3✔
63
            }
64

65
            throw new InvalidOperationException("Connection string must be specified.");
3✔
66
        }
67
    }
68

69
    /// <summary>
70
    /// Returns the native connection object that can be used to call DuckDB C API functions.
71
    /// </summary>
72
    public DuckDBNativeConnection NativeConnection => connectionReference?.NativeConnection
158,769!
73
                                                      ?? throw new InvalidOperationException("The DuckDBConnection must be open to access the native connection.");
158,769✔
74

75
    public override string ServerVersion => NativeMethods.Startup.DuckDBLibraryVersion();
×
76

77
    public override ConnectionState State => connectionState;
259,227✔
78

79
    public override void ChangeDatabase(string databaseName)
80
    {
81
        throw new NotSupportedException();
×
82
    }
83

84
    public override void Close()
85
    {
86
        if (connectionState == ConnectionState.Closed)
64,947✔
87
        {
88
            throw new InvalidOperationException("Connection is already closed.");
6✔
89
        }
90

91
        if (connectionReference is not null) //Should always be the case
64,941✔
92
        {
93
            connectionManager.ReturnConnectionReference(connectionReference);
64,941✔
94
        }
95

96
        connectionState = ConnectionState.Closed;
64,941✔
97
        OnStateChange(FromOpenToClosedEventArgs);
64,941✔
98
    }
64,941✔
99

100
    public override void Open()
101
    {
102
        if (connectionState == ConnectionState.Open)
64,968✔
103
        {
104
            throw new InvalidOperationException("Connection is already open.");
3✔
105
        }
106

107
        //In case of inMemoryDuplication, we can safely take the hypothesis that connectionReference is already assigned
108
        connectionReference = inMemoryDuplication ? connectionManager.DuplicateConnectionReference(connectionReference!)
64,965✔
109
                                                  : connectionManager.GetConnectionReference(ParsedConnection);
64,965✔
110

111
        connectionState = ConnectionState.Open;
64,947✔
112
        OnStateChange(FromClosedToOpenEventArgs);
64,947✔
113
    }
64,947✔
114

115
    protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
116
    {
117
        return BeginTransaction(isolationLevel);
6✔
118
    }
119

120
    public new DuckDBTransaction BeginTransaction()
121
    {
122
        return BeginTransaction(IsolationLevel.Unspecified);
30✔
123
    }
124

125
    private new DuckDBTransaction BeginTransaction(IsolationLevel isolationLevel)
126
    {
127
        EnsureConnectionOpen();
36✔
128
        if (Transaction != null)
33✔
129
        {
130
            throw new InvalidOperationException("Already in a transaction.");
3✔
131
        }
132

133
        return Transaction = new DuckDBTransaction(this, isolationLevel);
30✔
134
    }
135

136
    protected override DbCommand CreateDbCommand()
137
    {
138
        return CreateCommand();
60,708✔
139
    }
140

141
    public new virtual DuckDBCommand CreateCommand()
142
    {
143
        return new DuckDBCommand
81,330✔
144
        {
81,330✔
145
            Connection = this,
81,330✔
146
            Transaction = Transaction
81,330✔
147
        };
81,330✔
148
    }
149

150
    public DuckDBAppender CreateAppender(string table) => CreateAppender(null, null, table);
195✔
151

152
    public DuckDBAppender CreateAppender(string? schema, string table) => CreateAppender(null, schema, table);
9✔
153

154
    public DuckDBAppender CreateAppender(string? catalog, string? schema, string table)
155
    {
156
        EnsureConnectionOpen();
219✔
157

158
        var appenderState = NativeMethods.Appender.DuckDBAppenderCreateExt(NativeConnection, catalog, schema, table, out var nativeAppender);
219✔
159

160
        if (!appenderState.IsSuccess())
219✔
161
        {
162
            try
163
            {
164
                NativeMethods.Appender.DuckDBAppenderErrorData(nativeAppender).ThrowOnError();
3✔
UNCOV
165
            }
×
166
            finally
167
            {
168
                nativeAppender.Close();
3✔
169
            }
3✔
170
        }
171

172
        return new DuckDBAppender(nativeAppender, GetTableName());
216✔
173

174
        string GetTableName()
175
        {
176
            return string.IsNullOrEmpty(schema) ? table : $"{schema}.{table}";
216✔
177
        }
178
    }
179

180
    /// <summary>
181
    /// Creates a type-safe appender using an AppenderMap for property-to-column mappings.
182
    /// </summary>
183
    /// <typeparam name="T">The type to append</typeparam>
184
    /// <typeparam name="TMap">The AppenderMap type defining the mappings</typeparam>
185
    /// <param name="table">The table name</param>
186
    /// <returns>A type-safe mapped appender</returns>
187
    public DuckDBMappedAppender<T, TMap> CreateAppender<T, TMap>(string table) 
188
        where TMap : Mapping.DuckDBAppenderMap<T>, new()
189
    {
190
        return CreateAppender<T, TMap>(null, null, table);
12✔
191
    }
192

193
    /// <summary>
194
    /// Creates a type-safe appender using an AppenderMap for property-to-column mappings.
195
    /// </summary>
196
    /// <typeparam name="T">The type to append</typeparam>
197
    /// <typeparam name="TMap">The AppenderMap type defining the mappings</typeparam>
198
    /// <param name="schema">The schema name</param>
199
    /// <param name="table">The table name</param>
200
    /// <returns>A type-safe mapped appender</returns>
201
    public DuckDBMappedAppender<T, TMap> CreateAppender<T, TMap>(string? schema, string table)
202
        where TMap : Mapping.DuckDBAppenderMap<T>, new()
203
    {
204
        return CreateAppender<T, TMap>(null, schema, table);
×
205
    }
206

207
    /// <summary>
208
    /// Creates a type-safe appender using an AppenderMap for property-to-column mappings.
209
    /// </summary>
210
    /// <typeparam name="T">The type to append</typeparam>
211
    /// <typeparam name="TMap">The AppenderMap type defining the mappings</typeparam>
212
    /// <param name="catalog">The catalog name</param>
213
    /// <param name="schema">The schema name</param>
214
    /// <param name="table">The table name</param>
215
    /// <returns>A type-safe mapped appender</returns>
216
    public DuckDBMappedAppender<T, TMap> CreateAppender<T, TMap>(string? catalog, string? schema, string table)
217
        where TMap : Mapping.DuckDBAppenderMap<T>, new()
218
    {
219
        var appender = CreateAppender(catalog, schema, table);
12✔
220
        return new DuckDBMappedAppender<T, TMap>(appender);
12✔
221
    }
222

223
    protected override void Dispose(bool disposing)
224
    {
225
        if (disposing)
64,913✔
226
        {
227
            // this check is to ensure exact same behavior as previous version
228
            // where Close() was calling Dispose(true) instead of the other way around.
229
            if (connectionState == ConnectionState.Open)
64,911✔
230
            {
231
                Close();
64,863✔
232
            }
233
        }
234

235
        base.Dispose(disposing);
64,913✔
236
    }
64,913✔
237

238
    private void EnsureConnectionOpen([CallerMemberName] string operation = "")
239
    {
240
        if (State != ConnectionState.Open)
264✔
241
        {
242
            throw new InvalidOperationException($"{operation} requires an open connection");
6✔
243
        }
244
    }
258✔
245

246
    public DuckDBConnection Duplicate()
247
    {
248
        EnsureConnectionOpen();
9✔
249

250
        // We're sure that the connectionString is not null because we previously checked the connection was open
251
        if (!ParsedConnection!.InMemory)
6✔
252
        {
253
            throw new NotSupportedException("Duplication of the connection is only supported for in-memory connections.");
3✔
254
        }
255

256
        var duplicatedConnection = new DuckDBConnection(ConnectionString)
3✔
257
        {
3✔
258
            parsedConnection = ParsedConnection,
3✔
259
            inMemoryDuplication = true,
3✔
260
            connectionReference = connectionReference,
3✔
261
        };
3✔
262

263
        return duplicatedConnection;
3✔
264
    }
265

266
    public override DataTable GetSchema() =>
267
        GetSchema(DbMetaDataCollectionNames.MetaDataCollections);
3✔
268

269
    public override DataTable GetSchema(string collectionName) =>
270
        GetSchema(collectionName, null);
45✔
271

272
    public override DataTable GetSchema(string collectionName, string?[]? restrictionValues) =>
273
        DuckDBSchema.GetSchema(this, collectionName, restrictionValues);
72✔
274

275
    public DuckDBQueryProgress GetQueryProgress()
276
    {
277
        EnsureConnectionOpen();
×
278
        return NativeMethods.Startup.DuckDBQueryProgress(NativeConnection);
×
279
    }
280
}
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