diff --git a/postgresql/config.go b/postgresql/config.go index 371831b9..3a57db85 100644 --- a/postgresql/config.go +++ b/postgresql/config.go @@ -25,6 +25,8 @@ const ( featureReplication featureExtension featurePrivileges + featureForceDropDatabase + featurePid ) type dbRegistryEntry struct { @@ -65,6 +67,14 @@ var ( // We do not support postgresql_grant and postgresql_default_privileges // for Postgresql < 9. featurePrivileges: semver.MustParseRange(">=9.0.0"), + + // DROP DATABASE WITH FORCE + // for Postgresql >= 13 + featureForceDropDatabase: semver.MustParseRange(">=13.0.0"), + + // Column procpid was replaced by pid in pg_stat_activity + // for Postgresql >= 9.2 and above + featurePid: semver.MustParseRange(">=9.2.0"), } ) diff --git a/postgresql/resource_postgresql_database.go b/postgresql/resource_postgresql_database.go index a61c1178..b21c7d83 100644 --- a/postgresql/resource_postgresql_database.go +++ b/postgresql/resource_postgresql_database.go @@ -233,6 +233,7 @@ func resourcePostgreSQLDatabaseDelete(d *schema.ResourceData, meta interface{}) currentUser := c.config.getDatabaseUsername() owner := d.Get(dbOwnerAttr).(string) + var dropWithForce string var err error if owner != "" { // Needed in order to set the owner of the db if the connection user is not a @@ -263,7 +264,18 @@ func resourcePostgreSQLDatabaseDelete(d *schema.ResourceData, meta interface{}) return err } - sql := fmt.Sprintf("DROP DATABASE %s", pq.QuoteIdentifier(dbName)) + // Terminate all active connections and block new one + if err := terminateBConnections(c, dbName); err != nil { + return err + } + + // Drop with force only for psql 13+ + if c.featureSupported(featureForceDropDatabase) { + dropWithForce = "WITH ( FORCE )" + } + + sql := fmt.Sprintf("DROP DATABASE %s %s", pq.QuoteIdentifier(dbName), dropWithForce) + if _, err := c.DB().Exec(sql); err != nil { return fmt.Errorf("Error dropping database: %w", err) } @@ -548,3 +560,25 @@ func doSetDBIsTemplate(c *Client, dbName string, isTemplate bool) error { return nil } + +func terminateBConnections(c *Client, dbName string) error { + var terminateSql string + + if c.featureSupported(featureDBAllowConnections) { + alterSql := fmt.Sprintf("ALTER DATABASE %s ALLOW_CONNECTIONS false", pq.QuoteIdentifier(dbName)) + + if _, err := c.DB().Exec(alterSql); err != nil { + return fmt.Errorf("Error blocking connections to database: %w", err) + } + } + pid := "procpid" + if c.featureSupported(featurePid) { + pid = "pid" + } + terminateSql = fmt.Sprintf("SELECT pg_terminate_backend(%s) FROM pg_stat_activity WHERE datname = '%s' AND %s <> pg_backend_pid()", pid, dbName, pid) + if _, err := c.DB().Exec(terminateSql); err != nil { + return fmt.Errorf("Error terminating database connections: %w", err) + } + + return nil +}