Commit f94e7774 authored by Johannes M. Schmitt's avatar Johannes M. Schmitt Committed by Benjamin Eberlei

improved exception messages when driver error occurs

parent 39e0f2f3
# 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.
## 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
......
......@@ -594,7 +594,12 @@ class Connection implements DriverConnection
{
$this->connect();
try {
$stmt = new Statement($statement, $this);
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, $statement);
}
$stmt->setFetchMode($this->_defaultFetchMode);
return $stmt;
......@@ -626,6 +631,7 @@ class Connection implements DriverConnection
$logger->startQuery($query, $params, $types);
}
try {
if ($params) {
list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
......@@ -639,6 +645,9 @@ class Connection implements DriverConnection
} else {
$stmt = $this->_conn->query($query);
}
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, $query, $this->resolveParams($params, $types));
}
$stmt->setFetchMode($this->_defaultFetchMode);
......@@ -729,7 +738,12 @@ class Connection implements DriverConnection
$logger->startQuery($args[0]);
}
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);
if ($logger) {
......@@ -760,6 +774,7 @@ class Connection implements DriverConnection
$logger->startQuery($query, $params, $types);
}
try {
if ($params) {
list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
......@@ -774,6 +789,9 @@ class Connection implements DriverConnection
} else {
$result = $this->_conn->exec($query);
}
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, $query, $this->resolveParams($params, $types));
}
if ($logger) {
$logger->stopQuery();
......@@ -797,7 +815,11 @@ class Connection implements DriverConnection
$logger->startQuery($statement);
}
try {
$result = $this->_conn->exec($statement);
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, $statement);
}
if ($logger) {
$logger->stopQuery();
......@@ -1227,6 +1249,53 @@ class Connection implements DriverConnection
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.
*
......
......@@ -36,6 +36,17 @@ class DBALException extends \Exception
"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)
{
return new self("The given 'wrapperClass' ".$wrapperClass." has to be a ".
......
......@@ -134,7 +134,11 @@ class Statement implements \IteratorAggregate, DriverStatement
$logger->startQuery($this->sql, $this->params, $this->types);
}
try {
$stmt = $this->stmt->execute($params);
} catch (\Exception $ex) {
throw DBALException::driverExceptionDuringQuery($ex, $this->sql, $this->conn->resolveParams($this->params, $this->types));
}
if ($logger) {
$logger->stopQuery();
......
......@@ -122,6 +122,35 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
$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.
*
......
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