Skip to content

Commit b19d9f7

Browse files
authored
feat: Multitarget support for Npgsql 8 and 9 for netstandard and net8 (#34)
* feat: Multitarget support for Npgsql 8 and 9 for netstandard and net8 * fix: Update `DataSourceConnectionFactory` to use internal class visibility and adopt consistent naming for private fields --------- Co-authored-by: Egor Shokurov <[email protected]> Co-authored-by: shokurov <[email protected]> +semver:minor
1 parent 3eed659 commit b19d9f7

File tree

4 files changed

+134
-54
lines changed

4 files changed

+134
-54
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using System;
2+
using System.Data;
3+
using DbUp.Engine.Output;
4+
using DbUp.Engine.Transactions;
5+
using Npgsql;
6+
7+
namespace DbUp.Postgresql;
8+
9+
/// <summary>
10+
/// A connection factory that uses Npgsql's data source pattern to create PostgreSQL database connections.
11+
/// This factory provides better performance and resource management compared to traditional connection strings
12+
/// by reusing configured data sources and connection pooling.
13+
/// </summary>
14+
internal class DataSourceConnectionFactory : IConnectionFactory
15+
{
16+
17+
private readonly NpgsqlDataSource dataSource;
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="DataSourceConnectionFactory"/> class.
21+
/// </summary>
22+
/// <param name="connectionString">The PostgreSQL connection string used to configure the data source.</param>
23+
/// <param name="connectionOptions">Additional connection options including SSL certificate configuration.</param>
24+
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="connectionString"/> or <paramref name="connectionOptions"/> is null.</exception>
25+
/// <exception cref="System.ArgumentException">Thrown when <paramref name="connectionString"/> is empty or invalid.</exception>
26+
public DataSourceConnectionFactory(string connectionString, PostgresqlConnectionOptions connectionOptions)
27+
{
28+
if (connectionString == null)
29+
{
30+
throw new ArgumentNullException(nameof(connectionString));
31+
}
32+
if (string.IsNullOrEmpty(connectionString))
33+
{
34+
throw new ArgumentException("Connection string cannot be empty.", nameof(connectionString));
35+
}
36+
if (connectionOptions == null)
37+
{
38+
throw new ArgumentNullException(nameof(connectionOptions));
39+
}
40+
var builder = new NpgsqlDataSourceBuilder(connectionString);
41+
42+
#if NET8_0_OR_GREATER
43+
// Use the new SSL authentication callback API for .NET 8.0 with Npgsql 9
44+
if (connectionOptions.ClientCertificate != null || connectionOptions.UserCertificateValidationCallback != null)
45+
{
46+
builder.UseSslClientAuthenticationOptionsCallback(options =>
47+
{
48+
if (connectionOptions.ClientCertificate != null)
49+
{
50+
options.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection
51+
{
52+
connectionOptions.ClientCertificate
53+
};
54+
}
55+
if (connectionOptions.UserCertificateValidationCallback != null)
56+
{
57+
options.RemoteCertificateValidationCallback = connectionOptions.UserCertificateValidationCallback;
58+
}
59+
});
60+
}
61+
#else
62+
// Use legacy API for netstandard2.0 with Npgsql 8
63+
if (connectionOptions.ClientCertificate != null)
64+
{
65+
builder.UseClientCertificate(connectionOptions.ClientCertificate);
66+
}
67+
if (connectionOptions.UserCertificateValidationCallback != null)
68+
{
69+
builder.UseUserCertificateValidationCallback(connectionOptions.UserCertificateValidationCallback);
70+
}
71+
#endif
72+
dataSource = builder.Build();
73+
}
74+
75+
/// <summary>
76+
/// Creates a new database connection using the configured data source.
77+
/// </summary>
78+
/// <param name="upgradeLog">The upgrade log for recording connection-related messages. This parameter is not used in this implementation.</param>
79+
/// <param name="databaseConnectionManager">The database connection manager. This parameter is not used in this implementation.</param>
80+
/// <returns>A new <see cref="IDbConnection"/> instance ready for use.</returns>
81+
/// <remarks>
82+
/// The returned connection is not automatically opened. The caller is responsible for opening and properly disposing of the connection.
83+
/// The connection benefits from the data source's connection pooling and configuration reuse.
84+
/// </remarks>
85+
public IDbConnection CreateConnection(IUpgradeLog upgradeLog, DatabaseConnectionManager databaseConnectionManager) => dataSource.CreateConnection();
86+
87+
/// <summary>
88+
/// Creates a new database connection using the configured data source.
89+
/// Simpler implementation of <see cref="CreateConnection(IUpgradeLog, DatabaseConnectionManager)"/> for internal use.
90+
/// </summary>
91+
/// <returns>A new <see cref="IDbConnection"/> instance ready for use.</returns>
92+
internal NpgsqlConnection CreateConnection() => dataSource.CreateConnection();
93+
}

src/dbup-postgresql/PostgresqlConnectionManager.cs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Security.Cryptography.X509Certificates;
45
using DbUp.Engine.Transactions;
@@ -15,7 +16,7 @@ public class PostgresqlConnectionManager : DatabaseConnectionManager
1516
/// Disallow single quotes to be escaped with a backslash (\')
1617
/// </summary>
1718
public bool StandardConformingStrings { get; set; } = true;
18-
19+
1920
/// <summary>
2021
/// Creates a new PostgreSQL database connection.
2122
/// </summary>
@@ -44,14 +45,7 @@ public PostgresqlConnectionManager(string connectionString, X509Certificate2 cer
4445
/// <param name="connectionString">The PostgreSQL connection string.</param>
4546
/// <param name="connectionOptions">Custom options to apply on the created connection</param>
4647
public PostgresqlConnectionManager(string connectionString, PostgresqlConnectionOptions connectionOptions)
47-
: base(new DelegateConnectionFactory(l =>
48-
{
49-
NpgsqlConnection databaseConnection = new NpgsqlConnection(connectionString);
50-
databaseConnection.ApplyConnectionOptions(connectionOptions);
51-
52-
return databaseConnection;
53-
}
54-
))
48+
: base(new DataSourceConnectionFactory(connectionString, connectionOptions))
5549
{
5650
}
5751

@@ -78,4 +72,4 @@ public override IEnumerable<string> SplitScriptIntoCommands(string scriptContent
7872

7973
return scriptStatements;
8074
}
81-
}
75+
}

