Commit 43191656 authored by Benjamin Eberlei's avatar Benjamin Eberlei

Merge pull request #176 from schmittjoh/betterDriverErrors

improved exception messages when driver error occurs
parents 311bda52 9fcd78db
# Upgrade to 2.3 # Upgrade to 2.3
## Doctrine\DBAL\Connection and Doctrine\DBAL\Statement
The query related methods including but not limited to executeQuery, exec, query, and executeUpdate
now wrap the driver exceptions such as PDOException with DBALException to add more debugging
information such as the executed SQL statement, and any bound parameters.
If you want to retrieve the driver specific exception, you can retrieve it by calling the
``getPrevious()`` method on DBALException.
Before:
catch(\PDOException $ex) {
// ...
}
After:
catch(\Doctrine\DBAL\DBALException $ex) {
$pdoException = $ex->getPrevious();
// ...
}
## Doctrine\DBAL\Connection#setCharsetSQL() removed ## Doctrine\DBAL\Connection#setCharsetSQL() removed
This method only worked on MySQL and it is considered unsafe on MySQL to use SET NAMES UTF-8 instead This method only worked on MySQL and it is considered unsafe on MySQL to use SET NAMES UTF-8 instead
......
...@@ -594,7 +594,12 @@ class Connection implements DriverConnection ...@@ -594,7 +594,12 @@ class Connection implements DriverConnection
{ {
$this->connect(); $this->connect();
$stmt = new Statement($statement, $this); try {
$stmt = new Statement($statement, $this);
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, $statement);
}
$stmt->setFetchMode($this->_defaultFetchMode); $stmt->setFetchMode($this->_defaultFetchMode);
return $stmt; return $stmt;
...@@ -626,18 +631,22 @@ class Connection implements DriverConnection ...@@ -626,18 +631,22 @@ class Connection implements DriverConnection
$logger->startQuery($query, $params, $types); $logger->startQuery($query, $params, $types);
} }
if ($params) { try {
list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types); if ($params) {
list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
$stmt = $this->_conn->prepare($query); $stmt = $this->_conn->prepare($query);
if ($types) { if ($types) {
$this->_bindTypedValues($stmt, $params, $types); $this->_bindTypedValues($stmt, $params, $types);
$stmt->execute(); $stmt->execute();
} else {
$stmt->execute($params);
}
} else { } else {
$stmt->execute($params); $stmt = $this->_conn->query($query);
} }
} else { } catch (\Exception $ex) {
$stmt = $this->_conn->query($query); throw DBALException::driverExceptionDuringQuery($ex, $query, $this->resolveParams($params, $types));
} }
$stmt->setFetchMode($this->_defaultFetchMode); $stmt->setFetchMode($this->_defaultFetchMode);
...@@ -729,7 +738,12 @@ class Connection implements DriverConnection ...@@ -729,7 +738,12 @@ class Connection implements DriverConnection
$logger->startQuery($args[0]); $logger->startQuery($args[0]);
} }
$statement = call_user_func_array(array($this->_conn, 'query'), $args); try {
$statement = call_user_func_array(array($this->_conn, 'query'), $args);
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, func_get_arg(0));
}
$statement->setFetchMode($this->_defaultFetchMode); $statement->setFetchMode($this->_defaultFetchMode);
if ($logger) { if ($logger) {
...@@ -760,19 +774,23 @@ class Connection implements DriverConnection ...@@ -760,19 +774,23 @@ class Connection implements DriverConnection
$logger->startQuery($query, $params, $types); $logger->startQuery($query, $params, $types);
} }
if ($params) { try {
list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types); if ($params) {
list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
$stmt = $this->_conn->prepare($query); $stmt = $this->_conn->prepare($query);
if ($types) { if ($types) {
$this->_bindTypedValues($stmt, $params, $types); $this->_bindTypedValues($stmt, $params, $types);
$stmt->execute(); $stmt->execute();
} else {
$stmt->execute($params);
}
$result = $stmt->rowCount();
} else { } else {
$stmt->execute($params); $result = $this->_conn->exec($query);
} }
$result = $stmt->rowCount(); } catch (\Exception $ex) {
} else { throw DBALException::driverExceptionDuringQuery($ex, $query, $this->resolveParams($params, $types));
$result = $this->_conn->exec($query);
} }
if ($logger) { if ($logger) {
...@@ -797,7 +815,11 @@ class Connection implements DriverConnection ...@@ -797,7 +815,11 @@ class Connection implements DriverConnection
$logger->startQuery($statement); $logger->startQuery($statement);
} }
$result = $this->_conn->exec($statement); try {
$result = $this->_conn->exec($statement);
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, $statement);
}
if ($logger) { if ($logger) {
$logger->stopQuery(); $logger->stopQuery();
...@@ -1227,6 +1249,53 @@ class Connection implements DriverConnection ...@@ -1227,6 +1249,53 @@ class Connection implements DriverConnection
return array($value, $bindingType); return array($value, $bindingType);
} }
/**
* Resolves the parameters to a format which can be displayed.
*
* @internal This is a purely internal method. If you rely on this method, you are advised to
* copy/paste the code as this method may change, or be removed without prior notice.
*
* @param array $params
* @param array $types
*
* @return array
*/
public function resolveParams(array $params, array $types)
{
$resolvedParams = array();
// Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
if (is_int(key($params))) {
// Positional parameters
$typeOffset = array_key_exists(0, $types) ? -1 : 0;
$bindIndex = 1;
foreach ($params as $value) {
$typeIndex = $bindIndex + $typeOffset;
if (isset($types[$typeIndex])) {
$type = $types[$typeIndex];
list($value,) = $this->getBindingInfo($value, $type);
$resolvedParams[$bindIndex] = $value;
} else {
$resolvedParams[$bindIndex] = $value;
}
++$bindIndex;
}
} else {
// Named parameters
foreach ($params as $name => $value) {
if (isset($types[$name])) {
$type = $types[$name];
list($value,) = $this->getBindingInfo($value, $type);
$resolvedParams[$name] = $value;
} else {
$resolvedParams[$name] = $value;
}
}
}
return $resolvedParams;
}
/** /**
* Create a new instance of a SQL query builder. * Create a new instance of a SQL query builder.
* *
......
...@@ -36,6 +36,17 @@ class DBALException extends \Exception ...@@ -36,6 +36,17 @@ class DBALException extends \Exception
"Doctrine currently supports only the following drivers: ".implode(", ", $knownDrivers)); "Doctrine currently supports only the following drivers: ".implode(", ", $knownDrivers));
} }
public static function driverExceptionDuringQuery(\Exception $driverEx, $sql, array $params = array())
{
$msg = "An exception occurred while executing '".$sql."'";
if ($params) {
$msg .= " with params ".json_encode($params);
}
$msg .= ":\n\n".$driverEx->getMessage();
return new self($msg, 0, $driverEx);
}
public static function invalidWrapperClass($wrapperClass) public static function invalidWrapperClass($wrapperClass)
{ {
return new self("The given 'wrapperClass' ".$wrapperClass." has to be a ". return new self("The given 'wrapperClass' ".$wrapperClass." has to be a ".
......
...@@ -134,7 +134,11 @@ class Statement implements \IteratorAggregate, DriverStatement ...@@ -134,7 +134,11 @@ class Statement implements \IteratorAggregate, DriverStatement
$logger->startQuery($this->sql, $this->params, $this->types); $logger->startQuery($this->sql, $this->params, $this->types);
} }
$stmt = $this->stmt->execute($params); try {
$stmt = $this->stmt->execute($params);
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, $this->sql, $this->conn->resolveParams($this->params, $this->types));
}
if ($logger) { if ($logger) {
$logger->stopQuery(); $logger->stopQuery();
......
...@@ -122,6 +122,35 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase ...@@ -122,6 +122,35 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
$this->assertSame($this->_conn->getEventManager(), $this->_conn->getDatabasePlatform()->getEventManager()); $this->assertSame($this->_conn->getEventManager(), $this->_conn->getDatabasePlatform()->getEventManager());
} }
/**
* @expectedException Doctrine\DBAL\DBALException
* @dataProvider getQueryMethods
*/
public function testDriverExceptionIsWrapped($method)
{
$this->setExpectedException('Doctrine\DBAL\DBALException', "An exception occurred while executing 'MUUHAAAAHAAAA':
SQLSTATE[HY000]: General error: 1 near \"MUUHAAAAHAAAA\"");
$con = \Doctrine\DBAL\DriverManager::getConnection(array(
'driver' => 'pdo_sqlite',
'memory' => true,
));
$con->$method('MUUHAAAAHAAAA');
}
public function getQueryMethods()
{
return array(
array('exec'),
array('query'),
array('executeQuery'),
array('executeUpdate'),
array('prepare'),
);
}
/** /**
* Pretty dumb test, however we want to check that the EchoSQLLogger correctly implements the interface. * Pretty dumb test, however we want to check that the EchoSQLLogger correctly implements the interface.
* *
......
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