diff --git a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs index 00d5ce0..d7c8f73 100644 --- a/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs +++ b/src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs @@ -13,11 +13,17 @@ public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Bui public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString, string schema, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } public static DbUp.Builder.UpgradeEngineBuilder PostgresqlDatabase(this DbUp.Builder.SupportedDatabases supported, string connectionString, string schema, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForDropDatabase supported, string connectionString) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForDropDatabase supported, string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForDropDatabase supported, string connectionString, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForDropDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForEnsureDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForDropDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } + public static void PostgresqlDatabase(this DbUp.SupportedDatabasesForDropDatabase supported, string connectionString, DbUp.Engine.Output.IUpgradeLog logger, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { } } namespace DbUp.Postgresql { diff --git a/src/dbup-postgresql/PostgresqlExtensions.cs b/src/dbup-postgresql/PostgresqlExtensions.cs index f6ed796..921df87 100644 --- a/src/dbup-postgresql/PostgresqlExtensions.cs +++ b/src/dbup-postgresql/PostgresqlExtensions.cs @@ -230,6 +230,164 @@ PostgresqlConnectionOptions connectionOptions logger.LogInformation(@"Created database {0}", databaseName); } + /// + /// Drops the database specified in the connection string if it exists. + /// + /// Fluent helper type. + /// The connection string. + /// + public static void PostgresqlDatabase(this SupportedDatabasesForDropDatabase supported, string connectionString) + { + PostgresqlDatabase(supported, connectionString, new ConsoleUpgradeLog()); + } + + /// + /// Drops the database specified in the connection string if it exists using SSL for the connection. + /// + /// Fluent helper type. + /// The connection string. + /// Certificate for securing connection. + /// + public static void PostgresqlDatabase(this SupportedDatabasesForDropDatabase supported, string connectionString, X509Certificate2 certificate) + { + PostgresqlDatabase(supported, connectionString, new ConsoleUpgradeLog(), certificate); + } + + /// + /// Drops the database specified in the connection string if it exists using SSL for the connection. + /// + /// Fluent helper type. + /// The connection string. + /// Connection SSL to customize SSL behaviour + /// + public static void PostgresqlDatabase(this SupportedDatabasesForDropDatabase supported, string connectionString, PostgresqlConnectionOptions connectionOptions) + { + PostgresqlDatabase(supported, connectionString, new ConsoleUpgradeLog(), connectionOptions); + } + + /// + /// Drops that the database specified in the connection string if it exists. + /// + /// Fluent helper type. + /// The connection string. + /// The used to record actions. + /// + public static void PostgresqlDatabase(this SupportedDatabasesForDropDatabase supported, string connectionString, IUpgradeLog logger) + { + PostgresqlDatabase(supported, connectionString, logger, new PostgresqlConnectionOptions()); + } + + public static void PostgresqlDatabase(this SupportedDatabasesForDropDatabase supported, string connectionString, IUpgradeLog logger, X509Certificate2 certificate) + { + var options = new PostgresqlConnectionOptions + { + ClientCertificate = certificate + }; + PostgresqlDatabase(supported, connectionString, logger, options); + } + + public static void PostgresqlDatabase( + this SupportedDatabasesForDropDatabase supported, + string connectionString, + IUpgradeLog logger, + PostgresqlConnectionOptions connectionOptions + ) + { + if (supported == null) throw new ArgumentNullException("supported"); + + if (string.IsNullOrEmpty(connectionString) || connectionString.Trim() == string.Empty) + { + throw new ArgumentNullException("connectionString"); + } + + if (logger == null) throw new ArgumentNullException("logger"); + + var masterConnectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString); + + var databaseName = masterConnectionStringBuilder.Database; + + if (string.IsNullOrEmpty(databaseName) || databaseName.Trim() == string.Empty) + { + throw new InvalidOperationException("The connection string does not specify a database name."); + } + + if (databaseName == connectionOptions.MasterDatabaseName) + { + throw new InvalidOperationException("Database in connection string needs to be different than the master database in PostgresqlConnectionOptions."); + } + + masterConnectionStringBuilder.Database = connectionOptions.MasterDatabaseName; + masterConnectionStringBuilder.SearchPath = "public"; + + var logMasterConnectionStringBuilder = new NpgsqlConnectionStringBuilder(masterConnectionStringBuilder.ConnectionString); + if (!string.IsNullOrEmpty(logMasterConnectionStringBuilder.Password)) + { + logMasterConnectionStringBuilder.Password = "******"; + } + + logger.LogDebug("Master ConnectionString => {0}", logMasterConnectionStringBuilder.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 + }) + { + var results = Convert.ToInt32(command.ExecuteScalar()); + + // if the database does not exist, we're done here... + if (results == 0) + { + logger.LogInformation(@"Database {0} does not exist. Skipping delete operation.", databaseName); + return; + } + } + + // prevent new connections to the database + sqlCommandText = $"alter database \"{databaseName}\" with ALLOW_CONNECTIONS false;"; + using (var command = new NpgsqlCommand(sqlCommandText, connection) + { + CommandType = CommandType.Text + }) + { + command.ExecuteNonQuery(); + } + + logger.LogInformation(@"Stopped connections for database {0}.", databaseName); + + // terminate all existing connections to the database + sqlCommandText = $"select pg_terminate_backend(pg_stat_activity.pid) from pg_stat_activity where pg_stat_activity.datname = \'{databaseName}\';"; + using (var command = new NpgsqlCommand(sqlCommandText, connection) + { + CommandType = CommandType.Text + }) + { + command.ExecuteNonQuery(); + } + + logger.LogInformation(@"Closed existing connections for database {0}.", databaseName); + + sqlCommandText = $"drop database \"{databaseName}\";"; + + // drop the database + using (var command = new NpgsqlCommand(sqlCommandText, connection) + { + CommandType = CommandType.Text + }) + { + command.ExecuteNonQuery(); + } + + logger.LogInformation(@"Dropped database {0}.", databaseName); + } + /// /// Tracks the list of executed scripts in a PostgreSQL table. ///