Commit c13ee0cc authored by Marco Pivetta's avatar Marco Pivetta

Merge branch 'fix/#2636-fix-dropping-in-use-databases-on-sql-server-and-oracledb' into 2.5

Backport #2636 to 2.5.x
parents d153ef03 50937291
......@@ -19,9 +19,10 @@
namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\DBALException;
class SQLSrvException extends DBALException
use Doctrine\DBAL\Driver\AbstractDriverException;
class SQLSrvException extends AbstractDriverException
{
/**
* Helper method to turn sql server errors into exception.
......@@ -32,13 +33,24 @@ class SQLSrvException extends DBALException
{
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
$message = "";
$sqlState = null;
$errorCode = null;
foreach ($errors as $error) {
$message .= "SQLSTATE [".$error['SQLSTATE'].", ".$error['code']."]: ". $error['message']."\n";
if (null === $sqlState) {
$sqlState = $error['SQLSTATE'];
}
if (null === $errorCode) {
$errorCode = $error['code'];
}
}
if ( ! $message) {
$message = "SQL Server error occurred but no error message was retrieved from driver.";
}
return new self(rtrim($message));
return new self(rtrim($message), $sqlState, $errorCode);
}
}
......@@ -19,6 +19,8 @@
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Types\Type;
/**
......@@ -31,6 +33,34 @@ use Doctrine\DBAL\Types\Type;
*/
class OracleSchemaManager extends AbstractSchemaManager
{
/**
* {@inheritdoc}
*/
public function dropDatabase($database)
{
try {
parent::dropDatabase($database);
} catch (DBALException $exception) {
$exception = $exception->getPrevious();
if (! $exception instanceof DriverException) {
throw $exception;
}
// If we have a error code 1940 (ORA-01940), 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->getErrorCode() !== 1940) {
throw $exception;
}
$this->killUserSessions($database);
parent::dropDatabase($database);
}
}
/**
* {@inheritdoc}
*/
......@@ -305,7 +335,7 @@ class OracleSchemaManager extends AbstractSchemaManager
$query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
$this->_conn->executeUpdate($query);
$query = 'GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE, CREATE SEQUENCE, CREATE TRIGGER TO ' . $username;
$query = 'GRANT DBA TO ' . $username;
$this->_conn->executeUpdate($query);
return true;
......@@ -354,4 +384,42 @@ class OracleSchemaManager extends AbstractSchemaManager
return $identifier;
}
/**
* Kills sessions connected with the given user.
*
* This is useful to force DROP USER operations which could fail because of active user sessions.
*
* @param string $user The name of the user to kill sessions for.
*
* @return void
*/
private function killUserSessions($user)
{
$sql = <<<SQL
SELECT
s.sid,
s.serial#
FROM
gv\$session s,
gv\$process p
WHERE
s.username = ?
AND p.addr(+) = s.paddr
SQL;
$activeUserSessions = $this->_conn->fetchAll($sql, array(strtoupper($user)));
foreach ($activeUserSessions as $activeUserSession) {
$activeUserSession = array_change_key_case($activeUserSession, \CASE_LOWER);
$this->_execSql(
sprintf(
"ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE",
$activeUserSession['sid'],
$activeUserSession['serial#']
)
);
}
}
}
......@@ -19,7 +19,8 @@
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Driver\SQLSrv\SQLSrvException;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Types\Type;
/**
......@@ -34,6 +35,34 @@ use Doctrine\DBAL\Types\Type;
*/
class SQLServerSchemaManager extends AbstractSchemaManager
{
/**
* {@inheritdoc}
*/
public function dropDatabase($database)
{
try {
parent::dropDatabase($database);
} catch (DBALException $exception) {
$exception = $exception->getPrevious();
if (! $exception instanceof DriverException) {
throw $exception;
}
// If we have a error code 3702, 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->getErrorCode() !== 3702) {
throw $exception;
}
$this->closeActiveDatabaseConnections($database);
parent::dropDatabase($database);
}
}
/**
* {@inheritdoc}
*/
......@@ -212,7 +241,7 @@ class SQLServerSchemaManager extends AbstractSchemaManager
} else {
throw $e;
}
} catch (SQLSrvException $e) {
} catch (DBALException $e) {
if (strpos($e->getMessage(), 'SQLSTATE [01000, 15472]') === 0) {
return array();
} else {
......@@ -258,4 +287,25 @@ class SQLServerSchemaManager extends AbstractSchemaManager
WHERE Col.[Name] = " . $this->_conn->quote($column) ." AND Tab.[Name] = " . $this->_conn->quote($table) . "
ORDER BY Col.[Name]";
}
/**
* Closes 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 void
*/
private function closeActiveDatabaseConnections($database)
{
$database = new Identifier($database);
$this->_execSql(
sprintf(
'ALTER DATABASE %s SET SINGLE_USER WITH ROLLBACK IMMEDIATE',
$database->getQuotedName($this->_platform)
)
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment