diff --git a/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php b/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php index dcb5ebe9c7fc6676d6029b0fd62498c74cff56f3..19cf803563055e40d87d1456fd7c8bf26224eaa7 100644 --- a/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php +++ b/lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php @@ -72,4 +72,12 @@ class PostgreSQL92Platform extends PostgreSQL91Platform parent::initializeDoctrineTypeMappings(); $this->doctrineTypeMapping['json'] = 'json_array'; } + + /** + * {@inheritdoc} + */ + public function getCloseActiveDatabaseConnectionsSQL($database) + { + return "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$database'"; + } } diff --git a/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php b/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php index 8b3b284c932c40f3e3a28958e295b7feeb705579..8c8caa0a85513ce8bfad35d3bd668db93914a83f 100644 --- a/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php +++ b/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php @@ -420,6 +420,34 @@ class PostgreSqlPlatform extends AbstractPlatform return 'CREATE DATABASE ' . $name; } + /** + * Returns the SQL statement for disallowing new connections on the given database. + * + * This is useful to force DROP DATABASE operations which could fail because of active connections. + * + * @param string $database The name of the database to disallow new connections for. + * + * @return string + */ + public function getDisallowDatabaseConnectionsSQL($database) + { + return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = '$database'"; + } + + /** + * Returns the SQL statement for closing currently active connections on the given database. + * + * This is useful to force DROP DATABASE operations which could fail because of active connections. + * + * @param string $database The name of the database to close currently active connections for. + * + * @return string + */ + public function getCloseActiveDatabaseConnectionsSQL($database) + { + return "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = '$database'"; + } + /** * {@inheritDoc} */ diff --git a/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php b/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php index 7d9e3c801473de7bf3521e1df30945173d615a7b..6fc17583bd304d3c2859805462119b97dc5781ef 100644 --- a/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php +++ b/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php @@ -19,6 +19,7 @@ namespace Doctrine\DBAL\Schema; +use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Types\Type; /** @@ -100,6 +101,33 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager }); } + /** + * {@inheritdoc} + */ + public function dropDatabase($database) + { + try { + parent::dropDatabase($database); + } catch (DriverException $exception) { + // If we have a SQLSTATE 55006, the drop database operation failed + // because of active connections on the database. + // To force dropping the database, we first have to close all active connections + // on that database and issue the drop database operation again. + if ($exception->getSQLState() !== '55006') { + throw $exception; + } + + $this->_execSql( + array( + $this->_platform->getDisallowDatabaseConnectionsSQL($database), + $this->_platform->getCloseActiveDatabaseConnectionsSQL($database), + ) + ); + + parent::dropDatabase($database); + } + } + /** * {@inheritdoc} */ diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php index a2cf7311d6d5bfcb3a866a5e3686453b22199b63..3a9bf61b294e7fd642354d53dbc0e8663888746a 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -40,6 +40,36 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest $this->_sm = $this->_conn->getSchemaManager(); } + /** + * @group DBAL-1220 + */ + public function testDropsDatabaseWithActiveConnections() + { + if (! $this->_sm->getDatabasePlatform()->supportsCreateDropDatabase()) { + $this->markTestSkipped('Cannot drop Database client side with this Driver.'); + } + + $this->_sm->dropAndCreateDatabase('test_drop_database'); + + $this->assertContains('test_drop_database', $this->_sm->listDatabases()); + + $params = $this->_conn->getParams(); + $params['dbname'] = 'test_drop_database'; + + $user = isset($params['user']) ? $params['user'] : null; + $password = isset($params['password']) ? $params['password'] : null; + + $connection = $this->_conn->getDriver()->connect($params, $user, $password); + + $this->assertInstanceOf('Doctrine\DBAL\Driver\Connection', $connection); + + $this->_sm->dropDatabase('test_drop_database'); + + $this->assertNotContains('test_drop_database', $this->_sm->listDatabases()); + + unset($connection); + } + /** * @group DBAL-195 */ diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php index 67282b1c204fd7930f05bca213f0c39a620b2b67..dd03a0f98779865d65067e0b5c3bf91cc41ee82a 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SqliteSchemaManagerTest.php @@ -26,6 +26,32 @@ class SqliteSchemaManagerTest extends SchemaManagerFunctionalTestCase $this->assertEquals(false, file_exists($path)); } + /** + * @group DBAL-1220 + */ + public function testDropsDatabaseWithActiveConnections() + { + $this->_sm->dropAndCreateDatabase('test_drop_database'); + + $this->assertFileExists('test_drop_database'); + + $params = $this->_conn->getParams(); + $params['dbname'] = 'test_drop_database'; + + $user = isset($params['user']) ? $params['user'] : null; + $password = isset($params['password']) ? $params['password'] : null; + + $connection = $this->_conn->getDriver()->connect($params, $user, $password); + + $this->assertInstanceOf('Doctrine\DBAL\Driver\Connection', $connection); + + $this->_sm->dropDatabase('test_drop_database'); + + $this->assertFileNotExists('test_drop_database'); + + unset($connection); + } + public function testRenameTable() { $this->createTestTable('oldname'); diff --git a/tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php b/tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php index 58da19b7397632906c61a1813a648e4e7dbf114d..29295c25a0824ef45e5f28c14a1e478fc0c159f4 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/AbstractPostgreSqlPlatformTestCase.php @@ -792,4 +792,26 @@ abstract class AbstractPostgreSqlPlatformTestCase extends AbstractPlatformTestCa $this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('tsvector')); $this->assertEquals('text', $this->_platform->getDoctrineTypeMapping('tsvector')); } + + /** + * @group DBAL-1220 + */ + public function testReturnsDisallowDatabaseConnectionsSQL() + { + $this->assertSame( + "UPDATE pg_database SET datallowconn = 'false' WHERE datname = 'foo'", + $this->_platform->getDisallowDatabaseConnectionsSQL('foo') + ); + } + + /** + * @group DBAL-1220 + */ + public function testReturnsCloseActiveDatabaseConnectionsSQL() + { + $this->assertSame( + "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = 'foo'", + $this->_platform->getCloseActiveDatabaseConnectionsSQL('foo') + ); + } } diff --git a/tests/Doctrine/Tests/DBAL/Platforms/PostgreSQL92PlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/PostgreSQL92PlatformTest.php index 083df4beead607d81e2c947945860c237892ba9c..2ec361242b1d5992b84e44610e4a50b2d96475de 100644 --- a/tests/Doctrine/Tests/DBAL/Platforms/PostgreSQL92PlatformTest.php +++ b/tests/Doctrine/Tests/DBAL/Platforms/PostgreSQL92PlatformTest.php @@ -56,4 +56,15 @@ class PostgreSQL92PlatformTest extends AbstractPostgreSqlPlatformTestCase $this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('json')); $this->assertEquals('json_array', $this->_platform->getDoctrineTypeMapping('json')); } + + /** + * @group DBAL-1220 + */ + public function testReturnsCloseActiveDatabaseConnectionsSQL() + { + $this->assertSame( + "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'foo'", + $this->_platform->getCloseActiveDatabaseConnectionsSQL('foo') + ); + } }