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

Giorgi / DuckDB.NET / 18406925343

10 Oct 2025 12:46PM UTC coverage: 88.061%. First build
18406925343

Pull #296

github

web-flow
Merge 39d6c8fd2 into fd45dbbc9
Pull Request #296: Add ClassMap-based type-safe appender API to DuckDB.NET

1154 of 1361 branches covered (84.79%)

Branch coverage included in aggregate %.

106 of 155 new or added lines in 4 files covered. (68.39%)

2239 of 2492 relevant lines covered (89.85%)

587198.28 hits per line

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

93.5
/DuckDB.NET.Data/DuckDBConnection.cs
1
using DuckDB.NET.Data.Connection;
2
using DuckDB.NET.Native;
3
using System;
4
using System.ComponentModel;
5
using System.Data;
6
using System.Data.Common;
7
using System.Diagnostics.CodeAnalysis;
8
using System.Runtime.CompilerServices;
9

10
namespace DuckDB.NET.Data;
11

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

23
    #region Protected Properties
24

25
    protected override DbProviderFactory? DbProviderFactory => DuckDBClientFactory.Instance;
×
26

27
    #endregion
28

29
    internal DuckDBTransaction? Transaction { get; set; }
80,691✔
30

31
    internal DuckDBConnectionString ParsedConnection => parsedConnection ??= DuckDBConnectionStringBuilder.Parse(ConnectionString);
64,965✔
32

33
    public DuckDBConnection()
3✔
34
    {
35
        ConnectionString = string.Empty;
3✔
36
    }
3✔
37

38
    public DuckDBConnection(string connectionString)
64,890✔
39
    {
40
        ConnectionString = connectionString;
64,890✔
41
    }
64,890✔
42

43
#if NET6_0_OR_GREATER
44
    [AllowNull]
45
#endif
46
    [DefaultValue("")]
47
    public override string ConnectionString { get; set; }
190,116✔
48

49
    public override string Database
50
    {
51
        get
52
        {
53
            if (!string.IsNullOrEmpty(ConnectionString))
6✔
54
            {
55
                return ParsedConnection.DataSource;
3✔
56
            }
57

58
            throw new InvalidOperationException("Connection string must be specified.");
3✔
59
        }
60
    }
61

62
    public override string DataSource
63
    {
64
        get
65
        {
66
            if (!string.IsNullOrEmpty(ConnectionString))
6✔
67
            {
68
                return ParsedConnection!.DataSource;
3✔
69
            }
70

71
            throw new InvalidOperationException("Connection string must be specified.");
3✔
72
        }
73
    }
74

75
    /// <summary>
76
    /// Returns the native connection object that can be used to call DuckDB C API functions.
77
    /// </summary>
78
    public DuckDBNativeConnection NativeConnection => connectionReference?.NativeConnection
157,860!
79
                                                      ?? throw new InvalidOperationException("The DuckDBConnection must be open to access the native connection.");
157,860✔
80

81
    public override string ServerVersion => NativeMethods.Startup.DuckDBLibraryVersion().ToManagedString(false);
×
82

83
    public override ConnectionState State => connectionState;
257,745✔
84

85
    public override void ChangeDatabase(string databaseName)
86
    {
87
        throw new NotSupportedException();
×
88
    }
89

90
    public override void Close()
91
    {
92
        if (connectionState == ConnectionState.Closed)
64,935✔
93
        {
94
            throw new InvalidOperationException("Connection is already closed.");
6✔
95
        }
96

97
        if (connectionReference is not null) //Should always be the case
64,929✔
98
        {
99
            connectionManager.ReturnConnectionReference(connectionReference);
64,929✔
100
        }
101

102
        connectionState = ConnectionState.Closed;
64,929✔
103
        OnStateChange(FromOpenToClosedEventArgs);
64,929✔
104
    }
64,929✔
105

106
    public override void Open()
107
    {
108
        if (connectionState == ConnectionState.Open)
64,956✔
109
        {
110
            throw new InvalidOperationException("Connection is already open.");
3✔
111
        }
112

113
        //In case of inMemoryDuplication, we can safely take the hypothesis that connectionReference is already assigned
114
        connectionReference = inMemoryDuplication ? connectionManager.DuplicateConnectionReference(connectionReference!)
64,953✔
115
                                                  : connectionManager.GetConnectionReference(ParsedConnection);
64,953✔
116

117
        connectionState = ConnectionState.Open;
64,935✔
118
        OnStateChange(FromClosedToOpenEventArgs);
64,935✔
119
    }
64,935✔
120

121
    protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
122
    {
123
        return BeginTransaction(isolationLevel);
6✔
124
    }
125

126
    public new DuckDBTransaction BeginTransaction()
127
    {
128
        return BeginTransaction(IsolationLevel.Unspecified);
24✔
129
    }
130

131
    private new DuckDBTransaction BeginTransaction(IsolationLevel isolationLevel)
132
    {
133
        EnsureConnectionOpen();
30✔
134
        if (Transaction != null)
27✔
135
        {
136
            throw new InvalidOperationException("Already in a transaction.");
3✔
137
        }
138

139
        return Transaction = new DuckDBTransaction(this, isolationLevel);
24✔
140
    }
141

142
    protected override DbCommand CreateDbCommand()
143
    {
144
        return CreateCommand();
60,477✔
145
    }
146

147
    public new virtual DuckDBCommand CreateCommand()
148
    {
149
        return new DuckDBCommand
80,622✔
150
        {
80,622✔
151
            Connection = this,
80,622✔
152
            Transaction = Transaction
80,622✔
153
        };
80,622✔
154
    }
155

156
    public DuckDBAppender CreateAppender(string table) => CreateAppender(null, null, table);
