diff --git a/src/dbup-postgresql/DataSourceConnectionFactory.cs b/src/dbup-postgresql/DataSourceConnectionFactory.cs new file mode 100644 index 0000000..56c0a12 --- /dev/null +++ b/src/dbup-postgresql/DataSourceConnectionFactory.cs @@ -0,0 +1,93 @@ +using System; +using System.Data; +using DbUp.Engine.Output; +using DbUp.Engine.Transactions; +using Npgsql; + +namespace DbUp.Postgresql; + +/// +/// A connection factory that uses Npgsql's data source pattern to create PostgreSQL database connections. +/// This factory provides better performance and resource management compared to traditional connection strings +/// by reusing configured data sources and connection pooling. +/// +internal class DataSourceConnectionFactory : IConnectionFactory +{ + + private readonly NpgsqlDataSource dataSource; + + /// + /// Initializes a new instance of the class. + /// + /// The PostgreSQL connection string used to configure the data source. + /// Additional connection options including SSL certificate configuration. + /// Thrown when or is null. + /// Thrown when is empty or invalid. + public DataSourceConnectionFactory(string connectionString, PostgresqlConnectionOptions connectionOptions) + { + if (connectionString == null) + { + throw new ArgumentNullException(nameof(connectionString)); + } + if (string.IsNullOrEmpty(connectionString)) + { + throw new ArgumentException("Connection string cannot be empty.", nameof(connectionString)); + } + if (connectionOptions == null) + { + throw new ArgumentNullException(nameof(connectionOptions)); + } + var builder = new NpgsqlDataSourceBuilder(connectionString); + +#if NET8_0_OR_GREATER + // Use the new SSL authentication callback API for .NET 8.0 with Npgsql 9 + if (connectionOptions.ClientCertificate != null || connectionOptions.UserCertificateValidationCallback != null) + { + builder.UseSslClientAuthenticationOptionsCallback(options => + { + if (connectionOptions.ClientCertificate != null) + { + options.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection + { + connectionOptions.ClientCertificate + }; + } + if (connectionOptions.UserCertificateValidationCallback != null) + { + options.RemoteCertificateValidationCallback = connectionOptions.UserCertificateValidationCallback; + } + }); + } +#else + // Use legacy API for netstandard2.0 with Npgsql 8 + if (connectionOptions.ClientCertificate != null) + { + builder.UseClientCertificate(connectionOptions.ClientCertificate); + } + if (connectionOptions.UserCertificateValidationCallback != null) + { + builder.UseUserCertificateValidationCallback(connectionOptions.UserCertificateValidationCallback); + } +#endif + dataSource = builder.Build(); + } + + /// + /// Creates a new database connection using the configured data source. + /// + /// The upgrade log for recording connection-related messages. This parameter is not used in this implementation. + /// The database connection manager. This parameter is not used in this implementation. + /// A new instance ready for use. + /// + /// The returned connection is not automatically opened. The caller is responsible for opening and properly disposing of the connection. + /// The connection benefits from the data source's connection pooling and configuration reuse. + /// + public IDbConnection CreateConnection(IUpgradeLog upgradeLog, DatabaseConnectionManager databaseConnectionManager) => dataSource.CreateConnection(); + + /// + /// Creates a new database connection using the configured data source. + /// Simpler implementation of for internal use. + /// + /// A new instance ready for use. + internal NpgsqlConnection CreateConnection() => dataSource.CreateConnection(); +} diff --git a/src/dbup-postgresql/PostgresqlConnectionManager.cs b/src/dbup-postgresql/PostgresqlConnectionManager.cs index b30bea3..14eeea1 100644 --- a/src/dbup-postgresql/PostgresqlConnectionManager.cs +++ b/src/dbup-postgresql/PostgresqlConnectionManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; using DbUp.Engine.Transactions; @@ -15,7 +16,7 @@ public class PostgresqlConnectionManager : DatabaseConnectionManager /// Disallow single quotes to be escaped with a backslash (\') /// public bool StandardConformingStrings { get; set; } = true; - + /// /// Creates a new PostgreSQL database connection. /// @@ -44,14 +45,7 @@ public PostgresqlConnectionManager(string connectionString, X509Certificate2 cer /// The PostgreSQL connection string. /// Custom options to apply on the created connection public PostgresqlConnectionManager(string connectionString, PostgresqlConnectionOptions connectionOptions) - : base(new DelegateConnectionFactory(l => - { - NpgsqlConnection databaseConnection = new NpgsqlConnection(connectionString); - databaseConnection.ApplyConnectionOptions(connectionOptions); - - return databaseConnection; - } - )) + : base(new DataSourceConnectionFactory(connectionString, connectionOptions)) { } @@ -78,4 +72,4 @@ public override IEnumerable SplitScriptIntoCommands(string scriptContent return scriptStatements; } -} \ No newline at end of file +} diff --git a/src/dbup-postgresql/PostgresqlExtensions.cs b/src/dbup-postgresql/PostgresqlExtensions.cs index 5aef97b..f6ed796 100644 --- a/src/dbup-postgresql/PostgresqlExtensions.cs +++ b/src/dbup-postgresql/PostgresqlExtensions.cs @@ -194,42 +194,40 @@ PostgresqlConnectionOptions connectionOptions logger.LogDebug("Master ConnectionString => {0}", logMasterConnectionStringBuilder.ConnectionString); - using (var connection = new NpgsqlConnection(masterConnectionStringBuilder.ConnectionString)) + var factory = new DataSourceConnectionFactory(masterConnectionStringBuilder.ConnectionString, connectionOptions); + using var connection = factory.CreateConnection(); + connection.Open(); + + var sqlCommandText = + $"SELECT case WHEN oid IS NOT NULL THEN 1 ELSE 0 end FROM pg_database WHERE datname = '{databaseName}' limit 1;"; + + // check to see if the database already exists.. + using (var command = new NpgsqlCommand(sqlCommandText, connection) + { + CommandType = CommandType.Text + }) { - connection.ApplyConnectionOptions(connectionOptions); - connection.Open(); + var results = Convert.ToInt32(command.ExecuteScalar()); - var sqlCommandText = - $@"SELECT case WHEN oid IS NOT NULL THEN 1 ELSE 0 end FROM pg_database WHERE datname = '{databaseName}' limit 1;"; - - // check to see if the database already exists.. - using (var command = new NpgsqlCommand(sqlCommandText, connection) - { - CommandType = CommandType.Text - }) + // if the database exists, we're done here... + if (results == 1) { - var results = Convert.ToInt32(command.ExecuteScalar()); - - // if the database exists, we're done here... - if (results == 1) - { - return; - } + return; } + } - sqlCommandText = $"create database \"{databaseName}\";"; - - // Create the database... - using (var command = new NpgsqlCommand(sqlCommandText, connection) - { - CommandType = CommandType.Text - }) - { - command.ExecuteNonQuery(); - } + sqlCommandText = $"create database \"{databaseName}\";"; - logger.LogInformation(@"Created database {0}", databaseName); + // Create the database... + using (var command = new NpgsqlCommand(sqlCommandText, connection) + { + CommandType = CommandType.Text + }) + { + command.ExecuteNonQuery(); } + + logger.LogInformation(@"Created database {0}", databaseName); } /// @@ -244,16 +242,4 @@ public static UpgradeEngineBuilder JournalToPostgresqlTable(this UpgradeEngineBu builder.Configure(c => c.Journal = new PostgresqlTableJournal(() => c.ConnectionManager, () => c.Log, schema, table)); return builder; } - - internal static void ApplyConnectionOptions(this NpgsqlConnection connection, PostgresqlConnectionOptions connectionOptions) - { - connection.SslClientAuthenticationOptionsCallback = options => - { - if (connectionOptions?.ClientCertificate != null) - options.ClientCertificates = new X509Certificate2Collection(connectionOptions.ClientCertificate); - - if (connectionOptions?.UserCertificateValidationCallback != null) - options.RemoteCertificateValidationCallback = connectionOptions.UserCertificateValidationCallback; - }; - } } diff --git a/src/dbup-postgresql/dbup-postgresql.csproj b/src/dbup-postgresql/dbup-postgresql.csproj index f121447..e6df8c2 100644 --- a/src/dbup-postgresql/dbup-postgresql.csproj +++ b/src/dbup-postgresql/dbup-postgresql.csproj @@ -6,7 +6,7 @@ DbUp Contributors DbUp Copyright © DbUp Contributors 2015 - net8.0 + netstandard2.0;net8.0 dbup-postgresql DbUp.Postgresql dbup-postgresql @@ -24,7 +24,14 @@ - + + + + + + + +