Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DBAL-1220] Fix dropping database with active connection on PostgreSQL #849

Merged
merged 1 commit into from
Aug 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lib/Doctrine/DBAL/Platforms/PostgreSQL92Platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,12 @@ protected function initializeDoctrineTypeMappings()
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'";
}
}
28 changes: 28 additions & 0 deletions lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,34 @@ public function getCreateDatabaseSQL($name)
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}
*/
Expand Down
28 changes: 28 additions & 0 deletions lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

namespace Doctrine\DBAL\Schema;

use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Types\Type;

/**
Expand Down Expand Up @@ -100,6 +101,33 @@ public function determineExistingSchemaSearchPaths()
});
}

/**
* {@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}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,36 @@ protected function setUp()
$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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,32 @@ public function testCreateAndDropDatabase()
$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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,4 +792,26 @@ public function testInitializesTsvectorTypeMapping()
$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')
);
}
}
11 changes: 11 additions & 0 deletions tests/Doctrine/Tests/DBAL/Platforms/PostgreSQL92PlatformTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,15 @@ public function testInitializesJsonTypeMapping()
$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')
);
}
}