Skip to content

Commit 7764294

Browse files
authored
Merge pull request #3369 from morozov/issues/3366
Handle binding errors in OCI8Statement::execute() and MySQLiStatement::execute()
2 parents 0ef7d47 + fd81a77 commit 7764294

File tree

5 files changed

+69
-32
lines changed

5 files changed

+69
-32
lines changed

UPGRADE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Upgrade to 3.0
22

3+
## BC BREAK `Statement::execute()` with redundant parameters.
4+
5+
Similarly to the drivers based on `pdo_pgsql` and `pdo_sqlsrv`, `OCI8Statement::execute()` and `MySQLiStatement::execute()` do not longer ignore redundant parameters.
6+
37
## BC BREAK: `Doctrine\DBAL\Types\Type::getDefaultLength()` removed
48

59
The `Doctrine\DBAL\Types\Type::getDefaultLength()` method has been removed as it served no purpose.

lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class MysqliStatement implements IteratorAggregate, Statement
4646
protected $_rowBindedValues;
4747

4848
/** @var mixed[] */
49-
protected $_bindedValues;
49+
protected $_bindedValues = [];
5050

5151
/** @var string */
5252
protected $types;
@@ -138,18 +138,18 @@ public function bindValue($param, $value, $type = ParameterType::STRING)
138138
*/
139139
public function execute($params = null)
140140
{
141-
if ($this->_bindedValues !== null) {
142-
if ($params !== null) {
143-
if (! $this->_bindValues($params)) {
144-
throw new MysqliException($this->_stmt->error, $this->_stmt->errno);
145-
}
146-
} else {
147-
[$types, $values, $streams] = $this->separateBoundValues();
148-
if (! $this->_stmt->bind_param($types, ...$values)) {
149-
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
150-
}
151-
$this->sendLongData($streams);
141+
if ($params !== null && count($params) > 0) {
142+
if (! $this->_bindValues($params)) {
143+
throw new MysqliException($this->_stmt->error, $this->_stmt->errno);
152144
}
145+
} else {
146+
[$types, $values, $streams] = $this->separateBoundValues();
147+
148+
if (count($values) > 0 && ! $this->_stmt->bind_param($types, ...$values)) {
149+
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
150+
}
151+
152+
$this->sendLongData($streams);
153153
}
154154

155155
if (! $this->_stmt->execute()) {

lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,9 +359,13 @@ public function execute($params = null)
359359
$hasZeroIndex = array_key_exists(0, $params);
360360
foreach ($params as $key => $val) {
361361
if ($hasZeroIndex && is_numeric($key)) {
362-
$this->bindValue($key + 1, $val);
362+
$param = $key + 1;
363363
} else {
364-
$this->bindValue($key, $val);
364+
$param = $key;
365+
}
366+
367+
if (! $this->bindValue($param, $val)) {
368+
throw OCI8Exception::fromErrorInfo($this->errorInfo());
365369
}
366370
}
367371
}

tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,15 @@ public function testExecute(array $params)
4141
->disableOriginalConstructor()
4242
->getMock();
4343

44-
$statement->expects($this->at(0))
45-
->method('bindValue')
46-
->with(
47-
$this->equalTo(1),
48-
$this->equalTo($params[0])
49-
);
50-
$statement->expects($this->at(1))
51-
->method('bindValue')
52-
->with(
53-
$this->equalTo(2),
54-
$this->equalTo($params[1])
55-
);
56-
$statement->expects($this->at(2))
57-
->method('bindValue')
58-
->with(
59-
$this->equalTo(3),
60-
$this->equalTo($params[2])
61-
);
44+
foreach ($params as $index => $value) {
45+
$statement->expects($this->at($index))
46+
->method('bindValue')
47+
->with(
48+
$this->equalTo($index + 1),
49+
$this->equalTo($value)
50+
)
51+
->willReturn(true);
52+
}
6253

6354
// can't pass to constructor since we don't have a real database handle,
6455
// but execute must check the connection for the executeMode

tests/Doctrine/Tests/DBAL/Functional/StatementTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
namespace Doctrine\Tests\DBAL\Functional;
44

5+
use Doctrine\DBAL\DBALException;
56
use Doctrine\DBAL\Driver\Statement;
67
use Doctrine\DBAL\FetchMode;
78
use Doctrine\DBAL\ParameterType;
89
use Doctrine\DBAL\Schema\Table;
910
use Doctrine\DBAL\Types\Type;
1011
use Doctrine\Tests\DbalFunctionalTestCase;
1112
use function base64_decode;
13+
use function sprintf;
1214
use function stream_get_contents;
1315

1416
class StatementTest extends DbalFunctionalTestCase
@@ -292,4 +294,40 @@ public function testFetchInColumnMode() : void
292294

293295
self::assertEquals(1, $result);
294296
}
297+
298+
public function testExecWithRedundantParameters() : void
299+
{
300+
$driver = $this->connection->getDriver()->getName();
301+
302+
switch ($driver) {
303+
case 'pdo_mysql':
304+
case 'pdo_oracle':
305+
case 'pdo_sqlsrv':
306+
self::markTestSkipped(sprintf(
307+
'PDOStatement::execute() implemented in the "%s" driver does not report redundant parameters',
308+
$driver
309+
));
310+
311+
return;
312+
case 'ibm_db2':
313+
self::markTestSkipped('db2_execute() does not report redundant parameters');
314+
315+
return;
316+
case 'sqlsrv':
317+
self::markTestSkipped('sqlsrv_prepare() does not report redundant parameters');
318+
319+
return;
320+
}
321+
322+
$platform = $this->connection->getDatabasePlatform();
323+
$query = $platform->getDummySelectSQL();
324+
$stmt = $this->connection->prepare($query);
325+
326+
// we want to make sure the exception is thrown by the DBAL code, not by PHPUnit due to a PHP-level error,
327+
// but the wrapper connection wraps everything in a DBAL exception
328+
$this->iniSet('error_reporting', 0);
329+
330+
self::expectException(DBALException::class);
331+
$stmt->execute([null]);
332+
}
295333
}

0 commit comments

Comments
 (0)