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

Giorgi / DuckDB.NET / 21922970228

11 Feb 2026 09:04PM UTC coverage: 89.265% (+0.02%) from 89.25%
21922970228

push

github

Giorgi
Replace ToManagedString with custom marshallers

Introduce DuckDBOwnedStringMarshaller and
DuckDBCallerOwnedStringMarshaller to handle native string
lifetime semantics at the marshalling layer.

1195 of 1387 branches covered (86.16%)

Branch coverage included in aggregate %.

14 of 17 new or added lines in 10 files covered. (82.35%)

2314 of 2544 relevant lines covered (90.96%)

557359.58 hits per line

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

93.28
/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,896✔
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; }
80,805✔
26

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

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

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

39
    [AllowNull]
40
    [DefaultValue("")]
41
    public override string ConnectionString { get; set; }
190,128✔
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,037!
73
                                                      ?? throw new InvalidOperationException("The DuckDBConnection must be open to access the native connection.");
158,037✔
74

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

77
    public override ConnectionState State => connectionState;
258,087✔
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,938✔
87
        {
88
            throw new InvalidOperationException("Connection is already closed.");
6✔
89
        }
90

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

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

100
    public override void Open()
101
    {
102
        if (connectionState == ConnectionState.Open)
64,959✔
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,956✔
109
                                                  : connectionManager.GetConnectionReference(ParsedConnection);
64,956✔
110

111
        connectionState = ConnectionState.Open;
64,938✔
112
        OnStateChange(FromClosedToOpenEventArgs);
64,938✔
113
    }
64,938✔
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,492✔
139
    }
140

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

150
    public DuckDBAppender CreateAppender(string table) => CreateAppender(null, null, table);
177✔
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();
198✔
157

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

160
        if (!appenderState.IsSuccess())
198✔
161
        {
162
            try
163
            {
164
                DuckDBAppender.ThrowLastError(nativeAppender);
3✔
165
            }
166
            finally
167
            {
168
                nativeAppender.Close();
3✔
169
            }
3✔
170
        }
171

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

174
        string GetTableName()
175
        {
176
            return string.IsNullOrEmpty(schema) ? table : $"{schema}.{table}";
195✔
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);
9✔
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);
9✔
220
        return new DuckDBMappedAppender<T, TMap>(appender);
9✔
221
    }
222

223
    protected override void Dispose(bool disposing)
224
    {
225
        if (disposing)
64,904✔
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,902✔
230
            {
231
                Close();
64,854✔
232
            }
233
        }
234

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

238
    private void EnsureConnectionOpen([CallerMemberName] string operation = "")
239
    {
240
        if (State != ConnectionState.Open)
243✔
241
        {
242
            throw new InvalidOperationException($"{operation} requires an open connection");
6✔
243
        }
244
    }
237✔
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 TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc