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 @@ ...@@ -19,9 +19,10 @@
namespace Doctrine\DBAL\Driver\SQLSrv; 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. * Helper method to turn sql server errors into exception.
...@@ -32,13 +33,24 @@ class SQLSrvException extends DBALException ...@@ -32,13 +33,24 @@ class SQLSrvException extends DBALException
{ {
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
$message = ""; $message = "";
$sqlState = null;
$errorCode = null;
foreach ($errors as $error) { foreach ($errors as $error) {
$message .= "SQLSTATE [".$error['SQLSTATE'].", ".$error['code']."]: ". $error['message']."\n"; $message .= "SQLSTATE [".$error['SQLSTATE'].", ".$error['code']."]: ". $error['message']."\n";
if (null === $sqlState) {
$sqlState = $error['SQLSTATE'];
}
if (null === $errorCode) {
$errorCode = $error['code'];
}
} }
if ( ! $message) { if ( ! $message) {
$message = "SQL Server error occurred but no error message was retrieved from driver."; $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 @@ ...@@ -19,6 +19,8 @@
namespace Doctrine\DBAL\Schema; namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Type;
/** /**
...@@ -31,6 +33,34 @@ use Doctrine\DBAL\Types\Type; ...@@ -31,6 +33,34 @@ use Doctrine\DBAL\Types\Type;
*/ */
class OracleSchemaManager extends AbstractSchemaManager 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} * {@inheritdoc}
*/ */
...@@ -305,7 +335,7 @@ class OracleSchemaManager extends AbstractSchemaManager ...@@ -305,7 +335,7 @@ class OracleSchemaManager extends AbstractSchemaManager
$query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password; $query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
$this->_conn->executeUpdate($query); $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); $this->_conn->executeUpdate($query);
return true; return true;
...@@ -354,4 +384,42 @@ class OracleSchemaManager extends AbstractSchemaManager ...@@ -354,4 +384,42 @@ class OracleSchemaManager extends AbstractSchemaManager
return $identifier; 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 @@ ...@@ -19,7 +19,8 @@
namespace Doctrine\DBAL\Schema; namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Driver\SQLSrv\SQLSrvException; use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Type;
/** /**
...@@ -34,6 +35,34 @@ use Doctrine\DBAL\Types\Type; ...@@ -34,6 +35,34 @@ use Doctrine\DBAL\Types\Type;
*/ */
class SQLServerSchemaManager extends AbstractSchemaManager 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} * {@inheritdoc}
*/ */
...@@ -212,7 +241,7 @@ class SQLServerSchemaManager extends AbstractSchemaManager ...@@ -212,7 +241,7 @@ class SQLServerSchemaManager extends AbstractSchemaManager
} else { } else {
throw $e; throw $e;
} }
} catch (SQLSrvException $e) { } catch (DBALException $e) {
if (strpos($e->getMessage(), 'SQLSTATE [01000, 15472]') === 0) { if (strpos($e->getMessage(), 'SQLSTATE [01000, 15472]') === 0) {
return array(); return array();
} else { } else {
...@@ -258,4 +287,25 @@ class SQLServerSchemaManager extends AbstractSchemaManager ...@@ -258,4 +287,25 @@ class SQLServerSchemaManager extends AbstractSchemaManager
WHERE Col.[Name] = " . $this->_conn->quote($column) ." AND Tab.[Name] = " . $this->_conn->quote($table) . " WHERE Col.[Name] = " . $this->_conn->quote($column) ." AND Tab.[Name] = " . $this->_conn->quote($table) . "
ORDER BY Col.[Name]"; 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