Unverified Commit 4f578bea authored by Sergei Morozov's avatar Sergei Morozov Committed by GitHub

Merge pull request #4136 from morozov/exception-converter

Move the logic of driver exception conversion into a separate interface
parents 7f6cafc3 23c88b40
# Upgrade to 3.0
## BC BREAK: Changes in driver-level exception handling
1. The `convertException()` method has been removed from the `Driver` interface. The logic of exception conversion has been moved to the `ExceptionConverter` interface. The drivers now must implement the `getExceptionConverter()` method.
2. The `driverException()` and `driverExceptionDuringQuery()` factory methods have been removed from the `DBALException` class.
3. Non-driver exceptions (e.g. exceptions of type `Error`) are no longer wrapped in a `DBALException`.
## BC BREAK: More driver-level methods are allowed to throw a Driver\Exception.
The following driver-level methods are allowed to throw a Driver\Exception:
......
......@@ -8,10 +8,6 @@ parameters:
reportUnmatchedIgnoredErrors: false
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
earlyTerminatingMethodCalls:
Doctrine\DBAL\Connection:
- handleDriverException
- handleExceptionDuringQuery
ignoreErrors:
# removing it would be BC break
- '~^Constructor of class Doctrine\\DBAL\\Schema\\Table has an unused parameter \$idGeneratorType\.\z~'
......
This diff is collapsed.
......@@ -220,6 +220,8 @@ class PrimaryReadReplicaConnection extends Connection
* @param string $connectionName
*
* @return DriverConnection
*
* @throws DBALException
*/
protected function connectTo($connectionName)
{
......@@ -230,7 +232,7 @@ class PrimaryReadReplicaConnection extends Connection
try {
return $this->_driver->connect($connectionParams);
} catch (DriverException $e) {
throw DBALException::driverException($this->_driver, $e);
throw $this->convertException($e);
}
}
......
......@@ -2,24 +2,14 @@
namespace Doctrine\DBAL;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Exception;
use Throwable;
use function array_map;
use function bin2hex;
use function count;
use function get_class;
use function gettype;
use function implode;
use function is_object;
use function is_resource;
use function is_string;
use function json_encode;
use function preg_replace;
use function spl_object_hash;
use function sprintf;
......@@ -133,74 +123,6 @@ class DBALException extends Exception
'Doctrine currently supports only the following drivers: ' . implode(', ', $knownDrivers));
}
/**
* @param string $sql
* @param mixed[] $params
*
* @return self
*/
public static function driverExceptionDuringQuery(Driver $driver, Throwable $driverEx, $sql, array $params = [])
{
$msg = "An exception occurred while executing '" . $sql . "'";
if (count($params) > 0) {
$msg .= ' with params ' . self::formatParameters($params);
}
$msg .= ":\n\n" . $driverEx->getMessage();
return static::wrapException($driver, $driverEx, $msg);
}
/**
* @return self
*/
public static function driverException(Driver $driver, Throwable $driverEx)
{
return static::wrapException($driver, $driverEx, 'An exception occurred in driver: ' . $driverEx->getMessage());
}
/**
* @return self
*/
private static function wrapException(Driver $driver, Throwable $driverEx, string $msg)
{
if ($driverEx instanceof DriverException) {
return $driverEx;
}
if ($driverEx instanceof TheDriverException) {
return $driver->convertException($msg, $driverEx);
}
return new self($msg, 0, $driverEx);
}
/**
* Returns a human-readable representation of an array of parameters.
* This properly handles binary data by returning a hex representation.
*
* @param mixed[] $params
*
* @return string
*/
private static function formatParameters(array $params)
{
return '[' . implode(', ', array_map(static function ($param): string {
if (is_resource($param)) {
return (string) $param;
}
$json = @json_encode($param);
if (! is_string($json) || $json === 'null' && is_string($param)) {
// JSON encoding failed, this is not a UTF-8 string.
return sprintf('"%s"', preg_replace('/.{2}/', '\\x$0', bin2hex($param)));
}
return $json;
}, $params)) . ']';
}
/**
* @param string $wrapperClass
*
......
......@@ -2,9 +2,9 @@
namespace Doctrine\DBAL;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
......@@ -42,15 +42,7 @@ interface Driver
public function getSchemaManager(Connection $conn);
/**
* Converts a given driver-level exception into a DBAL-level driver exception.
*
* Implementors should use the vendor-specific error code and SQLSTATE of the exception
* and instantiate the most appropriate specialized {@link DriverException} subclass.
*
* @param string $message The exception message to use.
* @param Exception $exception The driver exception to convert.
*
* @return DriverException An instance of {@link DriverException} or one of its subclasses.
* Gets the ExceptionConverter that can be used to convert driver-level exceptions into DBAL exceptions.
*/
public function convertException($message, Exception $exception);
public function getExceptionConverter(): ExceptionConverter;
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\DriverException;
final class DefaultExceptionConverter implements ExceptionConverter
{
public function convert(string $message, Exception $exception): DriverException
{
return new DriverException($message, $exception);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\DriverException;
interface ExceptionConverter
{
/**
* Converts a given driver-level exception into a DBAL-level driver exception.
*
* Implementors should use the vendor-specific error code and SQLSTATE of the exception
* and instantiate the most appropriate specialized {@link DriverException} subclass.
*
* @param string $message The exception message to use.
* @param Exception $exception The driver exception to convert.
*
* @return DriverException An instance of {@link DriverException} or one of its subclasses.
*/
public function convert(string $message, Exception $exception): DriverException;
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\MySQL;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
final class ExceptionConverter implements ExceptionConverterInterface
{
/**
* @link https://dev.mysql.com/doc/refman/8.0/en/client-error-reference.html
* @link https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html
*/
public function convert(string $message, Exception $exception): DriverException
{
switch ($exception->getCode()) {
case 1213:
return new DeadlockException($message, $exception);
case 1205:
return new LockWaitTimeoutException($message, $exception);
case 1050:
return new TableExistsException($message, $exception);
case 1051:
case 1146:
return new TableNotFoundException($message, $exception);
case 1216:
case 1217:
case 1451:
case 1452:
case 1701:
return new ForeignKeyConstraintViolationException($message, $exception);
case 1062:
case 1557:
case 1569:
case 1586:
return new UniqueConstraintViolationException($message, $exception);
case 1054:
case 1166:
case 1611:
return new InvalidFieldNameException($message, $exception);
case 1052:
case 1060:
case 1110:
return new NonUniqueFieldNameException($message, $exception);
case 1064:
case 1149:
case 1287:
case 1341:
case 1342:
case 1343:
case 1344:
case 1382:
case 1479:
case 1541:
case 1554:
case 1626:
return new SyntaxErrorException($message, $exception);
case 1044:
case 1045:
case 1046:
case 1049:
case 1095:
case 1142:
case 1143:
case 1227:
case 1370:
case 1429:
case 2002:
case 2005:
return new ConnectionException($message, $exception);
case 2006:
return new ConnectionLost($message, $exception);
case 1048:
case 1121:
case 1138:
case 1171:
case 1252:
case 1263:
case 1364:
case 1566:
return new NotNullConstraintViolationException($message, $exception);
}
return new DriverException($message, $exception);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\OCI;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
final class ExceptionConverter implements ExceptionConverterInterface
{
/**
* @link http://www.dba-oracle.com/t_error_code_list.htm
*/
public function convert(string $message, Exception $exception): DriverException
{
switch ($exception->getCode()) {
case 1:
case 2299:
case 38911:
return new UniqueConstraintViolationException($message, $exception);
case 904:
return new InvalidFieldNameException($message, $exception);
case 918:
case 960:
return new NonUniqueFieldNameException($message, $exception);
case 923:
return new SyntaxErrorException($message, $exception);
case 942:
return new TableNotFoundException($message, $exception);
case 955:
return new TableExistsException($message, $exception);
case 1017:
case 12545:
return new ConnectionException($message, $exception);
case 1400:
return new NotNullConstraintViolationException($message, $exception);
case 2266:
case 2291:
case 2292:
return new ForeignKeyConstraintViolationException($message, $exception);
}
return new DriverException($message, $exception);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\PostgreSQL;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use function strpos;
final class ExceptionConverter implements ExceptionConverterInterface
{
/**
* @link http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html
*/
public function convert(string $message, Exception $exception): DriverException
{
switch ($exception->getSQLState()) {
case '40001':
case '40P01':
return new DeadlockException($message, $exception);
case '0A000':
// Foreign key constraint violations during a TRUNCATE operation
// are considered "feature not supported" in PostgreSQL.
if (strpos($exception->getMessage(), 'truncate') !== false) {
return new ForeignKeyConstraintViolationException($message, $exception);
}
break;
case '23502':
return new NotNullConstraintViolationException($message, $exception);
case '23503':
return new ForeignKeyConstraintViolationException($message, $exception);
case '23505':
return new UniqueConstraintViolationException($message, $exception);
case '42601':
return new SyntaxErrorException($message, $exception);
case '42702':
return new NonUniqueFieldNameException($message, $exception);
case '42703':
return new InvalidFieldNameException($message, $exception);
case '42P01':
return new TableNotFoundException($message, $exception);
case '42P07':
return new TableExistsException($message, $exception);
}
// In some case (mainly connection errors) the PDO exception does not provide a SQLSTATE via its code.
// The exception code is always set to 7 here.
// We have to match against the SQLSTATE in the error message in these cases.
if ($exception->getCode() === 7 && strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) {
return new ConnectionException($message, $exception);
}
return new DriverException($message, $exception);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\API\SQLite;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\ReadOnlyException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use function strpos;
final class ExceptionConverter implements ExceptionConverterInterface
{
/**
* @link http://www.sqlite.org/c3ref/c_abort.html
*/
public function convert(string $message, Exception $exception): DriverException
{
if (strpos($exception->getMessage(), 'database is locked') !== false) {
return new LockWaitTimeoutException($message, $exception);
}
if (
strpos($exception->getMessage(), 'must be unique') !== false ||
strpos($exception->getMessage(), 'is not unique') !== false ||
strpos($exception->getMessage(), 'are not unique') !== false ||
strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false
) {
return new UniqueConstraintViolationException($message, $exception);
}
if (
strpos($exception->getMessage(), 'may not be NULL') !== false ||
strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false
) {
return new NotNullConstraintViolationException($message, $exception);
}
if (strpos($exception->getMessage(), 'no such table:') !== false) {
return new TableNotFoundException($message, $exception);
}
if (strpos($exception->getMessage(), 'already exists') !== false) {
return new TableExistsException($message, $exception);
}
if (strpos($exception->getMessage(), 'has no column named') !== false) {
return new InvalidFieldNameException($message, $exception);
}
if (strpos($exception->getMessage(), 'ambiguous column name') !== false) {
return new NonUniqueFieldNameException($message, $exception);
}
if (strpos($exception->getMessage(), 'syntax error') !== false) {
return new SyntaxErrorException($message, $exception);
}
if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) {
return new ReadOnlyException($message, $exception);
}
if (strpos($exception->getMessage(), 'unable to open database file') !== false) {
return new ConnectionException($message, $exception);
}
if (strpos($exception->getMessage(), 'FOREIGN KEY constraint failed') !== false) {
return new ForeignKeyConstraintViolationException($message, $exception);
}
return new DriverException($message, $exception);
}
}
......@@ -4,7 +4,8 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Driver\API\DefaultExceptionConverter;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Schema\DB2SchemaManager;
......@@ -29,13 +30,8 @@ abstract class AbstractDB2Driver implements Driver
return new DB2SchemaManager($conn);
}
/**
* @param string $message
*
* @return DriverException
*/
public function convertException($message, Exception $exception)
public function getExceptionConverter(): ExceptionConverter
{
return new DriverException($message, $exception);
return new DefaultExceptionConverter();
}
}
......@@ -4,19 +4,8 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\MySQL;
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MySQL57Platform;
use Doctrine\DBAL\Platforms\MySQL80Platform;
......@@ -33,96 +22,6 @@ use function version_compare;
*/
abstract class AbstractMySQLDriver implements VersionAwarePlatformDriver
{
/**
* {@inheritdoc}
*
* @link https://dev.mysql.com/doc/refman/8.0/en/client-error-reference.html
* @link https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html
*/
public function convertException($message, Exception $exception)
{
switch ($exception->getCode()) {
case 1213:
return new DeadlockException($message, $exception);
case 1205:
return new LockWaitTimeoutException($message, $exception);
case 1050:
return new TableExistsException($message, $exception);
case 1051:
case 1146:
return new TableNotFoundException($message, $exception);
case 1216:
case 1217:
case 1451:
case 1452:
case 1701:
return new ForeignKeyConstraintViolationException($message, $exception);
case 1062:
case 1557:
case 1569:
case 1586:
return new UniqueConstraintViolationException($message, $exception);
case 1054:
case 1166:
case 1611:
return new InvalidFieldNameException($message, $exception);
case 1052:
case 1060:
case 1110:
return new NonUniqueFieldNameException($message, $exception);
case 1064:
case 1149:
case 1287:
case 1341:
case 1342:
case 1343:
case 1344:
case 1382:
case 1479:
case 1541:
case 1554:
case 1626:
return new SyntaxErrorException($message, $exception);
case 1044:
case 1045:
case 1046:
case 1049:
case 1095:
case 1142:
case 1143:
case 1227:
case 1370:
case 1429:
case 2002:
case 2005:
return new ConnectionException($message, $exception);
case 2006:
return new ConnectionLost($message, $exception);
case 1048:
case 1121:
case 1138:
case 1171:
case 1252:
case 1263:
case 1364:
case 1566:
return new NotNullConstraintViolationException($message, $exception);
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*
......@@ -228,4 +127,9 @@ abstract class AbstractMySQLDriver implements VersionAwarePlatformDriver
{
return new MySqlSchemaManager($conn);
}
public function getExceptionConverter(): ExceptionConverter
{
return new MySQL\ExceptionConverter();
}
}
......@@ -5,16 +5,8 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractOracleDriver\EasyConnectString;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\OCI;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Schema\OracleSchemaManager;
......@@ -23,49 +15,6 @@ use Doctrine\DBAL\Schema\OracleSchemaManager;
*/
abstract class AbstractOracleDriver implements Driver
{
/**
* {@inheritdoc}
*/
public function convertException($message, Exception $exception)
{
switch ($exception->getCode()) {
case 1:
case 2299:
case 38911:
return new UniqueConstraintViolationException($message, $exception);
case 904:
return new InvalidFieldNameException($message, $exception);
case 918:
case 960:
return new NonUniqueFieldNameException($message, $exception);
case 923:
return new SyntaxErrorException($message, $exception);
case 942:
return new TableNotFoundException($message, $exception);
case 955:
return new TableExistsException($message, $exception);
case 1017:
case 12545:
return new ConnectionException($message, $exception);
case 1400:
return new NotNullConstraintViolationException($message, $exception);
case 2266:
case 2291:
case 2292:
return new ForeignKeyConstraintViolationException($message, $exception);
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*/
......@@ -82,6 +31,11 @@ abstract class AbstractOracleDriver implements Driver
return new OracleSchemaManager($conn);
}
public function getExceptionConverter(): ExceptionConverter
{
return new OCI\ExceptionConverter();
}
/**
* Returns an appropriate Easy Connect String for the given parameters.
*
......
......@@ -4,24 +4,14 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\PostgreSQL;
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Schema\PostgreSqlSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use function preg_match;
use function strpos;
use function version_compare;
/**
......@@ -29,62 +19,6 @@ use function version_compare;
*/
abstract class AbstractPostgreSQLDriver implements VersionAwarePlatformDriver
{
/**
* {@inheritdoc}
*
* @link http://www.postgresql.org/docs/9.4/static/errcodes-appendix.html
*/
public function convertException($message, Exception $exception)
{
switch ($exception->getSQLState()) {
case '40001':
case '40P01':
return new DeadlockException($message, $exception);
case '0A000':
// Foreign key constraint violations during a TRUNCATE operation
// are considered "feature not supported" in PostgreSQL.
if (strpos($exception->getMessage(), 'truncate') !== false) {
return new ForeignKeyConstraintViolationException($message, $exception);
}
break;
case '23502':
return new NotNullConstraintViolationException($message, $exception);
case '23503':
return new ForeignKeyConstraintViolationException($message, $exception);
case '23505':
return new UniqueConstraintViolationException($message, $exception);
case '42601':
return new SyntaxErrorException($message, $exception);
case '42702':
return new NonUniqueFieldNameException($message, $exception);
case '42703':
return new InvalidFieldNameException($message, $exception);
case '42P01':
return new TableNotFoundException($message, $exception);
case '42P07':
return new TableExistsException($message, $exception);
}
// In some case (mainly connection errors) the PDO exception does not provide a SQLSTATE via its code.
// The exception code is always set to 7 here.
// We have to match against the SQLSTATE in the error message in these cases.
if ($exception->getCode() === 7 && strpos($exception->getMessage(), 'SQLSTATE[08006]') !== false) {
return new ConnectionException($message, $exception);
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*/
......@@ -124,4 +58,9 @@ abstract class AbstractPostgreSQLDriver implements VersionAwarePlatformDriver
{
return new PostgreSqlSchemaManager($conn);
}
public function getExceptionConverter(): ExceptionConverter
{
return new PostgreSQL\ExceptionConverter();
}
}
......@@ -4,7 +4,8 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Driver\API\DefaultExceptionConverter;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
use Doctrine\DBAL\Schema\SQLServerSchemaManager;
......@@ -29,13 +30,8 @@ abstract class AbstractSQLServerDriver implements Driver
return new SQLServerSchemaManager($conn);
}
/**
* @param string $message
*
* @return DriverException
*/
public function convertException($message, Exception $exception)
public function getExceptionConverter(): ExceptionConverter
{
return new DriverException($message, $exception);
return new DefaultExceptionConverter();
}
}
......@@ -4,90 +4,16 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\ReadOnlyException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\SQLite;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Schema\SqliteSchemaManager;
use function strpos;
/**
* Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for SQLite based drivers.
*/
abstract class AbstractSQLiteDriver implements Driver
{
/**
* {@inheritdoc}
*
* @link http://www.sqlite.org/c3ref/c_abort.html
*/
public function convertException($message, Exception $exception)
{
if (strpos($exception->getMessage(), 'database is locked') !== false) {
return new LockWaitTimeoutException($message, $exception);
}
if (
strpos($exception->getMessage(), 'must be unique') !== false ||
strpos($exception->getMessage(), 'is not unique') !== false ||
strpos($exception->getMessage(), 'are not unique') !== false ||
strpos($exception->getMessage(), 'UNIQUE constraint failed') !== false
) {
return new UniqueConstraintViolationException($message, $exception);
}
if (
strpos($exception->getMessage(), 'may not be NULL') !== false ||
strpos($exception->getMessage(), 'NOT NULL constraint failed') !== false
) {
return new NotNullConstraintViolationException($message, $exception);
}
if (strpos($exception->getMessage(), 'no such table:') !== false) {
return new TableNotFoundException($message, $exception);
}
if (strpos($exception->getMessage(), 'already exists') !== false) {
return new TableExistsException($message, $exception);
}
if (strpos($exception->getMessage(), 'has no column named') !== false) {
return new InvalidFieldNameException($message, $exception);
}
if (strpos($exception->getMessage(), 'ambiguous column name') !== false) {
return new NonUniqueFieldNameException($message, $exception);
}
if (strpos($exception->getMessage(), 'syntax error') !== false) {
return new SyntaxErrorException($message, $exception);
}
if (strpos($exception->getMessage(), 'attempt to write a readonly database') !== false) {
return new ReadOnlyException($message, $exception);
}
if (strpos($exception->getMessage(), 'unable to open database file') !== false) {
return new ConnectionException($message, $exception);
}
if (strpos($exception->getMessage(), 'FOREIGN KEY constraint failed') !== false) {
return new ForeignKeyConstraintViolationException($message, $exception);
}
return new DriverException($message, $exception);
}
/**
* {@inheritdoc}
*/
......@@ -103,4 +29,9 @@ abstract class AbstractSQLiteDriver implements Driver
{
return new SqliteSchemaManager($conn);
}
public function getExceptionConverter(): ExceptionConverter
{
return new SQLite\ExceptionConverter();
}
}
......@@ -33,7 +33,7 @@ final class Result implements ResultInterface
try {
return $this->result->fetchNumeric();
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
......@@ -47,19 +47,21 @@ final class Result implements ResultInterface
try {
return $this->result->fetchAssociative();
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
/**
* {@inheritDoc}
*
* @throws DBALException
*/
public function fetchOne()
{
try {
return $this->result->fetchOne();
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
......@@ -73,7 +75,7 @@ final class Result implements ResultInterface
try {
return $this->result->fetchAllNumeric();
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
......@@ -87,7 +89,7 @@ final class Result implements ResultInterface
try {
return $this->result->fetchAllAssociative();
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
......@@ -101,7 +103,7 @@ final class Result implements ResultInterface
try {
return $this->result->fetchFirstColumn();
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
......@@ -117,7 +119,7 @@ final class Result implements ResultInterface
yield $row;
}
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
......@@ -133,7 +135,7 @@ final class Result implements ResultInterface
yield $row;
}
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
......@@ -149,7 +151,7 @@ final class Result implements ResultInterface
yield $value;
}
} catch (DriverException $e) {
$this->connection->handleDriverException($e);
throw $this->connection->convertException($e);
}
}
......
......@@ -2,11 +2,11 @@
namespace Doctrine\DBAL;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Throwable;
use function is_string;
......@@ -65,11 +65,21 @@ class Statement implements DriverStatement
*
* @param string $sql The SQL of the statement.
* @param Connection $conn The connection on which the statement should be executed.
*
* @throws DBALException
*/
public function __construct($sql, Connection $conn)
{
$driverConnection = $conn->getWrappedConnection();
try {
$stmt = $driverConnection->prepare($sql);
} catch (Exception $ex) {
throw $conn->convertExceptionDuringQuery($ex, $sql);
}
$this->sql = $sql;
$this->stmt = $conn->getWrappedConnection()->prepare($sql);
$this->stmt = $stmt;
$this->conn = $conn;
$this->platform = $conn->getDatabasePlatform();
}
......@@ -154,8 +164,8 @@ class Statement implements DriverStatement
$this->stmt->execute($params),
$this->conn
);
} catch (Throwable $ex) {
$this->conn->handleExceptionDuringQuery($ex, $this->sql, $this->params, $this->types);
} catch (Exception $ex) {
throw $this->conn->convertExceptionDuringQuery($ex, $this->sql, $this->params, $this->types);
} finally {
if ($logger !== null) {
$logger->stopQuery();
......
<?php
namespace Doctrine\Tests\DBAL\Connection;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\API\DefaultExceptionConverter;
use Doctrine\DBAL\Driver\Exception as DriverException;
use PHPUnit\Framework\TestCase;
use function chr;
use function fopen;
final class ExceptionHandlingTest extends TestCase
{
/** @var Connection */
private $connection;
protected function setUp(): void
{
$this->connection = new Connection([], $this->createConfiguredMock(Driver::class, [
'getExceptionConverter' => new DefaultExceptionConverter(),
]));
}
public function testDriverExceptionDuringQueryAcceptsBinaryData(): void
{
$e = $this->connection->convertExceptionDuringQuery(
$this->createMock(DriverException::class),
'',
['ABC', chr(128)]
);
self::assertStringContainsString('with params ["ABC", "\x80"]', $e->getMessage());
}
public function testDriverExceptionDuringQueryAcceptsResource(): void
{
$e = $this->connection->convertExceptionDuringQuery(
$this->createMock(DriverException::class),
'INSERT INTO file (`content`) VALUES (?)',
[
1 => fopen(__FILE__, 'r'),
]
);
self::assertStringContainsString('Resource', $e->getMessage());
}
}
......@@ -3,44 +3,13 @@
namespace Doctrine\DBAL\Tests;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Exception as InnerDriverException;
use Doctrine\DBAL\Exception\DriverException;
use Exception;
use PHPUnit\Framework\TestCase;
use stdClass;
use function chr;
use function fopen;
use function sprintf;
class DBALExceptionTest extends TestCase
{
public function testDriverExceptionDuringQueryAcceptsBinaryData(): void
{
$driver = $this->createMock(Driver::class);
$e = DBALException::driverExceptionDuringQuery($driver, new Exception(), '', ['ABC', chr(128)]);
self::assertStringContainsString('with params ["ABC", "\x80"]', $e->getMessage());
}
public function testDriverExceptionDuringQueryAcceptsResource(): void
{
$driver = $this->createMock(Driver::class);
$e = DBALException::driverExceptionDuringQuery($driver, new Exception(), 'INSERT INTO file (`content`) VALUES (?)', [1 => fopen(__FILE__, 'r')]);
self::assertStringContainsString('Resource', $e->getMessage());
}
public function testAvoidOverWrappingOnDriverException(): void
{
$driver = $this->createMock(Driver::class);
$inner = $this->createMock(InnerDriverException::class);
$ex = new DriverException('', $inner);
$e = DBALException::driverExceptionDuringQuery($driver, $ex, '');
self::assertSame($ex, $e);
}
public function testDriverRequiredWithUrl(): void
{
$url = 'mysql://localhost';
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Tests\Driver\API;
use Doctrine\DBAL\Driver\AbstractException;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Exception\DriverException;
use PHPUnit\Framework\TestCase;
use function array_merge;
abstract class ExceptionConverterTest extends TestCase
{
/** @var ExceptionConverter */
private $converter;
protected function setUp(): void
{
parent::setUp();
$this->converter = $this->createConverter();
}
abstract protected function createConverter(): ExceptionConverter;
/**
* @dataProvider exceptionConversionProvider
*/
public function testConvertsException(
string $expectedClass,
int $errorCode,
?string $sqlState = null,
string $message = ''
): void {
$driverException = $this->getMockForAbstractClass(
AbstractException::class,
[$message, $sqlState, $errorCode]
);
$dbalMessage = 'DBAL exception message';
$dbalException = $this->converter->convert($dbalMessage, $driverException);
self::assertInstanceOf($expectedClass, $dbalException);
self::assertSame($driverException->getCode(), $dbalException->getCode());
self::assertSame($driverException->getSQLState(), $dbalException->getSQLState());
self::assertSame($driverException, $dbalException->getPrevious());
self::assertSame($dbalMessage, $dbalException->getMessage());
}
/**
* @return iterable<mixed[]>
*/
public static function exceptionConversionProvider(): iterable
{
foreach (static::getExceptionConversionData() as $expectedClass => $items) {
foreach ($items as $item) {
yield array_merge([$expectedClass], $item);
}
}
yield [DriverException::class, 1, 'HY000', 'The message'];
}
/**
* @return array<string,mixed[][]>
*/
abstract protected static function getExceptionConversionData(): array;
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Tests\Driver\API\MySQL;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\API\MySQL\ExceptionConverter;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Tests\Driver\API\ExceptionConverterTest as BaseExceptionConverterTest;
final class ExceptionConverterTest extends BaseExceptionConverterTest
{
protected function createConverter(): ExceptionConverterInterface
{
return new ExceptionConverter();
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
{
return [
ConnectionException::class => [
[1044],
[1045],
[1046],
[1049],
[1095],
[1142],
[1143],
[1227],
[1370],
[2002],
[2005],
],
ForeignKeyConstraintViolationException::class => [
[1216],
[1217],
[1451],
[1452],
],
InvalidFieldNameException::class => [
[1054],
[1166],
[1611],
],
NonUniqueFieldNameException::class => [
[1052],
[1060],
[1110],
],
NotNullConstraintViolationException::class => [
[1048],
[1121],
[1138],
[1171],
[1252],
[1263],
[1364],
[1566],
],
SyntaxErrorException::class => [
[1064],
[1149],
[1287],
[1341],
[1342],
[1343],
[1344],
[1382],
[1479],
[1541],
[1554],
[1626],
],
TableExistsException::class => [
[1050],
],
TableNotFoundException::class => [
[1051],
[1146],
],
UniqueConstraintViolationException::class => [
[1062],
[1557],
[1569],
[1586],
],
DeadlockException::class => [
[1213],
],
LockWaitTimeoutException::class => [
[1205],
],
];
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Tests\Driver\API\OCI;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\API\OCI\ExceptionConverter;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Tests\Driver\API\ExceptionConverterTest as BaseExceptionConverterTest;
final class ExceptionConverterTest extends BaseExceptionConverterTest
{
protected function createConverter(): ExceptionConverterInterface
{
return new ExceptionConverter();
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
{
return [
ConnectionException::class => [
[1017],
[12545],
],
ForeignKeyConstraintViolationException::class => [
[2292],
],
InvalidFieldNameException::class => [
[904],
],
NonUniqueFieldNameException::class => [
[918],
[960],
],
NotNullConstraintViolationException::class => [
[1400],
],
SyntaxErrorException::class => [
[923],
],
TableExistsException::class => [
[955],
],
TableNotFoundException::class => [
[942],
],
UniqueConstraintViolationException::class => [
[1],
[2299],
[38911],
],
];
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Tests\Driver\API\PostgreSQL;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\API\PostgreSQL\ExceptionConverter;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Tests\Driver\API\ExceptionConverterTest as BaseExceptionConverterTest;
final class ExceptionConverterTest extends BaseExceptionConverterTest
{
protected function createConverter(): ExceptionConverterInterface
{
return new ExceptionConverter();
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
{
return [
ConnectionException::class => [
[7, null, 'SQLSTATE[08006]'],
],
ForeignKeyConstraintViolationException::class => [
[0, '23503'],
],
InvalidFieldNameException::class => [
[0, '42703'],
],
NonUniqueFieldNameException::class => [
[0, '42702'],
],
NotNullConstraintViolationException::class => [
[0, '23502'],
],
SyntaxErrorException::class => [
[0, '42601'],
],
TableExistsException::class => [
[0, '42P07'],
],
TableNotFoundException::class => [
[0, '42P01'],
],
UniqueConstraintViolationException::class => [
[0, '23505'],
],
DeadlockException::class => [
[0, '40001'],
[0, '40P01'],
],
];
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Tests\Driver\API\SQLite;
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\API\SQLite\ExceptionConverter;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\ReadOnlyException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Tests\Driver\API\ExceptionConverterTest as BaseExceptionConverterTest;
final class ExceptionConverterTest extends BaseExceptionConverterTest
{
protected function createConverter(): ExceptionConverterInterface
{
return new ExceptionConverter();
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
{
return [
ConnectionException::class => [
[0, null, 'unable to open database file'],
],
InvalidFieldNameException::class => [
[0, null, 'has no column named'],
],
NonUniqueFieldNameException::class => [
[0, null, 'ambiguous column name'],
],
NotNullConstraintViolationException::class => [
[0, null, 'may not be NULL'],
],
ReadOnlyException::class => [
[0, null, 'attempt to write a readonly database'],
],
SyntaxErrorException::class => [
[0, null, 'syntax error'],
],
TableExistsException::class => [
[0, null, 'already exists'],
],
TableNotFoundException::class => [
[0, null, 'no such table:'],
],
UniqueConstraintViolationException::class => [
[0, null, 'must be unique'],
[0, null, 'is not unique'],
[0, null, 'are not unique'],
],
LockWaitTimeoutException::class => [
[0, null, 'database is locked'],
],
];
}
}
......@@ -5,6 +5,8 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractDB2Driver;
use Doctrine\DBAL\Driver\API\DefaultExceptionConverter;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
......@@ -26,4 +28,9 @@ class AbstractDB2DriverTest extends AbstractDriverTest
{
return new DB2SchemaManager($connection);
}
protected function createExceptionConverter(): ExceptionConverter
{
return new DefaultExceptionConverter();
}
}
......@@ -5,26 +5,7 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractException;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
use Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\ConstraintViolationException;
use Doctrine\DBAL\Exception\DatabaseObjectExistsException;
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\ReadOnlyException;
use Doctrine\DBAL\Exception\ServerException;
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
......@@ -32,30 +13,11 @@ use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;
use function array_merge;
use function get_class;
use function sprintf;
abstract class AbstractDriverTest extends TestCase
{
public const EXCEPTION_CONNECTION = ConnectionException::class;
public const EXCEPTION_CONSTRAINT_VIOLATION = ConstraintViolationException::class;
public const EXCEPTION_DATABASE_OBJECT_EXISTS = DatabaseObjectExistsException::class;
public const EXCEPTION_DATABASE_OBJECT_NOT_FOUND = DatabaseObjectNotFoundException::class;
public const EXCEPTION_DRIVER = DriverException::class;
public const EXCEPTION_FOREIGN_KEY_CONSTRAINT_VIOLATION = ForeignKeyConstraintViolationException::class;
public const EXCEPTION_INVALID_FIELD_NAME = InvalidFieldNameException::class;
public const EXCEPTION_NON_UNIQUE_FIELD_NAME = NonUniqueFieldNameException::class;
public const EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION = NotNullConstraintViolationException::class;
public const EXCEPTION_READ_ONLY = ReadOnlyException::class;
public const EXCEPTION_SERVER = ServerException::class;
public const EXCEPTION_SYNTAX_ERROR = SyntaxErrorException::class;
public const EXCEPTION_TABLE_EXISTS = TableExistsException::class;
public const EXCEPTION_TABLE_NOT_FOUND = TableNotFoundException::class;
public const EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION = UniqueConstraintViolationException::class;
public const EXCEPTION_DEADLOCK = DeadlockException::class;
public const EXCEPTION_LOCK_WAIT_TIMEOUT = LockWaitTimeoutException::class;
/**
* The driver mock under test.
*
......@@ -70,39 +32,6 @@ abstract class AbstractDriverTest extends TestCase
$this->driver = $this->createDriver();
}
/**
* @dataProvider exceptionConversionProvider
*/
public function testConvertsException(
string $expectedClass,
int $errorCode,
?string $sqlState = null,
string $message = ''
): void {
if ($this->driver instanceof IBMDB2\Driver) {
self::markTestSkipped("The IBM DB2 driver currently doesn't instantiate specialized exceptions");
}
if ($this->driver instanceof AbstractSQLServerDriver) {
self::markTestSkipped("The SQL Server drivers currently don't instantiate specialized exceptions");
}
$driverException = $this->getMockForAbstractClass(
AbstractException::class,
[$message, $sqlState, $errorCode]
);
$dbalMessage = 'DBAL exception message';
$dbalException = $this->driver->convertException($dbalMessage, $driverException);
self::assertInstanceOf($expectedClass, $dbalException);
self::assertSame($driverException->getCode(), $dbalException->getCode());
self::assertSame($driverException->getSQLState(), $dbalException->getSQLState());
self::assertSame($driverException, $dbalException->getPrevious());
self::assertSame($dbalMessage, $dbalException->getMessage());
}
public function testCreatesDatabasePlatformForVersion(): void
{
if (! $this->driver instanceof VersionAwarePlatformDriver) {
......@@ -164,6 +93,11 @@ abstract class AbstractDriverTest extends TestCase
self::assertSame($connection, $re->getValue($schemaManager));
}
public function testReturnsExceptionConverter(): void
{
self::assertEquals($this->createExceptionConverter(), $this->driver->getExceptionConverter());
}
/**
* Factory method for creating the driver instance under test.
*/
......@@ -187,6 +121,8 @@ abstract class AbstractDriverTest extends TestCase
*/
abstract protected function createSchemaManager(Connection $connection): AbstractSchemaManager;
abstract protected function createExceptionConverter(): ExceptionConverter;
/**
* @return Connection&MockObject
*/
......@@ -202,26 +138,4 @@ abstract class AbstractDriverTest extends TestCase
{
return [];
}
/**
* @return iterable<mixed[]>
*/
public static function exceptionConversionProvider(): iterable
{
foreach (static::getExceptionConversionData() as $expectedClass => $items) {
foreach ($items as $item) {
yield array_merge([$expectedClass], $item);
}
}
yield [self::EXCEPTION_DRIVER, 1, 'HY000', 'The message'];
}
/**
* @return array<string,mixed[][]>
*/
protected static function getExceptionConversionData(): array
{
return [];
}
}
......@@ -5,6 +5,8 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\MySQL;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MySQL57Platform;
......@@ -30,6 +32,11 @@ class AbstractMySQLDriverTest extends AbstractDriverTest
return new MySqlSchemaManager($connection);
}
protected function createExceptionConverter(): ExceptionConverter
{
return new MySQL\ExceptionConverter();
}
/**
* {@inheritDoc}
*/
......@@ -55,85 +62,4 @@ class AbstractMySQLDriverTest extends AbstractDriverTest
['10.2.8-MariaDB-1~lenny-log', MariaDb1027Platform::class],
];
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
{
return [
self::EXCEPTION_CONNECTION => [
[1044],
[1045],
[1046],
[1049],
[1095],
[1142],
[1143],
[1227],
[1370],
[2002],
[2005],
],
self::EXCEPTION_FOREIGN_KEY_CONSTRAINT_VIOLATION => [
[1216],
[1217],
[1451],
[1452],
],
self::EXCEPTION_INVALID_FIELD_NAME => [
[1054],
[1166],
[1611],
],
self::EXCEPTION_NON_UNIQUE_FIELD_NAME => [
[1052],
[1060],
[1110],
],
self::EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION => [
[1048],
[1121],
[1138],
[1171],
[1252],
[1263],
[1364],
[1566],
],
self::EXCEPTION_SYNTAX_ERROR => [
[1064],
[1149],
[1287],
[1341],
[1342],
[1343],
[1344],
[1382],
[1479],
[1541],
[1554],
[1626],
],
self::EXCEPTION_TABLE_EXISTS => [
[1050],
],
self::EXCEPTION_TABLE_NOT_FOUND => [
[1051],
[1146],
],
self::EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION => [
[1062],
[1557],
[1569],
[1586],
],
self::EXCEPTION_DEADLOCK => [
[1213],
],
self::EXCEPTION_LOCK_WAIT_TIMEOUT => [
[1205],
],
];
}
}
......@@ -5,6 +5,8 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractOracleDriver;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\OCI;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
......@@ -27,43 +29,8 @@ class AbstractOracleDriverTest extends AbstractDriverTest
return new OracleSchemaManager($connection);
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
protected function createExceptionConverter(): ExceptionConverter
{
return [
self::EXCEPTION_CONNECTION => [
[1017],
[12545],
],
self::EXCEPTION_FOREIGN_KEY_CONSTRAINT_VIOLATION => [
[2292],
],
self::EXCEPTION_INVALID_FIELD_NAME => [
[904],
],
self::EXCEPTION_NON_UNIQUE_FIELD_NAME => [
[918],
[960],
],
self::EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION => [
[1400],
],
self::EXCEPTION_SYNTAX_ERROR => [
[923],
],
self::EXCEPTION_TABLE_EXISTS => [
[955],
],
self::EXCEPTION_TABLE_NOT_FOUND => [
[942],
],
self::EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION => [
[1],
[2299],
[38911],
],
];
return new OCI\ExceptionConverter();
}
}
......@@ -5,6 +5,8 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\PostgreSQL;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
......@@ -28,6 +30,11 @@ class AbstractPostgreSQLDriverTest extends AbstractDriverTest
return new PostgreSqlSchemaManager($connection);
}
protected function createExceptionConverter(): ExceptionConverter
{
return new PostgreSQL\ExceptionConverter();
}
/**
* {@inheritDoc}
*/
......@@ -40,44 +47,4 @@ class AbstractPostgreSQLDriverTest extends AbstractDriverTest
['10', PostgreSQL100Platform::class],
];
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
{
return [
self::EXCEPTION_CONNECTION => [
[7, null, 'SQLSTATE[08006]'],
],
self::EXCEPTION_FOREIGN_KEY_CONSTRAINT_VIOLATION => [
[0, '23503'],
],
self::EXCEPTION_INVALID_FIELD_NAME => [
[0, '42703'],
],
self::EXCEPTION_NON_UNIQUE_FIELD_NAME => [
[0, '42702'],
],
self::EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION => [
[0, '23502'],
],
self::EXCEPTION_SYNTAX_ERROR => [
[0, '42601'],
],
self::EXCEPTION_TABLE_EXISTS => [
[0, '42P07'],
],
self::EXCEPTION_TABLE_NOT_FOUND => [
[0, '42P01'],
],
self::EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION => [
[0, '23505'],
],
self::EXCEPTION_DEADLOCK => [
[0, '40001'],
[0, '40P01'],
],
];
}
}
......@@ -4,6 +4,8 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost;
use Doctrine\DBAL\Driver\API\DefaultExceptionConverter;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
......@@ -21,6 +23,11 @@ abstract class AbstractSQLServerDriverTest extends AbstractDriverTest
return new SQLServerSchemaManager($connection);
}
protected function createExceptionConverter(): ExceptionConverter
{
return new DefaultExceptionConverter();
}
/**
* {@inheritDoc}
*/
......
......@@ -5,6 +5,8 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractSQLiteDriver;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\API\SQLite;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
......@@ -27,44 +29,8 @@ class AbstractSQLiteDriverTest extends AbstractDriverTest
return new SqliteSchemaManager($connection);
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
protected function createExceptionConverter(): ExceptionConverter
{
return [
self::EXCEPTION_CONNECTION => [
[0, null, 'unable to open database file'],
],
self::EXCEPTION_INVALID_FIELD_NAME => [
[0, null, 'has no column named'],
],
self::EXCEPTION_NON_UNIQUE_FIELD_NAME => [
[0, null, 'ambiguous column name'],
],
self::EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION => [
[0, null, 'may not be NULL'],
],
self::EXCEPTION_READ_ONLY => [
[0, null, 'attempt to write a readonly database'],
],
self::EXCEPTION_SYNTAX_ERROR => [
[0, null, 'syntax error'],
],
self::EXCEPTION_TABLE_EXISTS => [
[0, null, 'already exists'],
],
self::EXCEPTION_TABLE_NOT_FOUND => [
[0, null, 'no such table:'],
],
self::EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION => [
[0, null, 'must be unique'],
[0, null, 'is not unique'],
[0, null, 'are not unique'],
],
self::EXCEPTION_LOCK_WAIT_TIMEOUT => [
[0, null, 'database is locked'],
],
];
return new SQLite\ExceptionConverter();
}
}
......@@ -4,10 +4,6 @@ namespace Doctrine\DBAL\Tests\Functional;
use DateTime;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\IBMDB2\Driver as IBMDB2Driver;
use Doctrine\DBAL\Driver\Mysqli\Driver as MySQLiDriver;
use Doctrine\DBAL\Driver\SQLSrv\Driver as SQLSrvDriver;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\TrimMode;
......@@ -190,66 +186,6 @@ class DataAccessTest extends FunctionalTestCase
self::assertStringStartsWith($datetimeString, $row['test_datetime']);
}
/**
* @group DBAL-209
* @dataProvider fetchProvider
*/
public function testFetchAllWithMissingTypes(callable $fetch): void
{
if (
$this->connection->getDriver() instanceof MySQLiDriver ||
$this->connection->getDriver() instanceof SQLSrvDriver
) {
self::markTestSkipped('mysqli and sqlsrv actually supports this');
}
if (
$this->connection->getDriver() instanceof IBMDB2Driver
) {
$this->markTestSkipped(
'ibm_ibm2 may or may not report the error depending on the PHP version and the connection state'
);
}
$datetimeString = '2010-01-01 10:10:10';
$datetime = new DateTime($datetimeString);
$sql = 'SELECT test_int, test_datetime FROM fetch_table WHERE test_int = ? AND test_datetime = ?';
$this->expectException(DBALException::class);
$fetch($this->connection, $sql, [1, $datetime]);
}
/**
* @return iterable<string,array{0:callable}>
*/
public static function fetchProvider(): iterable
{
yield 'fetch-all-associative' => [
static function (Connection $connection, string $query, array $params): void {
$connection->fetchAllAssociative($query, $params);
},
];
yield 'fetch-numeric' => [
static function (Connection $connection, string $query, array $params): void {
$connection->fetchNumeric($query, $params);
},
];
yield 'fetch-associative' => [
static function (Connection $connection, string $query, array $params): void {
$connection->fetchAssociative($query, $params);
},
];
yield 'fetch-one' => [
static function (Connection $connection, string $query, array $params): void {
$connection->fetchOne($query, $params);
},
];
}
public function testFetchNoResult(): void
{
self::assertFalse(
......
......@@ -7,11 +7,11 @@ use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\Exception as DriverException;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Logging\SQLLogger;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Statement;
use Exception;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
......@@ -127,10 +127,6 @@ class StatementTest extends TestCase
->method('getSQLLogger')
->will(self::returnValue($logger));
$this->conn->expects(self::any())
->method('handleExceptionDuringQuery')
->will(self::throwException(new DBALException()));
$logger->expects(self::once())
->method('startQuery');
......@@ -139,7 +135,9 @@ class StatementTest extends TestCase
$this->driverStatement->expects(self::once())
->method('execute')
->will(self::throwException(new Exception('Mock test exception')));
->will(self::throwException(
$this->createMock(DriverException::class)
));
$statement = new Statement('', $this->conn);
......
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