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

PdoDriver: fix pdo connection in exception mode (target >=4.1.0) #293

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
32 changes: 22 additions & 10 deletions src/Dibi/Drivers/PdoDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
101 changes: 101 additions & 0 deletions tests/dibi/PdoDriver.providedConnection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php
/**
* @dataProvider ../databases.ini != nothing, pdo
*/

// Context:
// When PDO connection is passed into Dibi it can be in (re)configured in various ways.
// This affects how connection is then internally handled.
// There should be no visible difference in Dibi behaviour regardless of PDO configuration.
//
// There are two cases that needs to be tested:
// 1. When query succeeds -> 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)));