From e676583f26fdb96e58ff207c81920adf8dfdc7ff Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Fri, 12 Jun 2020 14:54:18 -0700 Subject: [PATCH] The IBM DB2 driver Exception class must implement the DriverException interface --- .../DBAL/Driver/IBMDB2/DB2Connection.php | 18 ++-- .../DBAL/Driver/IBMDB2/DB2Exception.php | 4 +- .../DBAL/Driver/IBMDB2/DB2Statement.php | 5 +- .../IBMDB2/Exception/ConnectionError.php | 24 +++++ .../IBMDB2/Exception/ConnectionFailed.php | 21 ++++ .../Driver/IBMDB2/Exception/PrepareFailed.php | 18 ++++ .../IBMDB2/Exception/StatementError.php | 24 +++++ phpcs.xml.dist | 5 + .../Tests/DBAL/Functional/DataAccessTest.php | 98 ++++++++----------- .../Driver/IBMDB2/ConnectionTest.php | 55 +++++++++++ .../Driver/IBMDB2/DB2StatementTest.php | 17 +++- 11 files changed, 217 insertions(+), 72 deletions(-) create mode 100644 lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionError.php create mode 100644 lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionFailed.php create mode 100644 lib/Doctrine/DBAL/Driver/IBMDB2/Exception/PrepareFailed.php create mode 100644 lib/Doctrine/DBAL/Driver/IBMDB2/Exception/StatementError.php create mode 100644 tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/ConnectionTest.php diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php index 60dbe59f58d..d74f9db795c 100644 --- a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php @@ -3,6 +3,9 @@ namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\Connection; +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\ServerInfoAwareConnection; use Doctrine\DBAL\ParameterType; use stdClass; @@ -21,7 +24,7 @@ use function db2_prepare; use function db2_rollback; use function db2_server_info; -use function db2_stmt_errormsg; +use function error_get_last; use function func_get_args; use function is_bool; @@ -52,7 +55,7 @@ public function __construct(array $params, $username, $password, $driverOptions } if ($conn === false) { - throw new DB2Exception(db2_conn_errormsg()); + throw ConnectionFailed::new(); } $this->conn = $conn; @@ -83,8 +86,9 @@ public function requiresQueryForServerVersion() public function prepare($sql) { $stmt = @db2_prepare($this->conn, $sql); - if (! $stmt) { - throw new DB2Exception(db2_stmt_errormsg()); + + if ($stmt === false) { + throw PrepareFailed::new(error_get_last()['message']); } return new DB2Statement($stmt); @@ -125,7 +129,7 @@ public function exec($statement) $stmt = @db2_exec($this->conn, $statement); if ($stmt === false) { - throw new DB2Exception(db2_stmt_errormsg()); + throw ConnectionError::new($this->conn); } return db2_num_rows($stmt); @@ -156,7 +160,7 @@ public function beginTransaction() public function commit() { if (! db2_commit($this->conn)) { - throw new DB2Exception(db2_conn_errormsg($this->conn)); + throw ConnectionError::new($this->conn); } $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON); @@ -171,7 +175,7 @@ public function commit() public function rollBack() { if (! db2_rollback($this->conn)) { - throw new DB2Exception(db2_conn_errormsg($this->conn)); + throw ConnectionError::new($this->conn); } $result = db2_autocommit($this->conn, DB2_AUTOCOMMIT_ON); diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php index 662d8533a8e..f66d0e533f9 100644 --- a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php @@ -2,11 +2,11 @@ namespace Doctrine\DBAL\Driver\IBMDB2; -use Exception; +use Doctrine\DBAL\Driver\AbstractDriverException; /** * @psalm-immutable */ -class DB2Exception extends Exception +class DB2Exception extends AbstractDriverException { } diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php index 7c97017099d..5113a60da35 100644 --- a/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php @@ -3,6 +3,7 @@ namespace Doctrine\DBAL\Driver\IBMDB2; use Doctrine\DBAL\Driver\FetchUtils; +use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\StatementIterator; @@ -141,7 +142,7 @@ private function bind($position, &$variable, int $parameterType, int $dataType): $this->bindParam[$position] =& $variable; if (! db2_bind_param($this->stmt, $position, 'variable', $parameterType, $dataType)) { - throw new DB2Exception(db2_stmt_errormsg()); + throw StatementError::new($this->stmt); } } @@ -228,7 +229,7 @@ public function execute($params = null) $this->lobs = []; if ($retval === false) { - throw new DB2Exception(db2_stmt_errormsg()); + throw StatementError::new($this->stmt); } $this->result = true; diff --git a/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionError.php b/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionError.php new file mode 100644 index 00000000000..7293f87eb6f --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/IBMDB2/Exception/ConnectionError.php @@ -0,0 +1,24 @@ +lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php + + + tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php + + lib/Doctrine/DBAL/Schema/Comparator.php diff --git a/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php b/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php index 1fe74ad4075..b3330b7c093 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/DataAccessTest.php @@ -5,6 +5,7 @@ use DateTime; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; use Doctrine\DBAL\Driver\Mysqli\Driver as MySQLiDriver; use Doctrine\DBAL\Driver\OCI8\Driver as Oci8Driver; use Doctrine\DBAL\Driver\PDOConnection; @@ -245,8 +246,9 @@ public function testFetchAllWithTypes(): void /** * @group DBAL-209 + * @dataProvider fetchProvider */ - public function testFetchAllWithMissingTypes(): void + public function testFetchAllWithMissingTypes(callable $fetch): void { if ( $this->connection->getDriver() instanceof MySQLiDriver || @@ -255,13 +257,51 @@ public function testFetchAllWithMissingTypes(): void $this->markTestSkipped('mysqli and sqlsrv actually supports this'); } + if ( + $this->connection->getDriver() instanceof DB2Driver + ) { + $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); - $this->connection->fetchAll($sql, [1, $datetime]); + $fetch($this->connection, $sql, [1, $datetime]); + } + + /** + * @return iterable + */ + public static function fetchProvider(): iterable + { + yield 'fetch-all-associative' => [ + static function (Connection $connection, string $query, array $params): void { + $connection->fetchAll($query, $params); + }, + ]; + + yield 'fetch-numeric' => [ + static function (Connection $connection, string $query, array $params): void { + $connection->fetchArray($query, $params); + }, + ]; + + yield 'fetch-associative' => [ + static function (Connection $connection, string $query, array $params): void { + $connection->fetchAssoc($query, $params); + }, + ]; + + yield 'fetch-one' => [ + static function (Connection $connection, string $query, array $params): void { + $connection->fetchColumn($query, $params); + }, + ]; } public function testFetchBoth(): void @@ -319,24 +359,6 @@ public function testFetchAssocWithTypes(): void self::assertStringStartsWith($datetimeString, $row['test_datetime']); } - public function testFetchAssocWithMissingTypes(): void - { - if ( - $this->connection->getDriver() instanceof MySQLiDriver || - $this->connection->getDriver() instanceof SQLSrvDriver - ) { - $this->markTestSkipped('mysqli and sqlsrv actually supports this'); - } - - $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); - - $this->connection->fetchAssoc($sql, [1, $datetime]); - } - public function testFetchArray(): void { $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; @@ -366,24 +388,6 @@ public function testFetchArrayWithTypes(): void self::assertStringStartsWith($datetimeString, $row[1]); } - public function testFetchArrayWithMissingTypes(): void - { - if ( - $this->connection->getDriver() instanceof MySQLiDriver || - $this->connection->getDriver() instanceof SQLSrvDriver - ) { - $this->markTestSkipped('mysqli and sqlsrv actually supports this'); - } - - $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); - - $this->connection->fetchArray($sql, [1, $datetime]); - } - public function testFetchColumn(): void { $sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?'; @@ -415,24 +419,6 @@ public function testFetchColumnWithTypes(): void self::assertStringStartsWith($datetimeString, $column); } - public function testFetchColumnWithMissingTypes(): void - { - if ( - $this->connection->getDriver() instanceof MySQLiDriver || - $this->connection->getDriver() instanceof SQLSrvDriver - ) { - $this->markTestSkipped('mysqli and sqlsrv actually supports this'); - } - - $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); - - $this->connection->fetchColumn($sql, [1, $datetime], 1); - } - /** * @group DDC-697 */ diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/ConnectionTest.php new file mode 100644 index 00000000000..1ffc5a7b9bb --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/ConnectionTest.php @@ -0,0 +1,55 @@ +markTestSkipped('ibm_db2 is not installed.'); + } + + parent::setUp(); + + if ($this->connection->getDriver() instanceof DB2Driver) { + return; + } + + $this->markTestSkipped('ibm_db2 only test.'); + } + + protected function tearDown(): void + { + $this->resetSharedConn(); + } + + public function testConnectionFailure(): void + { + $this->expectException(ConnectionFailed::class); + new DB2Connection(['dbname' => 'garbage'], '', ''); + } + + public function testPrepareFailure(): void + { + $driverConnection = $this->connection->getWrappedConnection(); + + $re = new ReflectionProperty($driverConnection, 'conn'); + $re->setAccessible(true); + $conn = $re->getValue($driverConnection); + db2_close($conn); + + $this->expectException(PrepareFailed::class); + $driverConnection->prepare('SELECT 1'); + } +} diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php index ab25cfbf252..2d504933060 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/IBMDB2/DB2StatementTest.php @@ -5,10 +5,15 @@ namespace Doctrine\Tests\DBAL\Functional\Driver\IBMDB2; use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver\IBMDB2\Exception\StatementError; use Doctrine\Tests\DbalFunctionalTestCase; use function extension_loaded; +use const E_ALL; +use const E_NOTICE; +use const E_WARNING; + class DB2StatementTest extends DbalFunctionalTestCase { protected function setUp(): void @@ -28,12 +33,14 @@ protected function setUp(): void public function testExecutionErrorsAreNotSuppressed(): void { - $stmt = $this->connection->prepare('SELECT * FROM SYSIBM.SYSDUMMY1 WHERE \'foo\' = ?'); + $driverConnection = $this->connection->getWrappedConnection(); + + $stmt = $driverConnection->prepare('SELECT * FROM SYSIBM.SYSDUMMY1 WHERE \'foo\' = ?'); - // unwrap the statement to prevent the wrapper from handling the PHPUnit-originated exception - $wrappedStmt = $stmt->getWrappedStatement(); + // prevent the PHPUnit error handler from handling the errors that db2_execute() may trigger + $this->iniSet('error_reporting', (string) (E_ALL & ~E_WARNING & ~E_NOTICE)); - $this->expectNotice(); - $wrappedStmt->execute([[]]); + $this->expectException(StatementError::class); + $stmt->execute([[]]); } }