Skip to content

Commit

Permalink
Merge pull request #3369 from morozov/issues/3366
Browse files Browse the repository at this point in the history
Handle binding errors in OCI8Statement::execute() and MySQLiStatement::execute()
  • Loading branch information
Ocramius authored Dec 1, 2018
2 parents 0ef7d47 + fd81a77 commit 7764294
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 32 deletions.
4 changes: 4 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Upgrade to 3.0

## BC BREAK `Statement::execute()` with redundant parameters.

Similarly to the drivers based on `pdo_pgsql` and `pdo_sqlsrv`, `OCI8Statement::execute()` and `MySQLiStatement::execute()` do not longer ignore redundant parameters.

## BC BREAK: `Doctrine\DBAL\Types\Type::getDefaultLength()` removed

The `Doctrine\DBAL\Types\Type::getDefaultLength()` method has been removed as it served no purpose.
Expand Down
24 changes: 12 additions & 12 deletions lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class MysqliStatement implements IteratorAggregate, Statement
protected $_rowBindedValues;

/** @var mixed[] */
protected $_bindedValues;
protected $_bindedValues = [];

/** @var string */
protected $types;
Expand Down Expand Up @@ -138,18 +138,18 @@ public function bindValue($param, $value, $type = ParameterType::STRING)
*/
public function execute($params = null)
{
if ($this->_bindedValues !== null) {
if ($params !== null) {
if (! $this->_bindValues($params)) {
throw new MysqliException($this->_stmt->error, $this->_stmt->errno);
}
} else {
[$types, $values, $streams] = $this->separateBoundValues();
if (! $this->_stmt->bind_param($types, ...$values)) {
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
}
$this->sendLongData($streams);
if ($params !== null && count($params) > 0) {
if (! $this->_bindValues($params)) {
throw new MysqliException($this->_stmt->error, $this->_stmt->errno);
}
} else {
[$types, $values, $streams] = $this->separateBoundValues();

if (count($values) > 0 && ! $this->_stmt->bind_param($types, ...$values)) {
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
}

$this->sendLongData($streams);
}

if (! $this->_stmt->execute()) {
Expand Down
8 changes: 6 additions & 2 deletions lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,13 @@ public function execute($params = null)
$hasZeroIndex = array_key_exists(0, $params);
foreach ($params as $key => $val) {
if ($hasZeroIndex && is_numeric($key)) {
$this->bindValue($key + 1, $val);
$param = $key + 1;
} else {
$this->bindValue($key, $val);
$param = $key;
}

if (! $this->bindValue($param, $val)) {
throw OCI8Exception::fromErrorInfo($this->errorInfo());
}
}
}
Expand Down
27 changes: 9 additions & 18 deletions tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,15 @@ public function testExecute(array $params)
->disableOriginalConstructor()
->getMock();

$statement->expects($this->at(0))
->method('bindValue')
->with(
$this->equalTo(1),
$this->equalTo($params[0])
);
$statement->expects($this->at(1))
->method('bindValue')
->with(
$this->equalTo(2),
$this->equalTo($params[1])
);
$statement->expects($this->at(2))
->method('bindValue')
->with(
$this->equalTo(3),
$this->equalTo($params[2])
);
foreach ($params as $index => $value) {
$statement->expects($this->at($index))
->method('bindValue')
->with(
$this->equalTo($index + 1),
$this->equalTo($value)
)
->willReturn(true);
}

// can't pass to constructor since we don't have a real database handle,
// but execute must check the connection for the executeMode
Expand Down
38 changes: 38 additions & 0 deletions tests/Doctrine/Tests/DBAL/Functional/StatementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

namespace Doctrine\Tests\DBAL\Functional;

use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type;
use Doctrine\Tests\DbalFunctionalTestCase;
use function base64_decode;
use function sprintf;
use function stream_get_contents;

class StatementTest extends DbalFunctionalTestCase
Expand Down Expand Up @@ -292,4 +294,40 @@ public function testFetchInColumnMode() : void

self::assertEquals(1, $result);
}

public function testExecWithRedundantParameters() : void
{
$driver = $this->connection->getDriver()->getName();

switch ($driver) {
case 'pdo_mysql':
case 'pdo_oracle':
case 'pdo_sqlsrv':
self::markTestSkipped(sprintf(
'PDOStatement::execute() implemented in the "%s" driver does not report redundant parameters',
$driver
));

return;
case 'ibm_db2':
self::markTestSkipped('db2_execute() does not report redundant parameters');

return;
case 'sqlsrv':
self::markTestSkipped('sqlsrv_prepare() does not report redundant parameters');

return;
}

$platform = $this->connection->getDatabasePlatform();
$query = $platform->getDummySelectSQL();
$stmt = $this->connection->prepare($query);

// we want to make sure the exception is thrown by the DBAL code, not by PHPUnit due to a PHP-level error,
// but the wrapper connection wraps everything in a DBAL exception
$this->iniSet('error_reporting', 0);

self::expectException(DBALException::class);
$stmt->execute([null]);
}
}

0 comments on commit 7764294

Please sign in to comment.