Unverified Commit bcb17e0b authored by Sergei Morozov's avatar Sergei Morozov

Merge pull request #3417 from morozov/issues/3358

Statement::fetchColumn() will throw an exception in the case of invalid index
parents 068e9df8 7e8b1c3c
# Upgrade to 3.0 # Upgrade to 3.0
## MINOR BC BREAK `Statement::fetchColumn()` with an invalid index.
Similarly to `PDOStatement::fetchColumn()`, DBAL statements throw an exception in case of an invalid column index.
## BC BREAK `Statement::execute()` with redundant parameters. ## 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. Similarly to the drivers based on `pdo_pgsql` and `pdo_sqlsrv`, `OCI8Statement::execute()` and `MySQLiStatement::execute()` do not longer ignore redundant parameters.
......
...@@ -3,10 +3,12 @@ ...@@ -3,10 +3,12 @@
namespace Doctrine\DBAL\Cache; namespace Doctrine\DBAL\Cache;
use ArrayIterator; use ArrayIterator;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
use InvalidArgumentException; use InvalidArgumentException;
use IteratorAggregate; use IteratorAggregate;
use function array_key_exists;
use function array_merge; use function array_merge;
use function array_values; use function array_values;
use function count; use function count;
...@@ -130,7 +132,14 @@ class ArrayStatement implements IteratorAggregate, ResultStatement ...@@ -130,7 +132,14 @@ class ArrayStatement implements IteratorAggregate, ResultStatement
{ {
$row = $this->fetch(FetchMode::NUMERIC); $row = $this->fetch(FetchMode::NUMERIC);
// TODO: verify that return false is the correct behavior if ($row === false) {
return $row[$columnIndex] ?? false; return false;
}
if (! array_key_exists($columnIndex, $row)) {
throw DBALException::invalidColumnIndex($columnIndex, count($row));
}
return $row[$columnIndex];
} }
} }
...@@ -4,14 +4,17 @@ namespace Doctrine\DBAL\Cache; ...@@ -4,14 +4,17 @@ namespace Doctrine\DBAL\Cache;
use ArrayIterator; use ArrayIterator;
use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\Cache;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
use InvalidArgumentException; use InvalidArgumentException;
use IteratorAggregate; use IteratorAggregate;
use function array_key_exists;
use function array_merge; use function array_merge;
use function array_values; use function array_values;
use function assert; use function assert;
use function count;
use function reset; use function reset;
/** /**
...@@ -187,8 +190,15 @@ class ResultCacheStatement implements IteratorAggregate, ResultStatement ...@@ -187,8 +190,15 @@ class ResultCacheStatement implements IteratorAggregate, ResultStatement
{ {
$row = $this->fetch(FetchMode::NUMERIC); $row = $this->fetch(FetchMode::NUMERIC);
// TODO: verify that return false is the correct behavior if ($row === false) {
return $row[$columnIndex] ?? false; return false;
}
if (! array_key_exists($columnIndex, $row)) {
throw DBALException::invalidColumnIndex($columnIndex, count($row));
}
return $row[$columnIndex];
} }
/** /**
......
...@@ -291,4 +291,14 @@ class DBALException extends Exception ...@@ -291,4 +291,14 @@ class DBALException extends Exception
sprintf('Type of the class %s@%s is already registered.', get_class($type), spl_object_hash($type)) sprintf('Type of the class %s@%s is already registered.', get_class($type), spl_object_hash($type))
); );
} }
public static function invalidColumnIndex(int $index, int $count) : self
{
return new self(sprintf(
'Invalid column index %d. The statement result contains %d column%s.',
$index,
$count,
$count === 1 ? '' : 's'
));
}
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Doctrine\DBAL\Driver\IBMDB2; namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
...@@ -18,6 +19,7 @@ use const DB2_LONG; ...@@ -18,6 +19,7 @@ use const DB2_LONG;
use const DB2_PARAM_FILE; use const DB2_PARAM_FILE;
use const DB2_PARAM_IN; use const DB2_PARAM_IN;
use function array_change_key_case; use function array_change_key_case;
use function array_key_exists;
use function count; use function count;
use function db2_bind_param; use function db2_bind_param;
use function db2_execute; use function db2_execute;
...@@ -338,7 +340,11 @@ class DB2Statement implements IteratorAggregate, Statement ...@@ -338,7 +340,11 @@ class DB2Statement implements IteratorAggregate, Statement
return false; return false;
} }
return $row[$columnIndex] ?? null; if (! array_key_exists($columnIndex, $row)) {
throw DBALException::invalidColumnIndex($columnIndex, count($row));
}
return $row[$columnIndex];
} }
/** /**
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Doctrine\DBAL\Driver\Mysqli; namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\Exception\InvalidArgumentException; use Doctrine\DBAL\Exception\InvalidArgumentException;
...@@ -12,6 +13,7 @@ use mysqli; ...@@ -12,6 +13,7 @@ use mysqli;
use mysqli_stmt; use mysqli_stmt;
use function array_combine; use function array_combine;
use function array_fill; use function array_fill;
use function array_key_exists;
use function assert; use function assert;
use function count; use function count;
use function feof; use function feof;
...@@ -380,7 +382,11 @@ class MysqliStatement implements IteratorAggregate, Statement ...@@ -380,7 +382,11 @@ class MysqliStatement implements IteratorAggregate, Statement
return false; return false;
} }
return $row[$columnIndex] ?? null; if (! array_key_exists($columnIndex, $row)) {
throw DBALException::invalidColumnIndex($columnIndex, count($row));
}
return $row[$columnIndex];
} }
/** /**
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Doctrine\DBAL\Driver\OCI8; namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
...@@ -521,7 +522,11 @@ class OCI8Statement implements IteratorAggregate, Statement ...@@ -521,7 +522,11 @@ class OCI8Statement implements IteratorAggregate, Statement
return false; return false;
} }
return $row[$columnIndex] ?? null; if (! array_key_exists($columnIndex, $row)) {
throw DBALException::invalidColumnIndex($columnIndex, count($row));
}
return $row[$columnIndex];
} }
/** /**
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Doctrine\DBAL\Driver; namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\ParameterType;
use IteratorAggregate; use IteratorAggregate;
...@@ -193,7 +194,17 @@ class PDOStatement implements IteratorAggregate, Statement ...@@ -193,7 +194,17 @@ class PDOStatement implements IteratorAggregate, Statement
public function fetchColumn($columnIndex = 0) public function fetchColumn($columnIndex = 0)
{ {
try { try {
return $this->stmt->fetchColumn($columnIndex); $value = $this->stmt->fetchColumn($columnIndex);
if ($value === null) {
$columnCount = $this->columnCount();
if ($columnIndex < 0 || $columnIndex >= $columnCount) {
throw DBALException::invalidColumnIndex($columnIndex, $columnCount);
}
}
return $value;
} catch (\PDOException $exception) { } catch (\PDOException $exception) {
throw new PDOException($exception); throw new PDOException($exception);
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Doctrine\DBAL\Driver\SQLAnywhere; namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
...@@ -284,7 +285,9 @@ class SQLAnywhereStatement implements IteratorAggregate, Statement ...@@ -284,7 +285,9 @@ class SQLAnywhereStatement implements IteratorAggregate, Statement
return false; return false;
} }
return $row[$columnIndex] ?? null; if (! array_key_exists($columnIndex, $row)) {
throw DBALException::invalidColumnIndex($columnIndex, count($row));
}
} }
/** /**
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Doctrine\DBAL\Driver\SQLSrv; namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\StatementIterator; use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
...@@ -412,7 +413,11 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -412,7 +413,11 @@ class SQLSrvStatement implements IteratorAggregate, Statement
return false; return false;
} }
return $row[$columnIndex] ?? null; if (! array_key_exists($columnIndex, $row)) {
throw DBALException::invalidColumnIndex($columnIndex, count($row));
}
return $row[$columnIndex];
} }
/** /**
......
...@@ -62,3 +62,8 @@ parameters: ...@@ -62,3 +62,8 @@ parameters:
# https://github.com/doctrine/dbal/issues/3237 # https://github.com/doctrine/dbal/issues/3237
- '~^Call to an undefined method Doctrine\\DBAL\\Driver\\PDOStatement::nextRowset\(\)~' - '~^Call to an undefined method Doctrine\\DBAL\\Driver\\PDOStatement::nextRowset\(\)~'
# https://github.com/phpstan/phpstan/pull/1886
-
message: '~^Strict comparison using === between string|false and null will always evaluate to false\.~'
path: %currentWorkingDirectory%/lib/Doctrine/DBAL/Driver/PDOStatement.php
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Doctrine\Tests\DBAL\Functional; namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\DBALException; use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\PDOConnection;
use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOracleDriver; use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOracleDriver;
use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\FetchMode;
...@@ -357,4 +358,34 @@ EOF ...@@ -357,4 +358,34 @@ EOF
self::expectException(DBALException::class); self::expectException(DBALException::class);
$stmt->execute([null]); $stmt->execute([null]);
} }
/**
* @throws DBALException
*
* @dataProvider nonExistingIndexProvider
*/
public function testFetchColumnNonExistingIndex(int $index) : void
{
if ($this->connection->getWrappedConnection() instanceof PDOConnection) {
$this->markTestSkipped('PDO supports this behavior natively but throws a different exception');
}
$platform = $this->connection->getDatabasePlatform();
$query = $platform->getDummySelectSQL();
$stmt = $this->connection->query($query);
self::expectException(DBALException::class);
$stmt->fetchColumn($index);
}
/**
* @return mixed[][]
*/
public static function nonExistingIndexProvider() : iterable
{
return [
[1],
[-1],
];
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment