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 IBM DB2 #4929

Merged
merged 2 commits into from
Oct 25, 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
44 changes: 44 additions & 0 deletions src/Driver/API/IBMDB2/ExceptionConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,60 @@

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://www.ibm.com/docs/en/db2/11.5?topic=messages-sql
*/
final class ExceptionConverter implements ExceptionConverterInterface
{
public function convert(Exception $exception, ?Query $query): DriverException
{
switch ($exception->getCode()) {
case -104:
return new SyntaxErrorException($exception, $query);

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

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

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

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

case -530:
case -531:
case -532:
case -20356:
return new ForeignKeyConstraintViolationException($exception, $query);

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

case -803:
return new UniqueConstraintViolationException($exception, $query);

case -1336:
case -30082:
return new ConnectionException($exception, $query);
}

return new DriverException($exception, $query);
}
}
1 change: 1 addition & 0 deletions src/Driver/API/MySQL/ExceptionConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public function convert(Exception $exception, ?Query $query): DriverException
case 1429:
case 2002:
case 2005:
case 2054:
return new ConnectionException($exception, $query);

case 2006:
Expand Down
3 changes: 2 additions & 1 deletion src/Driver/IBMDB2/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionError;
use Doctrine\DBAL\Driver\IBMDB2\Exception\ConnectionFailed;
use Doctrine\DBAL\Driver\IBMDB2\Exception\PrepareFailed;
use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
Expand Down Expand Up @@ -109,7 +110,7 @@ public function exec(string $sql): int
$stmt = @db2_exec($this->conn, $sql);

if ($stmt === false) {
throw ConnectionError::new($this->conn);
throw StatementError::new();
}

return db2_num_rows($stmt);
Expand Down
7 changes: 6 additions & 1 deletion src/Driver/IBMDB2/Exception/ConnectionError.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ final class ConnectionError extends AbstractException
*/
public static function new($connection): self
{
return new self(db2_conn_errormsg($connection), db2_conn_error($connection));
$message = db2_conn_errormsg($connection);
$sqlState = db2_conn_error($connection);

return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
return new self($message, $sqlState, $code);
});
}
}
7 changes: 6 additions & 1 deletion src/Driver/IBMDB2/Exception/ConnectionFailed.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ final class ConnectionFailed extends AbstractException
{
public static function new(): self
{
return new self(db2_conn_errormsg(), db2_conn_error());
$message = db2_conn_errormsg();
$sqlState = db2_conn_error();

return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
return new self($message, $sqlState, $code);
});
}
}
35 changes: 35 additions & 0 deletions src/Driver/IBMDB2/Exception/Factory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Driver\IBMDB2\Exception;

use Doctrine\DBAL\Driver\AbstractException;

use function preg_match;

/**
* @internal
*
* @psalm-immutable
*/
final class Factory
{
/**
* @param callable(int): T $constructor
*
* @return T
*
* @template T of AbstractException
*/
public static function create(string $message, callable $constructor): AbstractException
{
$code = 0;

if (preg_match('/ SQL(\d+)N /', $message, $matches) === 1) {
$code = -(int) $matches[1];
}

return $constructor($code);
}
}
16 changes: 13 additions & 3 deletions src/Driver/IBMDB2/Exception/StatementError.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,20 @@
final class StatementError extends AbstractException
{
/**
* @param resource $statement
* @param resource|null $statement
*/
public static function new($statement): self
public static function new($statement = null): self
{
return new self(db2_stmt_errormsg($statement), db2_stmt_error($statement));
if ($statement !== null) {
$message = db2_stmt_errormsg($statement);
$sqlState = db2_stmt_error($statement);
} else {
$message = db2_stmt_errormsg();
$sqlState = db2_stmt_error();
}

return Factory::create($message, static function (int $code) use ($message, $sqlState): self {
return new self($message, $sqlState, $code);
});
}
}
2 changes: 1 addition & 1 deletion src/Driver/IBMDB2/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public function execute($params = null): ResultInterface
$this->writeStringToStream($source, $target);
}

$result = db2_execute($this->stmt, $params);
$result = @db2_execute($this->stmt, $params);

foreach ($this->lobs as [, $handle]) {
fclose($handle);
Expand Down
16 changes: 2 additions & 14 deletions tests/Functional/ExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Doctrine\DBAL\Tests\Functional;

use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
use Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\SqlitePlatform;
Expand Down Expand Up @@ -31,17 +30,6 @@
*/
class ExceptionTest extends FunctionalTestCase
{
protected function setUp(): void
{
$driver = $this->connection->getDriver();

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

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

public function testPrimaryConstraintViolationException(): void
{
$table = new Table('duplicatekey_table');
Expand Down Expand Up @@ -220,15 +208,15 @@ public function testNotNullConstraintViolationException(): void

$table = $schema->createTable('notnull_table');
$table->addColumn('id', 'integer', []);
$table->addColumn('value', 'integer', ['notnull' => true]);
$table->addColumn('val', 'integer', ['notnull' => true]);
$table->setPrimaryKey(['id']);

foreach ($schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
$this->connection->executeStatement($sql);
}

$this->expectException(Exception\NotNullConstraintViolationException::class);
$this->connection->insert('notnull_table', ['id' => 1, 'value' => null]);
$this->connection->insert('notnull_table', ['id' => 1, 'val' => null]);
}

public function testInvalidFieldNameException(): void
Expand Down