src/dbup-postgresql/PostgresqlExtensions.cs

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -194,42 +194,40 @@ PostgresqlConnectionOptions connectionOptions
194194

195195
logger.LogDebug("Master ConnectionString => {0}", logMasterConnectionStringBuilder.ConnectionString);
196196

197-
using (var connection = new NpgsqlConnection(masterConnectionStringBuilder.ConnectionString))
197+
var factory = new DataSourceConnectionFactory(masterConnectionStringBuilder.ConnectionString, connectionOptions);
198+
using var connection = factory.CreateConnection();
199+
connection.Open();
200+
201+
var sqlCommandText =
202+
$"SELECT case WHEN oid IS NOT NULL THEN 1 ELSE 0 end FROM pg_database WHERE datname = '{databaseName}' limit 1;";
203+
204+
// check to see if the database already exists..
205+
using (var command = new NpgsqlCommand(sqlCommandText, connection)
206+
{
207+
CommandType = CommandType.Text
208+
})
198209
{
199-
connection.ApplyConnectionOptions(connectionOptions);
200-
connection.Open();
210+
var results = Convert.ToInt32(command.ExecuteScalar());
201211

202-
var sqlCommandText =
203-
$@"SELECT case WHEN oid IS NOT NULL THEN 1 ELSE 0 end FROM pg_database WHERE datname = '{databaseName}' limit 1;";
204-
205-
// check to see if the database already exists..
206-
using (var command = new NpgsqlCommand(sqlCommandText, connection)
207-
{
208-
CommandType = CommandType.Text
209-
})
212+
// if the database exists, we're done here...
213+
if (results == 1)
210214
{
211-
var results = Convert.ToInt32(command.ExecuteScalar());
212-
213-
// if the database exists, we're done here...
214-
if (results == 1)
215-
{
216-
return;
217-
}
215+
return;
218216
}
217+
}
219218

220-
sqlCommandText = $"create database \"{databaseName}\";";
221-
222-
// Create the database...
223-
using (var command = new NpgsqlCommand(sqlCommandText, connection)
224-
{
225-
CommandType = CommandType.Text
226-
})
227-
{
228-
command.ExecuteNonQuery();
229-
}
219+
sqlCommandText = $"create database \"{databaseName}\";";
230220

231-
logger.LogInformation(@"Created database {0}", databaseName);
221+
// Create the database...
222+
using (var command = new NpgsqlCommand(sqlCommandText, connection)
223+
{
224+
CommandType = CommandType.Text
225+
})
226+
{
227+
command.ExecuteNonQuery();
232228
}
229+
230+
logger.LogInformation(@"Created database {0}", databaseName);
233231
}
234232

235233
/// <summary>
@@ -244,16 +242,4 @@ public static UpgradeEngineBuilder JournalToPostgresqlTable(this UpgradeEngineBu
244242
builder.Configure(c => c.Journal = new PostgresqlTableJournal(() => c.ConnectionManager, () => c.Log, schema, table));
245243
return builder;
246244
}
247-
248-
internal static void ApplyConnectionOptions(this NpgsqlConnection connection, PostgresqlConnectionOptions connectionOptions)
249-
{
250-
connection.SslClientAuthenticationOptionsCallback = options =>
251-
{
252-
if (connectionOptions?.ClientCertificate != null)
253-
options.ClientCertificates = new X509Certificate2Collection(connectionOptions.ClientCertificate);
254-
255-
if (connectionOptions?.UserCertificateValidationCallback != null)
256-
options.RemoteCertificateValidationCallback = connectionOptions.UserCertificateValidationCallback;
257-
};
258-
}
259245
}

src/dbup-postgresql/dbup-postgresql.csproj

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<Company>DbUp Contributors</Company>
77
<Product>DbUp</Product>
88
<Copyright>Copyright © DbUp Contributors 2015</Copyright>
9-
<TargetFramework>net8.0</TargetFramework>
9+
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
1010
<AssemblyName>dbup-postgresql</AssemblyName>
1111
<RootNamespace>DbUp.Postgresql</RootNamespace>
1212
<PackageId>dbup-postgresql</PackageId>
@@ -24,7 +24,14 @@
2424

2525
<ItemGroup>
2626
<PackageReference Include="dbup-core" Version="6.0.4" />
27-
<PackageReference Include="Npgsql" Version="9.0.2" />
27+
</ItemGroup>
28+
29+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
30+
<PackageReference Include="Npgsql" Version="8.0.7" />
31+
</ItemGroup>
32+
33+
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
34+
<PackageReference Include="Npgsql" Version="9.0.3" />
2835
</ItemGroup>
2936

3037
<ItemGroup>

0 commit comments

Comments
 (0)