Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic exception handling for SQL Server #4928

Merged
merged 3 commits into from
Oct 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/Driver/API/SQLSrv/ExceptionConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,59 @@

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;
use Doctrine\DBAL\Query;

/**
* @internal
*
* @link https://docs.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors
*/
final class ExceptionConverter implements ExceptionConverterInterface
{
public function convert(Exception $exception, ?Query $query): DriverException
{
switch ($exception->getCode()) {
case 102:
return new SyntaxErrorException($exception, $query);

case 207:
return new InvalidFieldNameException($exception, $query);

case 208:
return new TableNotFoundException($exception, $query);

case 209:
return new NonUniqueFieldNameException($exception, $query);

case 515:
return new NotNullConstraintViolationException($exception, $query);

case 547:
case 4712:
return new ForeignKeyConstraintViolationException($exception, $query);

case 2601:
case 2627:
return new UniqueConstraintViolationException($exception, $query);

case 2714:
return new TableExistsException($exception, $query);

case 11001:
case 18456:
return new ConnectionException($exception, $query);
}

return new DriverException($exception, $query);
}
}
62 changes: 29 additions & 33 deletions tests/Functional/ExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@

use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
use Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Tests\TestUtil;
use Throwable;

use function array_merge;
use function assert;
use function chmod;
use function exec;
use function file_exists;
Expand All @@ -25,7 +23,6 @@
use function sys_get_temp_dir;
use function touch;
use function unlink;
use function version_compare;

use const PHP_OS_FAMILY;

Expand All @@ -38,15 +35,11 @@ protected function setUp(): void
{
$driver = $this->connection->getDriver();

if ($driver instanceof IBMDB2\Driver) {
self::markTestSkipped("The IBM DB2 driver currently doesn't instantiate specialized exceptions");
}

if (! $driver instanceof AbstractSQLServerDriver) {
if (! $driver instanceof IBMDB2\Driver) {
return;
}

self::markTestSkipped("The SQL Server drivers currently don't instantiate specialized exceptions");
self::markTestSkipped("The IBM DB2 driver currently doesn't instantiate specialized exceptions");
}

public function testPrimaryConstraintViolationException(): void
Expand Down Expand Up @@ -349,43 +342,46 @@ public function testConnectionExceptionSqLite(): void
}
}

public function testInvalidUserName(): void
{
$this->testConnectionException(['user' => 'not_existing']);
}

public function testInvalidPassword(): void
{
$this->testConnectionException(['password' => 'really_not']);
}

public function testInvalidHost(): void
{
if ($this->connection->getDriver() instanceof AbstractSQLServerDriver) {
self::markTestSkipped(
'Some sqlsrv and pdo_sqlsrv versions do not provide the exception code or SQLSTATE for login timeout'
);
}

$this->testConnectionException(['host' => 'localnope']);
}

/**
* @param array<string, mixed> $params
* @psalm-param Params $params
*
* @dataProvider getConnectionParams
*/
public function testConnectionException(array $params): void
private function testConnectionException(array $params): void
{
$platform = $this->connection->getDatabasePlatform();

if ($platform instanceof SqlitePlatform) {
self::markTestSkipped('Only skipped if platform is not sqlite');
self::markTestSkipped('The SQLite driver does not use a network connection');
}

if ($platform instanceof MySQLPlatform && isset($params['user'])) {
$wrappedConnection = $this->connection->getWrappedConnection();
assert($wrappedConnection instanceof ServerInfoAwareConnection);

if (version_compare($wrappedConnection->getServerVersion(), '8', '>=')) {
self::markTestIncomplete('PHP currently does not completely support MySQL 8');
}
}

$defaultParams = $this->connection->getParams();
$params = array_merge($defaultParams, $params);

$conn = DriverManager::getConnection($params);

$schema = new Schema();
$table = $schema->createTable('no_connection');
$table->addColumn('id', 'integer');
$params = array_merge(TestUtil::getConnectionParams(), $params);
$conn = DriverManager::getConnection($params);

$this->expectException(Exception\ConnectionException::class);

foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
$conn->executeStatement($sql);
}
$conn->connect();
}

/**
Expand Down