171✔
157

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

160
    public DuckDBAppender CreateAppender(string? catalog, string? schema, string table)
161
    {
162
        EnsureConnectionOpen();
192✔
163
        using var unmanagedCatalog = catalog.ToUnmanagedString();
192✔
164
        using var unmanagedSchema = schema.ToUnmanagedString();
192✔
165
        using var unmanagedTable = table.ToUnmanagedString();
192✔
166

167
        var appenderState = NativeMethods.Appender.DuckDBAppenderCreateExt(NativeConnection, unmanagedCatalog, unmanagedSchema, unmanagedTable, out var nativeAppender);
192✔
168

169
        if (!appenderState.IsSuccess())
192✔
170
        {
171
            try
172
            {
173
                DuckDBAppender.ThrowLastError(nativeAppender);
3✔
174
            }
175
            finally
176
            {
177
                nativeAppender.Close();
3✔
178
            }
3✔
179
        }
180

181
        return new DuckDBAppender(nativeAppender, GetTableName());
189✔
182

183
        string GetTableName()
184
        {
185
            return string.IsNullOrEmpty(schema) ? table : $"{schema}.{table}";
189✔
186
        }
187
    }
189✔
188

189
    /// <summary>
190
    /// Creates a type-safe appender using a ClassMap for property-to-column mappings.
191
    /// </summary>
192
    /// <typeparam name="T">The type to append</typeparam>
193
    /// <typeparam name="TMap">The ClassMap type defining the mappings</typeparam>
194
    /// <param name="table">The table name</param>
195
    /// <returns>A type-safe mapped appender</returns>
196
    public DuckDBMappedAppender<T, TMap> CreateAppender<T, TMap>(string table) 
197
        where TMap : Mapping.DuckDBClassMap<T>, new()
198
    {
199
        return CreateAppender<T, TMap>(null, null, table);
9✔
200
    }
201

202
    /// <summary>
203
    /// Creates a type-safe appender using a ClassMap for property-to-column mappings.
204
    /// </summary>
205
    /// <typeparam name="T">The type to append</typeparam>
206
    /// <typeparam name="TMap">The ClassMap type defining the mappings</typeparam>
207
    /// <param name="schema">The schema name</param>
208
    /// <param name="table">The table name</param>
209
    /// <returns>A type-safe mapped appender</returns>
210
    public DuckDBMappedAppender<T, TMap> CreateAppender<T, TMap>(string? schema, string table)
211
        where TMap : Mapping.DuckDBClassMap<T>, new()
212
    {
NEW
213
        return CreateAppender<T, TMap>(null, schema, table);
×
214
    }
215

216
    /// <summary>
217
    /// Creates a type-safe appender using a ClassMap for property-to-column mappings.
218
    /// </summary>
219
    /// <typeparam name="T">The type to append</typeparam>
220
    /// <typeparam name="TMap">The ClassMap type defining the mappings</typeparam>
221
    /// <param name="catalog">The catalog name</param>
222
    /// <param name="schema">The schema name</param>
223
    /// <param name="table">The table name</param>
224
    /// <returns>A type-safe mapped appender</returns>
225
    public DuckDBMappedAppender<T, TMap> CreateAppender<T, TMap>(string? catalog, string? schema, string table)
226
        where TMap : Mapping.DuckDBClassMap<T>, new()
227
    {
228
        var appender = CreateAppender(catalog, schema, table);
9✔
229
        return new DuckDBMappedAppender<T, TMap>(appender);
9✔
230
    }
231

232
    protected override void Dispose(bool disposing)
233
    {
234
        if (disposing)
64,901✔
235
        {
236
            // this check is to ensure exact same behavior as previous version
237
            // where Close() was calling Dispose(true) instead of the other way around.
238
            if (connectionState == ConnectionState.Open)
64,899✔
239
            {
240
                Close();
64,851✔
241
            }
242
        }
243

244
        base.Dispose(disposing);
64,901✔
245
    }
64,901✔
246

247
    private void EnsureConnectionOpen([CallerMemberName] string operation = "")
248
    {
249
        if (State != ConnectionState.Open)
231✔
250
        {
251
            throw new InvalidOperationException($"{operation} requires an open connection");
6✔
252
        }
253
    }
225✔
254

255
    public DuckDBConnection Duplicate()
256
    {
257
        EnsureConnectionOpen();
9✔
258

259
        // We're sure that the connectionString is not null because we previously checked the connection was open
260
        if (!ParsedConnection!.InMemory)
6✔
261
        {
262
            throw new NotSupportedException("Duplication of the connection is only supported for in-memory connections.");
3✔
263
        }
264

265
        var duplicatedConnection = new DuckDBConnection(ConnectionString)
3✔
266
        {
3✔
267
            parsedConnection = ParsedConnection,
3✔
268
            inMemoryDuplication = true,
3✔
269
            connectionReference = connectionReference,
3✔
270
        };
3✔
271

272
        return duplicatedConnection;
3✔
273
    }
274

275
    public override DataTable GetSchema() =>
276
        GetSchema(DbMetaDataCollectionNames.MetaDataCollections);
3✔
277

278
    public override DataTable GetSchema(string collectionName) =>
279
        GetSchema(collectionName, null);
45✔
280

281
    public override DataTable GetSchema(string collectionName, string?[]? restrictionValues) =>
282
        DuckDBSchema.GetSchema(this, collectionName, restrictionValues);
72✔
283

284
    public DuckDBQueryProgress GetQueryProgress()
285
    {
286
        EnsureConnectionOpen();
×
287
        return NativeMethods.Startup.DuckDBQueryProgress(NativeConnection);
×
288
    }
289
}
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

© 2025 Coveralls, Inc