diff --git a/src/Dibi/Drivers/PdoDriver.php b/src/Dibi/Drivers/PdoDriver.php index 66321c153..d5ab5ba90 100644 --- a/src/Dibi/Drivers/PdoDriver.php +++ b/src/Dibi/Drivers/PdoDriver.php @@ -89,31 +89,43 @@ public function disconnect(): void */ public function query(string $sql): ?Dibi\ResultDriver { - $res = $this->connection->query($sql); - if ($res) { - $this->affectedRows = $res->rowCount(); - return $res->columnCount() ? $this->createResultDriver($res) : null; + try { + if ($res = @$this->connection->query($sql)) { // intentionally @ to catch warnings in warning PDO mode + $this->affectedRows = $res->rowCount(); + return $res->columnCount() ? $this->createResultDriver($res) : null; + } + + } catch (\PDOException $pdoException) { } $this->affectedRows = null; + throw $this->createException( + isset($pdoException) ? $pdoException->errorInfo : $this->connection->errorInfo(), + $sql + ); + } + + + private function createException(array $errorInfo, string $sql): Dibi\DriverException + { + [$sqlState, $code, $message] = $errorInfo; - [$sqlState, $code, $message] = $this->connection->errorInfo(); $message = "SQLSTATE[$sqlState]: $message"; switch ($this->driverName) { case 'mysql': - throw MySqliDriver::createException($message, $code, $sql); + return MySqliDriver::createException($message, $code, $sql); case 'oci': - throw OracleDriver::createException($message, $code, $sql); + return OracleDriver::createException($message, $code, $sql); case 'pgsql': - throw PostgreDriver::createException($message, $sqlState, $sql); + return PostgreDriver::createException($message, $sqlState, $sql); case 'sqlite': - throw SqliteDriver::createException($message, $code, $sql); + return SqliteDriver::createException($message, $code, $sql); default: - throw new Dibi\DriverException($message, $code, $sql); + return new Dibi\DriverException($message, $code, $sql); } } diff --git a/tests/dibi/PdoDriver.providedConnection.phpt b/tests/dibi/PdoDriver.providedConnection.phpt new file mode 100644 index 000000000..20d161063 --- /dev/null +++ b/tests/dibi/PdoDriver.providedConnection.phpt @@ -0,0 +1,101 @@ + result returned +// 2. When query fails for various reasons -> proper exception should be generated + + +declare(strict_types=1); + +use Tester\Assert; + +require __DIR__ . '/bootstrap.php'; + + +function buildPDOConnection(int $errorMode = null): PDO +{ + global $config; + + // used to parse config, establish connection + $connection = new \Dibi\Connection($config); + $dibiDriver = $connection->getDriver(); + \assert($dibiDriver instanceof \Dibi\Drivers\PdoDriver); + + // hack: extract PDO connection from driver (no public interface for that) + $connectionProperty = (new ReflectionClass($dibiDriver)) + ->getProperty('connection'); + $connectionProperty->setAccessible(true); + $pdo = $connectionProperty->getValue($dibiDriver); + \assert($pdo instanceof PDO); + + // check that error reporting is in PHPs default value + \assert($pdo->getAttribute(\PDO::ATTR_ERRMODE) === \PDO::ERRMODE_SILENT); + + // override PDO error mode if provided + if ($errorMode !== null) { + $pdo->setAttribute(\PDO::ATTR_ERRMODE, $errorMode); + } + return $pdo; +} + + +function buildDibiConnection(PDO $pdo): \Dibi\Connection +{ + $conn = new \Dibi\Connection(['resource' => $pdo, 'driver' => 'pdo']); + \assert($conn->getDriver() instanceof \Dibi\Drivers\PdoDriver); + return $conn; +} + + +$runTests = function (\Dibi\Connection $connection) use ($config) { + $connection->loadFile(__DIR__ . "/data/$config[system].sql"); + if ($config['system'] === 'sqlite') { // @see issue #301 + $connection->query('PRAGMA foreign_keys=true'); + } + + // successful SELECT + test(function () use ($connection) { + $result = $connection->query('SELECT `product_id`, `title` FROM `products` WHERE `product_id` = 1')->fetch(); + Assert::equal(['product_id' => 1, 'title' => 'Chair'], $result->toArray()); + }); + + // Non-existing table: General exception should be generated + Assert::exception(function () use ($connection) { + $connection->query('SELECT * FROM `nonexisting`'); + }, \Dibi\DriverException::class); + + // Duplicated INSERT: UniqueConstraintViolationException + Assert::exception(function () use ($connection) { + $connection->query("INSERT INTO `products` (`product_id`, `title`) VALUES (1, 'Chair')"); + }, \Dibi\UniqueConstraintViolationException::class); + + // INSERT with NULL: NotNullConstraintViolationException + Assert::exception(function () use ($connection) { + $connection->query('INSERT INTO `products` (`title`) VALUES (NULL)'); + }, \Dibi\NotNullConstraintViolationException::class); + + // INSERT with NULL: ForeignKeyConstraintViolationException + Assert::exception(function () use ($connection) { + $connection->query('INSERT INTO `orders` (`customer_id`, `product_id`, `amount`) VALUES (99999 /*non-existing*/, 1, 7)'); + }, \Dibi\ForeignKeyConstraintViolationException::class); +}; + +// PDO error mode: exception +$runTests(buildDibiConnection(buildPDOConnection(\PDO::ERRMODE_EXCEPTION))); + +// PDO error mode: warning +$runTests(buildDibiConnection(buildPDOConnection(\PDO::ERRMODE_WARNING))); + +// PDO error mode: explicitly set silent +$runTests(buildDibiConnection(buildPDOConnection(\PDO::ERRMODE_SILENT))); + +// PDO error mode: implicitly set silent +$runTests(buildDibiConnection(buildPDOConnection(null)));