Extract Result from the Statement interface

parent 3d8c5756
# Upgrade to 3.0
## BC BREAK changes in fetching statement results
1. The `Statement` interface no longer extends `ResultStatement`.
2. The `ResultStatement` interface has been renamed to `Result`.
3. Instead of returning `bool`, `Statement::execute()` now returns a `Result` that should be used for fetching the result data and metadata.
4. The functionality previously available via `Statement::closeCursor()` is now available via `Result::free()`. The behavior of fetching data from a freed result is no longer portable. In this case, some drivers will return `false` while others may throw an exception.
Additional related changes:
1. The `ArrayStatement` and `ResultCacheStatement` classes from the `Cache` package have been renamed to `ArrayResult` and `CachingResult` respectively and marked `@internal`.
## BC BREAK `Statement::rowCount()` is moved.
`Statement::rowCount()` has been moved to the `ResultStatement` interface where it belongs by definition.
......
......@@ -97,7 +97,7 @@
<!-- some statement classes close cursor using an empty while-loop -->
<rule ref="Generic.CodeAnalysis.EmptyStatement.DetectedWhile">
<exclude-pattern>src/Driver/SQLSrv/SQLSrvStatement.php</exclude-pattern>
<exclude-pattern>src/Driver/SQLSrv/Result.php</exclude-pattern>
</rule>
<!-- see https://github.com/doctrine/dbal/issues/3377 -->
......
......@@ -58,7 +58,9 @@ parameters:
- '~unknown class OCI-(Lob|Collection)~'
# https://github.com/JetBrains/phpstorm-stubs/pull/766
- '~^Method Doctrine\\DBAL\\Driver\\Mysqli\\MysqliStatement::_fetch\(\) never returns null so it can be removed from the return typehint\.$~'
-
message: '~^Strict comparison using === between true and null will always evaluate to false\.$~'
path: %currentWorkingDirectory%/src/Driver/Mysqli/Result.php
# The ReflectionException in the case when the class does not exist is acceptable and does not need to be handled
- '~^Parameter #1 \$argument of class ReflectionClass constructor expects class-string<T of object>\|T of object, string given\.$~'
......
......@@ -4,16 +4,15 @@ namespace Doctrine\DBAL\Cache;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\ResultStatement;
use function array_values;
use function count;
use function reset;
/**
* @deprecated
* @internal The class is internal to the caching layer implementation.
*/
class ArrayStatement implements ResultStatement, Result
final class ArrayResult implements Result
{
/** @var mixed[] */
private $data;
......@@ -37,35 +36,6 @@ class ArrayStatement implements ResultStatement, Result
$this->columnCount = count($data[0]);
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return $this->columnCount;
}
public function rowCount(): int
{
if ($this->data === null) {
return 0;
}
return count($this->data);
}
/**
* {@inheritdoc}
*/
......@@ -126,6 +96,20 @@ class ArrayStatement implements ResultStatement, Result
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
if ($this->data === null) {
return 0;
}
return count($this->data);
}
public function columnCount(): int
{
return $this->columnCount;
}
public function free(): void
{
$this->data = [];
......
......@@ -6,14 +6,11 @@ use Doctrine\Common\Cache\Cache;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\ResultStatement;
use function array_map;
use function array_values;
/**
* Cache statement for SQL results.
*
* A result is saved in multiple cache keys, there is the originally specified
* cache key which is just pointing to result rows by key. The following things
* have to be ensured:
......@@ -24,12 +21,12 @@ use function array_values;
* Also you have to realize that the cache will load the whole result into memory at once to ensure 2.
* This means that the memory usage for cached results might increase by using this feature.
*
* @deprecated
* @internal The class is internal to the caching layer implementation.
*/
class ResultCacheStatement implements ResultStatement, Result
class CachingResult implements Result
{
/** @var Cache */
private $resultCache;
private $cache;
/** @var string */
private $cacheKey;
......@@ -40,8 +37,8 @@ class ResultCacheStatement implements ResultStatement, Result
/** @var int */
private $lifetime;
/** @var ResultStatement */
private $statement;
/** @var Result */
private $result;
/** @var array<int,array<string,mixed>>|null */
private $data;
......@@ -51,40 +48,15 @@ class ResultCacheStatement implements ResultStatement, Result
* @param string $realKey
* @param int $lifetime
*/
public function __construct(ResultStatement $stmt, Cache $resultCache, $cacheKey, $realKey, $lifetime)
public function __construct(Result $result, Cache $cache, $cacheKey, $realKey, $lifetime)
{
$this->statement = $stmt;
$this->resultCache = $resultCache;
$this->result = $result;
$this->cache = $cache;
$this->cacheKey = $cacheKey;
$this->realKey = $realKey;
$this->lifetime = $lifetime;
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return $this->statement->columnCount();
}
public function rowCount(): int
{
return $this->statement->rowCount();
}
/**
* {@inheritdoc}
*/
......@@ -121,7 +93,7 @@ class ResultCacheStatement implements ResultStatement, Result
public function fetchAllNumeric(): array
{
$this->store(
$this->statement->fetchAllAssociative()
$this->result->fetchAllAssociative()
);
return array_map('array_values', $this->data);
......@@ -133,7 +105,7 @@ class ResultCacheStatement implements ResultStatement, Result
public function fetchAllAssociative(): array
{
$this->store(
$this->statement->fetchAllAssociative()
$this->result->fetchAllAssociative()
);
return $this->data;
......@@ -147,6 +119,16 @@ class ResultCacheStatement implements ResultStatement, Result
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
return $this->result->rowCount();
}
public function columnCount(): int
{
return $this->result->columnCount();
}
public function free(): void
{
$this->data = null;
......@@ -163,7 +145,7 @@ class ResultCacheStatement implements ResultStatement, Result
$this->data = [];
}
$row = $this->statement->fetchAssociative();
$row = $this->result->fetchAssociative();
if ($row !== false) {
$this->data[] = $row;
......@@ -192,7 +174,7 @@ class ResultCacheStatement implements ResultStatement, Result
return;
}
$data = $this->resultCache->fetch($this->cacheKey);
$data = $this->cache->fetch($this->cacheKey);
if ($data === false) {
$data = [];
......@@ -200,6 +182,6 @@ class ResultCacheStatement implements ResultStatement, Result
$data[$this->realKey] = $this->data;
$this->resultCache->save($this->cacheKey, $data, $this->lifetime);
$this->cache->save($this->cacheKey, $data, $this->lifetime);
}
}
......@@ -4,13 +4,14 @@ namespace Doctrine\DBAL;
use Closure;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Cache\ArrayStatement;
use Doctrine\DBAL\Abstraction\Result as AbstractionResult;
use Doctrine\DBAL\Cache\ArrayResult;
use Doctrine\DBAL\Cache\CacheException;
use Doctrine\DBAL\Cache\CachingResult;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Cache\ResultCacheStatement;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\PingableConnection;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Exception\InvalidArgumentException;
......@@ -900,9 +901,9 @@ class Connection implements DriverConnection
public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable
{
try {
$stmt = $this->executeQuery($query, $params, $types);
$result = $this->executeQuery($query, $params, $types);
while (($row = $stmt->fetchNumeric()) !== false) {
while (($row = $result->fetchNumeric()) !== false) {
yield $row;
}
} catch (Throwable $e) {
......@@ -924,9 +925,9 @@ class Connection implements DriverConnection
public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable
{
try {
$stmt = $this->executeQuery($query, $params, $types);
$result = $this->executeQuery($query, $params, $types);
while (($row = $stmt->fetchAssociative()) !== false) {
while (($row = $result->fetchAssociative()) !== false) {
yield $row;
}
} catch (Throwable $e) {
......@@ -948,9 +949,9 @@ class Connection implements DriverConnection
public function iterateColumn(string $query, array $params = [], array $types = []): Traversable
{
try {
$stmt = $this->executeQuery($query, $params, $types);
$result = $this->executeQuery($query, $params, $types);
while (($value = $stmt->fetchOne()) !== false) {
while (($value = $result->fetchOne()) !== false) {
yield $value;
}
} catch (Throwable $e) {
......@@ -985,12 +986,14 @@ class Connection implements DriverConnection
* @param int[]|string[] $types The types the previous parameters are in.
* @param QueryCacheProfile|null $qcp The query cache profile, optional.
*
* @return ResultStatement The executed statement.
*
* @throws DBALException
*/
public function executeQuery(string $query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null): ResultStatement
{
public function executeQuery(
string $query,
array $params = [],
$types = [],
?QueryCacheProfile $qcp = null
): AbstractionResult {
if ($qcp !== null) {
return $this->executeCacheQuery($query, $params, $types, $qcp);
}
......@@ -1009,22 +1012,22 @@ class Connection implements DriverConnection
$stmt = $connection->prepare($query);
if (count($types) > 0) {
$this->_bindTypedValues($stmt, $params, $types);
$stmt->execute();
$result = $stmt->execute();
} else {
$stmt->execute($params);
$result = $stmt->execute($params);
}
} else {
$stmt = $connection->query($query);
$result = $connection->query($query);
}
return new Result($result, $this);
} catch (Throwable $ex) {
throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
}
} finally {
if ($logger !== null) {
$logger->stopQuery();
}
return $stmt;
}
}
/**
......@@ -1036,8 +1039,9 @@ class Connection implements DriverConnection
* @param QueryCacheProfile $qcp The query cache profile.
*
* @throws CacheException
* @throws DBALException
*/
public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp): ResultStatement
public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp): Result
{
$resultCache = $qcp->getResultCacheDriver() ?? $this->_config->getResultCacheImpl();
......@@ -1056,20 +1060,26 @@ class Connection implements DriverConnection
if ($data !== false) {
// is the real key part of this row pointers map or is the cache only pointing to other cache keys?
if (isset($data[$realKey])) {
$stmt = new ArrayStatement($data[$realKey]);
$result = new ArrayResult($data[$realKey]);
} elseif (array_key_exists($realKey, $data)) {
$stmt = new ArrayStatement([]);
$result = new ArrayResult([]);
}
}
if (! isset($stmt)) {
$stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
if (! isset($result)) {
$result = new CachingResult(
$this->executeQuery($query, $params, $types),
$resultCache,
$cacheKey,
$realKey,
$qcp->getLifetime()
);
}
return $stmt;
return new Result($result, $this);
}
public function query(string $sql): ResultStatement
public function query(string $sql): DriverResult
{
$connection = $this->getWrappedConnection();
......@@ -1079,16 +1089,14 @@ class Connection implements DriverConnection
}
try {
$statement = $connection->query($sql);
return $connection->query($sql);
} catch (Throwable $ex) {
throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $sql);
}
} finally {
if ($logger !== null) {
$logger->stopQuery();
}
return $statement;
}
}
/**
......@@ -1120,24 +1128,23 @@ class Connection implements DriverConnection
if (count($types) > 0) {
$this->_bindTypedValues($stmt, $params, $types);
$stmt->execute();
$result = $stmt->execute();
} else {
$stmt->execute($params);
$result = $stmt->execute($params);
}
$result = $stmt->rowCount();
} else {
$result = $connection->exec($query);
return $result->rowCount();
}
return $connection->exec($query);
} catch (Throwable $ex) {
throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $query, $this->resolveParams($params, $types));
}
} finally {
if ($logger !== null) {
$logger->stopQuery();
}
return $result;
}
}
public function exec(string $statement): int
......@@ -1150,16 +1157,14 @@ class Connection implements DriverConnection
}
try {
$result = $connection->exec($statement);
return $connection->exec($statement);
} catch (Throwable $ex) {
throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
}
} finally {
if ($logger !== null) {
$logger->stopQuery();
}
return $result;
}
}
/**
......@@ -1644,7 +1649,7 @@ class Connection implements DriverConnection
* copy/paste the code as this method may change, or be removed without prior notice.
*
* @param mixed[] $params
* @param int[]|string[] $types
* @param array<int|string|null> $types
*
* @return mixed[]
*/
......
......@@ -7,7 +7,7 @@ use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;
......@@ -341,7 +341,7 @@ class MasterSlaveConnection extends Connection
parent::rollbackSavepoint($savepoint);
}
public function query(string $sql): ResultStatement
public function query(string $sql): Result
{
$this->connect('master');
assert($this->_conn instanceof DriverConnection);
......
......@@ -23,7 +23,7 @@ interface Connection
*
* @throws DBALException
*/
public function query(string $sql): ResultStatement;
public function query(string $sql): Result;
/**
* Quotes a string for use in a query.
......
......@@ -2,7 +2,7 @@
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
......@@ -86,12 +86,9 @@ class DB2Connection implements ServerInfoAwareConnection
return new DB2Statement($stmt);
}
public function query(string $sql): ResultStatement
public function query(string $sql): ResultInterface
{
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt;
return $this->prepare($sql)->execute();
}
/**
......
......@@ -2,19 +2,13 @@
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
use function assert;
use function db2_bind_param;
use function db2_execute;
use function db2_fetch_array;
use function db2_fetch_assoc;
use function db2_free_result;
use function db2_num_fields;
use function db2_num_rows;
use function db2_stmt_errormsg;
use function error_get_last;
use function fclose;
......@@ -32,7 +26,7 @@ use const DB2_LONG;
use const DB2_PARAM_FILE;
use const DB2_PARAM_IN;
class DB2Statement implements Statement, Result
class DB2Statement implements Statement
{
/** @var resource */
private $stmt;
......@@ -48,13 +42,6 @@ class DB2Statement implements Statement, Result
*/
private $lobs = [];
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* @param resource $stmt
*/
......@@ -122,40 +109,8 @@ class DB2Statement implements Statement, Result
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->bindParam = [];
if (! db2_free_result($this->stmt)) {
return false;
}
$this->result = false;
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
$count = db2_num_fields($this->stmt);
if ($count !== false) {
return $count;
}
return 0;
}
/**
* {@inheritdoc}
*/
public function execute($params = null)
public function execute($params = null): ResultInterface
{
if ($params === null) {
ksort($this->bindParam);
......@@ -177,7 +132,7 @@ class DB2Statement implements Statement, Result
$this->writeStringToStream($source, $target);
}
$retval = db2_execute($this->stmt, $params);
$result = db2_execute($this->stmt, $params);
foreach ($this->lobs as [, $handle]) {
fclose($handle);
......@@ -185,85 +140,11 @@ class DB2Statement implements Statement, Result
$this->lobs = [];
if ($retval === false) {
if ($result === false) {
throw new DB2Exception(db2_stmt_errormsg());
}
$this->result = true;
return $retval;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
if (! $this->result) {
return false;
}
return db2_fetch_array($this->stmt);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
return db2_fetch_assoc($this->stmt);
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
return @db2_num_rows($this->stmt);
}
public function free(): void
{
$this->bindParam = [];
db2_free_result($this->stmt);
$this->result = false;
return new Result($this->stmt);
}
/**
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\IBMDB2;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use function db2_fetch_array;
use function db2_fetch_assoc;
use function db2_free_result;
use function db2_num_fields;
use function db2_num_rows;
use function db2_stmt_error;
use function db2_stmt_errormsg;
final class Result implements ResultInterface
{
/** @var resource */
private $statement;
/**
* @param resource $statement
*/
public function __construct($statement)
{
$this->statement = $statement;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
$row = @db2_fetch_array($this->statement);
if ($row === false && db2_stmt_error($this->statement) !== '02000') {
throw new DB2Exception(db2_stmt_errormsg($this->statement));
}
return $row;
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
$row = @db2_fetch_assoc($this->statement);
if ($row === false && db2_stmt_error($this->statement) !== '02000') {
throw new DB2Exception(db2_stmt_errormsg($this->statement));
}
return $row;
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
return @db2_num_rows($this->statement);
}
public function columnCount(): int
{
$count = db2_num_fields($this->statement);
if ($count !== false) {
return $count;
}
return 0;
}
public function free(): void
{
db2_free_result($this->statement);
}
}
......@@ -3,7 +3,7 @@
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\PingableConnection;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
......@@ -136,12 +136,9 @@ class MysqliConnection implements PingableConnection, ServerInfoAwareConnection
return new MysqliStatement($this->conn, $sql);
}
public function query(string $sql): ResultStatement
public function query(string $sql): ResultInterface
{
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt;
return $this->prepare($sql)->execute();
}
/**
......
......@@ -2,28 +2,25 @@
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\DBAL\ParameterType;
use mysqli;
use mysqli_stmt;
use function array_combine;
use function array_fill;
use function assert;
use function count;
use function feof;
use function fread;
use function get_resource_type;
use function is_array;
use function is_int;
use function is_resource;
use function sprintf;
use function str_repeat;
class MysqliStatement implements Statement, Result
class MysqliStatement implements Statement
{
/** @var string[] */
protected static $_paramTypeMap = [
......@@ -41,12 +38,6 @@ class MysqliStatement implements Statement, Result
/** @var mysqli_stmt */
protected $_stmt;
/** @var string[]|false|null */
protected $_columnNames;
/** @var mixed[] */
protected $_rowBindedValues = [];
/** @var mixed[] */
protected $_bindedValues;
......@@ -60,13 +51,6 @@ class MysqliStatement implements Statement, Result
*/
protected $_values = [];
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* @param string $prepareString
*
......@@ -131,7 +115,7 @@ class MysqliStatement implements Statement, Result
/**
* {@inheritdoc}
*/
public function execute($params = null)
public function execute($params = null): ResultInterface
{
if ($this->_bindedValues !== null) {
if ($params !== null) {
......@@ -147,57 +131,7 @@ class MysqliStatement implements Statement, Result
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
}
if ($this->_columnNames === null) {
$meta = $this->_stmt->result_metadata();
if ($meta !== false) {
$fields = $meta->fetch_fields();
assert(is_array($fields));
$columnNames = [];
foreach ($fields as $col) {
$columnNames[] = $col->name;
}
$meta->free();
$this->_columnNames = $columnNames;
} else {
$this->_columnNames = false;
}
}
if ($this->_columnNames !== false) {
// Store result of every execution which has it. Otherwise it will be impossible
// to execute a new statement in case if the previous one has non-fetched rows
// @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html
$this->_stmt->store_result();
// Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql,
// it will have to allocate as much memory as it may be needed for the given column type
// (e.g. for a LONGBLOB field it's 4 gigabytes)
// @link https://bugs.php.net/bug.php?id=51386#1270673122
//
// Make sure that the values are bound after each execution. Otherwise, if closeCursor() has been
// previously called on the statement, the values are unbound making the statement unusable.
//
// It's also important that row values are bound after _each_ call to store_result(). Otherwise,
// if mysqli is compiled with libmysql, subsequently fetched string values will get truncated
// to the length of the ones fetched during the previous execution.
$this->_rowBindedValues = array_fill(0, count($this->_columnNames), null);
$refs = [];
foreach ($this->_rowBindedValues as $key => &$value) {
$refs[$key] =& $value;
}
if (! $this->_stmt->bind_result(...$refs)) {
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
}
}
$this->result = true;
return true;
return new Result($this->_stmt);
}
/**
......@@ -281,132 +215,4 @@ class MysqliStatement implements Statement, Result
return $this->_stmt->bind_param($types, ...$params);
}
/**
* @return mixed[]|false|null
*/
private function _fetch()
{
$ret = $this->_stmt->fetch();
if ($ret === true) {
$values = [];
foreach ($this->_rowBindedValues as $v) {
$values[] = $v;
}
return $values;
}
return $ret;
}
/**
* {@inheritdoc}
*/
public function fetchNumeric()
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
$values = $this->_fetch();
if ($values === null) {
return false;
}
if ($values === false) {
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
}
return $values;
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
$values = $this->fetchNumeric();
if ($values === false) {
return false;
}
assert(is_array($this->_columnNames));
$row = array_combine($this->_columnNames, $values);
assert(is_array($row));
return $row;
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
public function rowCount(): int
{
if ($this->_columnNames === false) {
return $this->_stmt->affected_rows;
}
return $this->_stmt->num_rows;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return $this->_stmt->field_count;
}
public function free(): void
{
$this->_stmt->free_result();
$this->result = false;
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use mysqli_stmt;
use stdClass;
use function array_combine;
use function array_fill;
use function array_map;
use function assert;
use function count;
use function is_array;
final class Result implements ResultInterface
{
/** @var mysqli_stmt */
private $statement;
/**
* Whether the statement result has columns. The property should be used only after the result metadata
* has been fetched ({@see $metadataFetched}). Otherwise, the property value is undetermined.
*
* @var bool
*/
private $hasColumns = false;
/**
* Mapping of statement result column indexes to their names. The property should be used only
* if the statement result has columns ({@see $hasColumns}). Otherwise, the property value is undetermined.
*
* @var array<int,string>
*/
private $columnNames = [];
/** @var mixed[] */
private $boundValues = [];
/**
* @throws MysqliException
*/
public function __construct(mysqli_stmt $statement)
{
$this->statement = $statement;
$meta = $statement->result_metadata();
if ($meta === false) {
return;
}
$this->hasColumns = true;
$fields = $meta->fetch_fields();
assert(is_array($fields));
$this->columnNames = array_map(static function (stdClass $field): string {
return $field->name;
}, $fields);
$meta->free();
// Store result of every execution which has it. Otherwise it will be impossible
// to execute a new statement in case if the previous one has non-fetched rows
// @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html
$this->statement->store_result();
// Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql,
// it will have to allocate as much memory as it may be needed for the given column type
// (e.g. for a LONGBLOB field it's 4 gigabytes)
// @link https://bugs.php.net/bug.php?id=51386#1270673122
//
// Make sure that the values are bound after each execution. Otherwise, if free() has been
// previously called on the result, the values are unbound making the statement unusable.
//
// It's also important that row values are bound after _each_ call to store_result(). Otherwise,
// if mysqli is compiled with libmysql, subsequently fetched string values will get truncated
// to the length of the ones fetched during the previous execution.
$this->boundValues = array_fill(0, count($this->columnNames), null);
$refs = [];
foreach ($this->boundValues as &$value) {
$refs[] =& $value;
}
if (! $this->statement->bind_result(...$refs)) {
throw new MysqliException($this->statement->error, $this->statement->sqlstate, $this->statement->errno);
}
}
/**
* {@inheritdoc}
*/
public function fetchNumeric()
{
$ret = $this->statement->fetch();
if ($ret === false) {
throw new MysqliException($this->statement->error, $this->statement->sqlstate, $this->statement->errno);
}
if ($ret === null) {
return false;
}
$values = [];
foreach ($this->boundValues as $v) {
$values[] = $v;
}
return $values;
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
$values = $this->fetchNumeric();
if ($values === false) {
return false;
}
$row = array_combine($this->columnNames, $values);
assert(is_array($row));
return $row;
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
if ($this->hasColumns) {
return $this->statement->num_rows;
}
return $this->statement->affected_rows;
}
public function columnCount(): int
{
return $this->statement->field_count;
}
public function free(): void
{
$this->statement->free_result();
}
}
......@@ -3,7 +3,7 @@
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\Connection;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
......@@ -107,12 +107,9 @@ class OCI8Connection implements Connection, ServerInfoAwareConnection
return new OCI8Statement($this->dbh, $sql, $this);
}
public function query(string $sql): ResultStatement
public function query(string $sql): ResultInterface
{
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt;
return $this->prepare($sql)->execute();
}
/**
......@@ -131,10 +128,7 @@ class OCI8Connection implements Connection, ServerInfoAwareConnection
public function exec(string $statement): int
{
$stmt = $this->prepare($statement);
$stmt->execute();
return $stmt->rowCount();
return $this->prepare($statement)->execute()->rowCount();
}
/**
......
......@@ -2,8 +2,7 @@
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
......@@ -13,29 +12,18 @@ use function implode;
use function is_int;
use function is_resource;
use function oci_bind_by_name;
use function oci_cancel;
use function oci_error;
use function oci_execute;
use function oci_fetch_all;
use function oci_fetch_array;
use function oci_new_descriptor;
use function oci_num_fields;
use function oci_num_rows;
use function oci_parse;
use function preg_match;
use function preg_quote;
use function sprintf;
use function substr;
use const OCI_ASSOC;
use const OCI_B_BIN;
use const OCI_B_BLOB;
use const OCI_D_LOB;
use const OCI_FETCHSTATEMENT_BY_COLUMN;
use const OCI_FETCHSTATEMENT_BY_ROW;
use const OCI_NUM;
use const OCI_RETURN_LOBS;
use const OCI_RETURN_NULLS;
use const OCI_TEMP_BLOB;
use const PREG_OFFSET_CAPTURE;
use const SQLT_CHR;
......@@ -43,7 +31,7 @@ use const SQLT_CHR;
/**
* The OCI8 implementation of the Statement interface.
*/
class OCI8Statement implements Statement, Result
class OCI8Statement implements Statement
{
/** @var resource */
protected $_dbh;
......@@ -73,13 +61,6 @@ class OCI8Statement implements Statement, Result
*/
private $boundValues = [];
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* Creates a new OCI8Statement that uses the given connection handle and SQL statement.
*
......@@ -312,34 +293,8 @@ class OCI8Statement implements Statement, Result
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
$count = oci_num_fields($this->_sth);
if ($count !== false) {
return $count;
}
return 0;
}
/**
* {@inheritdoc}
*/
public function execute($params = null)
public function execute($params = null): ResultInterface
{
if ($params !== null) {
foreach ($params as $key => $val) {
......@@ -356,118 +311,6 @@ class OCI8Statement implements Statement, Result
throw OCI8Exception::fromErrorInfo(oci_error($this->_sth));
}
$this->result = true;
return $ret;
}
public function rowCount(): int
{
$count = oci_num_rows($this->_sth);
if ($count !== false) {
return $count;
}
return 0;
}
/**
* {@inheritdoc}
*/
public function fetchNumeric()
{
return $this->fetch(OCI_NUM);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
return $this->fetch(OCI_ASSOC);
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
return $this->fetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW);
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
{
return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0];
}
public function free(): void
{
// not having the result means there's nothing to close
if (! $this->result) {
return;
}
oci_cancel($this->_sth);
$this->result = false;
}
/**
* @return mixed|false
*/
private function fetch(int $mode)
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if (! $this->result) {
return false;
}
return oci_fetch_array(
$this->_sth,
$mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS
);
}
/**
* @return array<mixed>
*/
private function fetchAll(int $mode, int $fetchStructure): array
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if (! $this->result) {
return [];
}
oci_fetch_all(
$this->_sth,
$result,
0,
-1,
$mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS
);
return $result;
return new Result($this->_sth);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\OCI8;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use function oci_cancel;
use function oci_fetch_all;
use function oci_fetch_array;
use function oci_num_fields;
use function oci_num_rows;
use const OCI_ASSOC;
use const OCI_FETCHSTATEMENT_BY_COLUMN;
use const OCI_FETCHSTATEMENT_BY_ROW;
use const OCI_NUM;
use const OCI_RETURN_LOBS;
use const OCI_RETURN_NULLS;
final class Result implements ResultInterface
{
/** @var resource */
private $statement;
/**
* @param resource $statement
*/
public function __construct($statement)
{
$this->statement = $statement;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return $this->fetch(OCI_NUM);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return $this->fetch(OCI_ASSOC);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_ROW);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return $this->fetchAll(OCI_ASSOC, OCI_FETCHSTATEMENT_BY_ROW);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return $this->fetchAll(OCI_NUM, OCI_FETCHSTATEMENT_BY_COLUMN)[0];
}
public function rowCount(): int
{
$count = oci_num_rows($this->statement);
if ($count !== false) {
return $count;
}
return 0;
}
public function columnCount(): int
{
$count = oci_num_fields($this->statement);
if ($count !== false) {
return $count;
}
return 0;
}
public function free(): void
{
oci_cancel($this->statement);
}
/**
* @return mixed|false
*/
private function fetch(int $mode)
{
return oci_fetch_array(
$this->statement,
$mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS
);
}
/**
* @return array<mixed>
*/
private function fetchAll(int $mode, int $fetchStructure): array
{
oci_fetch_all(
$this->statement,
$result,
0,
-1,
$mode | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS
);
return $result;
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Driver\PDOException;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use PDO;
use PDOStatement;
use function assert;
use function is_array;
final class Result implements ResultInterface
{
/** @var PDOStatement */
private $statement;
public function __construct(PDOStatement $statement)
{
$this->statement = $statement;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return $this->fetch(PDO::FETCH_NUM);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return $this->fetch(PDO::FETCH_ASSOC);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return $this->fetch(PDO::FETCH_COLUMN);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return $this->fetchAll(PDO::FETCH_NUM);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return $this->fetchAll(PDO::FETCH_ASSOC);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return $this->fetchAll(PDO::FETCH_COLUMN);
}
public function rowCount(): int
{
try {
return $this->statement->rowCount();
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function columnCount(): int
{
try {
return $this->statement->columnCount();
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function free(): void
{
try {
$this->statement->closeCursor();
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
/**
* @return mixed|false
*
* @throws PDOException
*/
private function fetch(int $mode)
{
try {
return $this->statement->fetch($mode);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
/**
* @return array<int,mixed>
*
* @throws PDOException
*/
private function fetchAll(int $mode): array
{
try {
$data = $this->statement->fetchAll($mode);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
assert(is_array($data));
return $data;
}
}
......@@ -2,6 +2,8 @@
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\PDO\Result;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\ParameterType;
use PDO;
......@@ -63,13 +65,13 @@ class PDOConnection implements ServerInfoAwareConnection
}
}
public function query(string $sql): ResultStatement
public function query(string $sql): ResultInterface
{
try {
$stmt = $this->connection->query($sql);
assert($stmt instanceof \PDOStatement);
return $this->createStatement($stmt);
return new Result($stmt);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
......
......@@ -23,10 +23,9 @@ class Connection extends PDOConnection
return parent::lastInsertId($name);
}
$stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?');
$stmt->execute([$name]);
return $stmt->fetchOne();
return $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?')
->execute([$name])
->fetchOne();
}
/**
......
......@@ -2,14 +2,14 @@
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\PDO\Result;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\ParameterType;
use InvalidArgumentException;
use PDO;
use function array_slice;
use function assert;
use function func_get_args;
use function is_array;
/**
* The PDO implementation of the Statement interface.
......@@ -71,120 +71,15 @@ class PDOStatement implements Statement
/**
* {@inheritdoc}
*/
public function closeCursor()
public function execute($params = null): ResultInterface
{
try {
return $this->stmt->closeCursor();
} catch (\PDOException $exception) {
// Exceptions not allowed by the interface.
// In case driver implementations do not adhere to the interface, silence exceptions here.
return true;
}
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return $this->stmt->columnCount();
}
/**
* {@inheritdoc}
*/
public function execute($params = null)
{
try {
return $this->stmt->execute($params);
$this->stmt->execute($params);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function rowCount(): int
{
return $this->stmt->rowCount();
}
/**
* {@inheritdoc}
*/
public function fetchNumeric()
{
return $this->fetch(PDO::FETCH_NUM);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
return $this->fetch(PDO::FETCH_ASSOC);
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
return $this->fetch(PDO::FETCH_COLUMN);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
return $this->fetchAll(PDO::FETCH_NUM);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
return $this->fetchAll(PDO::FETCH_ASSOC);
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
{
return $this->fetchAll(PDO::FETCH_COLUMN);
}
/**
* @return mixed|false
*
* @throws PDOException
*/
private function fetch(int $mode)
{
try {
return $this->stmt->fetch($mode);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
/**
* @return array<int,mixed>
*
* @throws PDOException
*/
private function fetchAll(int $mode): array
{
try {
$data = $this->stmt->fetchAll($mode);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
assert(is_array($data));
return $data;
return new Result($this->stmt);
}
/**
......
......@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
/**
* Driver-level result statement execution result.
* Driver-level statement execution result.
*/
interface Result
{
......@@ -72,7 +72,7 @@ interface Result
*
* @return int The number of rows.
*/
public function rowCount();
public function rowCount(): int;
/**
* Returns the number of columns in the result
......@@ -80,16 +80,7 @@ interface Result
* @return int The number of columns in the result. If the columns cannot be counted,
* this method must return 0.
*/
public function columnCount();
/**
* Closes the cursor, enabling the statement to be executed again.
*
* @deprecated Use Result::free() instead.
*
* @return bool TRUE on success or FALSE on failure.
*/
public function closeCursor();
public function columnCount(): int;
/**
* Discards the non-fetched portion of the result, enabling the originating statement to be executed again.
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver;
/**
* Interface for the reading part of a prepare statement only.
*/
interface ResultStatement
{
/**
* Closes the cursor, enabling the statement to be executed again.
*
* @deprecated Use Result::free() instead.
*
* @return bool TRUE on success or FALSE on failure.
*/
public function closeCursor();
/**
* Returns the number of columns in the result set
*
* @return int The number of columns in the result set represented
* by the statement. If there is no result set,
* this method should return 0.
*/
public function columnCount();
/**
* Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement
* executed by the corresponding object.
*
* If the last SQL statement executed by the associated Statement object was a SELECT statement,
* some databases may return the number of rows returned by that statement. However,
* this behaviour is not guaranteed for all databases and should not be
* relied on for portable applications.
*/
public function rowCount(): int;
/**
* Returns the next row of a result set as a numeric array or FALSE if there are no more rows.
*
* @return array<int,mixed>|false
*
* @throws DriverException
*/
public function fetchNumeric();
/**
* Returns the next row of a result set as an associative array or FALSE if there are no more rows.
*
* @return array<string,mixed>|false
*
* @throws DriverException
*/
public function fetchAssociative();
/**
* Returns the first value of the next row of a result set or FALSE if there are no more rows.
*
* @return mixed|false
*
* @throws DriverException
*/
public function fetchOne();
/**
* Returns an array containing all of the result set rows represented as numeric arrays.
*
* @return array<int,array<int,mixed>>
*
* @throws DriverException
*/
public function fetchAllNumeric(): array;
/**
* Returns an array containing all of the result set rows represented as associative arrays.
*
* @return array<int,array<string,mixed>>
*
* @throws DriverException
*/
public function fetchAllAssociative(): array;
/**
* Returns an array containing the values of the first column of the result set.
*
* @return array<int,mixed>
*
* @throws DriverException
*/
public function fetchFirstColumn(): array;
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use function sasql_fetch_assoc;
use function sasql_fetch_row;
use function sasql_stmt_affected_rows;
use function sasql_stmt_field_count;
use function sasql_stmt_reset;
use function sasql_stmt_result_metadata;
final class Result implements ResultInterface
{
/** @var resource */
private $statement;
/** @var resource */
private $result;
/**
* @param resource $statement
*/
public function __construct($statement)
{
$this->statement = $statement;
$this->result = sasql_stmt_result_metadata($statement);
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return sasql_fetch_row($this->result);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return sasql_fetch_assoc($this->result);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
return sasql_stmt_affected_rows($this->statement);
}
public function columnCount(): int
{
return sasql_stmt_field_count($this->statement);
}
public function free(): void
{
sasql_stmt_reset($this->statement);
}
}
......@@ -2,7 +2,7 @@
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
......@@ -124,12 +124,9 @@ class SQLAnywhereConnection implements ServerInfoAwareConnection
return new SQLAnywhereStatement($this->connection, $sql);
}
public function query(string $sql): ResultStatement
public function query(string $sql): ResultInterface
{
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt;
return $this->prepare($sql)->execute();
}
/**
......
......@@ -2,9 +2,7 @@
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
......@@ -12,27 +10,15 @@ use function array_key_exists;
use function assert;
use function is_int;
use function is_resource;
use function sasql_fetch_assoc;
use function sasql_fetch_row;
use function sasql_prepare;
use function sasql_stmt_affected_rows;
use function sasql_stmt_bind_param_ex;
use function sasql_stmt_execute;
use function sasql_stmt_field_count;
use function sasql_stmt_reset;
use function sasql_stmt_result_metadata;
/**
* SAP SQL Anywhere implementation of the Statement interface.
*/
class SQLAnywhereStatement implements Statement, Result
class SQLAnywhereStatement implements Statement
{
/** @var resource The connection resource. */
private $conn;
/** @var resource|null The result set resource to fetch. */
private $result;
/** @var resource The prepared SQL statement to execute. */
private $stmt;
......@@ -112,29 +98,7 @@ class SQLAnywhereStatement implements Statement, Result
*
* @throws SQLAnywhereException
*/
public function closeCursor()
{
if (! sasql_stmt_reset($this->stmt)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
}
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return sasql_stmt_field_count($this->stmt);
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function execute($params = null)
public function execute($params = null): ResultInterface
{
if ($params !== null) {
$hasZeroIndex = array_key_exists(0, $params);
......@@ -152,80 +116,7 @@ class SQLAnywhereStatement implements Statement, Result
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
}
$this->result = sasql_stmt_result_metadata($this->stmt);
return true;
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function fetchNumeric()
{
if (! is_resource($this->result)) {
return false;
}
return sasql_fetch_row($this->result);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
if (! is_resource($this->result)) {
return false;
}
return sasql_fetch_assoc($this->result);
}
/**
* {@inheritdoc}
*
* @throws DriverException
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* @return array<int,array<int,mixed>>
*
* @throws DriverException
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* @return array<int,array<string,mixed>>
*
* @throws DriverException
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* @return array<int,mixed>
*
* @throws DriverException
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
return sasql_stmt_affected_rows($this->stmt);
return new Result($this->stmt);
}
public function free(): void
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use function sqlsrv_fetch;
use function sqlsrv_fetch_array;
use function sqlsrv_num_fields;
use function sqlsrv_rows_affected;
use const SQLSRV_FETCH_ASSOC;
use const SQLSRV_FETCH_NUMERIC;
final class Result implements ResultInterface
{
/** @var resource */
private $statement;
/**
* @param resource $stmt
*/
public function __construct($stmt)
{
$this->statement = $stmt;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return $this->fetch(SQLSRV_FETCH_NUMERIC);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return $this->fetch(SQLSRV_FETCH_ASSOC);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
if ($this->statement === null) {
return 0;
}
$count = sqlsrv_rows_affected($this->statement);
if ($count !== false) {
return $count;
}
return 0;
}
public function columnCount(): int
{
if ($this->statement === null) {
return 0;
}
$count = sqlsrv_num_fields($this->statement);
if ($count !== false) {
return $count;
}
return 0;
}
public function free(): void
{
// emulate it by fetching and discarding rows, similarly to what PDO does in this case
// @link http://php.net/manual/en/pdostatement.closecursor.php
// @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
// deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
while (sqlsrv_fetch($this->statement)) {
}
}
/**
* @return mixed|false
*/
private function fetch(int $fetchType)
{
return sqlsrv_fetch_array($this->statement, $fetchType) ?? false;
}
}
......@@ -2,7 +2,7 @@
namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
......@@ -76,12 +76,9 @@ class SQLSrvConnection implements ServerInfoAwareConnection
return new SQLSrvStatement($this->conn, $sql, $this->lastInsertId);
}
public function query(string $sql): ResultStatement
public function query(string $sql): ResultInterface
{
$stmt = $this->prepare($sql);
$stmt->execute();
return $stmt;
return $this->prepare($sql)->execute();
}
/**
......@@ -123,13 +120,13 @@ class SQLSrvConnection implements ServerInfoAwareConnection
public function lastInsertId($name = null)
{
if ($name !== null) {
$stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?');
$stmt->execute([$name]);
$result = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?')
->execute([$name]);
} else {
$stmt = $this->query('SELECT @@IDENTITY');
$result = $this->query('SELECT @@IDENTITY');
}
return $stmt->fetchOne();
return $result->fetchOne();
}
/**
......
......@@ -2,8 +2,7 @@
namespace Doctrine\DBAL\Driver\SQLSrv;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
......@@ -11,26 +10,21 @@ use function is_int;
use function is_numeric;
use function sqlsrv_execute;
use function sqlsrv_fetch;
use function sqlsrv_fetch_array;
use function sqlsrv_get_field;
use function sqlsrv_next_result;
use function sqlsrv_num_fields;
use function SQLSRV_PHPTYPE_STREAM;
use function SQLSRV_PHPTYPE_STRING;
use function sqlsrv_prepare;
use function sqlsrv_rows_affected;
use function SQLSRV_SQLTYPE_VARBINARY;
use function stripos;
use const SQLSRV_ENC_BINARY;
use const SQLSRV_FETCH_ASSOC;
use const SQLSRV_FETCH_NUMERIC;
use const SQLSRV_PARAM_IN;
/**
* SQL Server Statement.
*/
final class SQLSrvStatement implements Statement, Result
final class SQLSrvStatement implements Statement
{
/**
* The SQLSRV Resource.
......@@ -74,13 +68,6 @@ final class SQLSrvStatement implements Statement, Result
*/
private $lastInsertId;
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* Append to any INSERT query to retrieve the last insert id.
*
......@@ -140,40 +127,10 @@ final class SQLSrvStatement implements Statement, Result
return true;
}
/**
* {@inheritdoc}
*
* @deprecated Use free() instead.
*/
public function closeCursor()
{
$this->free();
return true;
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
if ($this->stmt === null) {
return 0;
}
$count = sqlsrv_num_fields($this->stmt);
if ($count !== false) {
return $count;
}
return 0;
}
/**
* {@inheritdoc}
*/
public function execute($params = null)
public function execute($params = null): ResultInterface
{
if ($params !== null) {
foreach ($params as $key => $val) {
......@@ -199,9 +156,7 @@ final class SQLSrvStatement implements Statement, Result
$this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
}
$this->result = true;
return true;
return new Result($this->stmt);
}
/**
......@@ -248,98 +203,4 @@ final class SQLSrvStatement implements Statement, Result
return $stmt;
}
/**
* {@inheritdoc}
*/
public function fetchNumeric()
{
return $this->fetch(SQLSRV_FETCH_NUMERIC);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
return $this->fetch(SQLSRV_FETCH_ASSOC);
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
if ($this->stmt === null) {
return 0;
}
$count = sqlsrv_rows_affected($this->stmt);
if ($count !== false) {
return $count;
}
return 0;
}
public function free(): void
{
// not having the result means there's nothing to close
if ($this->stmt === null || ! $this->result) {
return;
}
// emulate it by fetching and discarding rows, similarly to what PDO does in this case
// @link http://php.net/manual/en/pdostatement.closecursor.php
// @link https://github.com/php/php-src/blob/php-7.0.11/ext/pdo/pdo_stmt.c#L2075
// deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
while (sqlsrv_fetch($this->stmt)) {
}
$this->result = false;
}
/**
* @return mixed|false
*/
private function fetch(int $fetchType)
{
// do not try fetching from the statement if it's not expected to contain the result
// in order to prevent exceptional situation
if ($this->stmt === null || ! $this->result) {
return false;
}
return sqlsrv_fetch_array($this->stmt, $fetchType) ?? false;
}
}
......@@ -5,12 +5,9 @@ namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\ParameterType;
/**
* Statement interface.
* Drivers must implement this interface.
*
* This resembles (a subset of) the PDOStatement interface.
* Driver-level statement
*/
interface Statement extends ResultStatement
interface Statement
{
/**
* Binds a value to a corresponding named (not supported by mysqli driver, see comment below) or positional
......@@ -69,7 +66,7 @@ interface Statement extends ResultStatement
* @param mixed[]|null $params A numeric array of values with as many elements as there are
* bound parameters in the SQL statement being executed.
*
* @return bool TRUE on success or FALSE on failure.
* @throws DriverException
*/
public function execute($params = null);
public function execute($params = null): Result;
}
......@@ -2,11 +2,13 @@
namespace Doctrine\DBAL\Portability;
use Doctrine\DBAL\Abstraction\Result as AbstractionResult;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\ColumnCase;
use Doctrine\DBAL\Driver\PDOConnection;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Result as DBALResult;
use PDO;
use const CASE_LOWER;
......@@ -86,9 +88,11 @@ class Connection extends \Doctrine\DBAL\Connection
/**
* {@inheritdoc}
*/
public function executeQuery(string $query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null): ResultStatement
public function executeQuery(string $query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null): AbstractionResult
{
return new Statement(parent::executeQuery($query, $params, $types, $qcp), $this->converter);
return $this->wrapResult(
parent::executeQuery($query, $params, $types, $qcp)
);
}
public function prepare(string $sql): DriverStatement
......@@ -96,8 +100,18 @@ class Connection extends \Doctrine\DBAL\Connection
return new Statement(parent::prepare($sql), $this->converter);
}
public function query(string $sql): ResultStatement
public function query(string $sql): DriverResult
{
return $this->wrapResult(
parent::query($sql)
);
}
private function wrapResult(DriverResult $result): AbstractionResult
{
return new Statement(parent::query($sql), $this->converter);
return new DBALResult(
new Result($result, $this->converter),
$this
);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Portability;
use Doctrine\DBAL\Driver\Result as ResultInterface;
final class Result implements ResultInterface
{
/** @var ResultInterface */
private $result;
/** @var Converter */
private $converter;
public function __construct(ResultInterface $result, Converter $converter)
{
$this->result = $result;
$this->converter = $converter;
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return $this->converter->convertNumeric(
$this->result->fetchNumeric()
);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return $this->converter->convertAssociative(
$this->result->fetchAssociative()
);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return $this->converter->convertOne(
$this->result->fetchOne()
);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return $this->converter->convertAllNumeric(
$this->result->fetchAllNumeric()
);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return $this->converter->convertAllAssociative(
$this->result->fetchAllAssociative()
);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return $this->converter->convertFirstColumn(
$this->result->fetchFirstColumn()
);
}
public function rowCount(): int
{
return $this->result->rowCount();
}
public function columnCount(): int
{
return $this->result->columnCount();
}
public function free(): void
{
$this->result->free();
}
}
......@@ -2,18 +2,16 @@
namespace Doctrine\DBAL\Portability;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use function assert;
/**
* Portability wrapper for a Statement.
*/
class Statement implements DriverStatement
{
/** @var DriverStatement|ResultStatement */
/** @var DriverStatement */
private $stmt;
/** @var Converter */
......@@ -21,10 +19,8 @@ class Statement implements DriverStatement
/**
* Wraps <tt>Statement</tt> and applies portability measures.
*
* @param DriverStatement|ResultStatement $stmt
*/
public function __construct($stmt, Converter $converter)
public function __construct(DriverStatement $stmt, Converter $converter)
{
$this->stmt = $stmt;
$this->converter = $converter;
......@@ -35,8 +31,6 @@ class Statement implements DriverStatement
*/
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
{
assert($this->stmt instanceof DriverStatement);
return $this->stmt->bindParam($column, $variable, $type, $length);
}
......@@ -45,101 +39,17 @@ class Statement implements DriverStatement
*/
public function bindValue($param, $value, $type = ParameterType::STRING)
{
assert($this->stmt instanceof DriverStatement);
return $this->stmt->bindValue($param, $value, $type);
}
/**
* {@inheritdoc}
*/
public function closeCursor()
{
return $this->stmt->closeCursor();
}
/**
* {@inheritdoc}
*/
public function columnCount()
{
return $this->stmt->columnCount();
}
/**
* {@inheritdoc}
*/
public function execute($params = null)
{
assert($this->stmt instanceof DriverStatement);
return $this->stmt->execute($params);
}
public function rowCount(): int
{
assert($this->stmt instanceof DriverStatement);
return $this->stmt->rowCount();
}
/**
* {@inheritdoc}
*/
public function fetchNumeric()
{
return $this->converter->convertNumeric(
$this->stmt->fetchNumeric()
);
}
/**
* {@inheritdoc}
*/
public function fetchAssociative()
{
return $this->converter->convertAssociative(
$this->stmt->fetchAssociative()
);
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
return $this->converter->convertOne(
$this->stmt->fetchOne()
);
}
/**
* {@inheritdoc}
*/
public function fetchAllNumeric(): array
{
return $this->converter->convertAllNumeric(
$this->stmt->fetchAllNumeric()
);
}
/**
* {@inheritdoc}
*/
public function fetchAllAssociative(): array
{
return $this->converter->convertAllAssociative(
$this->stmt->fetchAllAssociative()
);
}
/**
* {@inheritdoc}
*/
public function fetchFirstColumn(): array
public function execute($params = null): ResultInterface
{
return $this->converter->convertFirstColumn(
$this->stmt->fetchFirstColumn()
return new Result(
$this->stmt->execute($params),
$this->converter
);
}
}
......@@ -3,7 +3,7 @@
namespace Doctrine\DBAL\Query;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
......@@ -200,7 +200,7 @@ class QueryBuilder
* Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
* for insert, update and delete statements.
*
* @return ResultStatement|int
* @return Result|int
*/
public function execute()
{
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL;
use Doctrine\DBAL\Abstraction\Result as ResultInterface;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Traversable;
final class Result implements ResultInterface
{
/** @var DriverResult */
private $result;
/** @var Connection */
private $connection;
public function __construct(DriverResult $result, Connection $connection)
{
$this->result = $result;
$this->connection = $connection;
}
/**
* {@inheritDoc}
*
* @throws DBALException
*/
public function fetchNumeric()
{
try {
return $this->result->fetchNumeric();
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
/**
* {@inheritDoc}
*
* @throws DBALException
*/
public function fetchAssociative()
{
try {
return $this->result->fetchAssociative();
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
try {
return $this->result->fetchOne();
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
/**
* {@inheritDoc}
*
* @throws DBALException
*/
public function fetchAllNumeric(): array
{
try {
return $this->result->fetchAllNumeric();
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
/**
* {@inheritDoc}
*
* @throws DBALException
*/
public function fetchAllAssociative(): array
{
try {
return $this->result->fetchAllAssociative();
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
/**
* {@inheritDoc}
*
* @throws DBALException
*/
public function fetchFirstColumn(): array
{
try {
return $this->result->fetchFirstColumn();
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
/**
* @return Traversable<int,array<int,mixed>>
*
* @throws DBALException
*/
public function iterateNumeric(): Traversable
{
try {
while (($row = $this->result->fetchNumeric()) !== false) {
yield $row;
}
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
/**
* @return Traversable<int,array<string,mixed>>
*
* @throws DBALException
*/
public function iterateAssociative(): Traversable
{
try {
while (($row = $this->result->fetchAssociative()) !== false) {
yield $row;
}
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
/**
* @return Traversable<int,mixed>
*
* @throws DBALException
*/
public function iterateColumn(): Traversable
{
try {
while (($value = $this->result->fetchOne()) !== false) {
yield $value;
}
} catch (DriverException $e) {
throw DBALException::driverException($this->connection->getDriver(), $e);
}
}
public function rowCount(): int
{
return $this->result->rowCount();
}
public function columnCount(): int
{
return $this->result->columnCount();
}
public function free(): void
{
$this->result->free();
}
}
......@@ -2,13 +2,11 @@
namespace Doctrine\DBAL;
use Doctrine\DBAL\Abstraction\Result;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Throwable;
use Traversable;
use function is_string;
......@@ -16,7 +14,7 @@ use function is_string;
* A thin wrapper around a Doctrine\DBAL\Driver\Statement that adds support
* for logging, DBAL mapping types, etc.
*/
class Statement implements DriverStatement, Result
class Statement implements DriverStatement
{
/**
* The SQL statement.
......@@ -42,7 +40,7 @@ class Statement implements DriverStatement, Result
/**
* The underlying driver statement.
*
* @var \Doctrine\DBAL\Driver\Statement
* @var DriverStatement
*/
protected $stmt;
......@@ -136,11 +134,9 @@ class Statement implements DriverStatement, Result
*
* @param mixed[]|null $params
*
* @return bool TRUE on success, FALSE on failure.
*
* @throws DBALException
*/
public function execute($params = null)
public function execute($params = null): DriverResult
{
if ($params !== null) {
$this->params = $params;
......@@ -152,213 +148,31 @@ class Statement implements DriverStatement, Result
}
try {
$stmt = $this->stmt->execute($params);
return new Result(
$this->stmt->execute($params),
$this->conn
);
} catch (Throwable $ex) {
if ($logger !== null) {
$logger->stopQuery();
}
throw DBALException::driverExceptionDuringQuery(
$this->conn->getDriver(),
$ex,
$this->sql,
$this->conn->resolveParams($this->params, $this->types)
);
}
} finally {
if ($logger !== null) {
$logger->stopQuery();
}
$this->params = [];
$this->types = [];
return $stmt;
}
/**
* Closes the cursor, freeing the database resources used by this statement.
*
* @deprecated Use Result::free() instead.
*
* @return bool TRUE on success, FALSE on failure.
*/
public function closeCursor()
{
return $this->stmt->closeCursor();
}
/**
* Returns the number of columns in the result set.
*
* @return int
*/
public function columnCount()
{
return $this->stmt->columnCount();
}
/**
* {@inheritdoc}
*
* @throws DBALException
*/
public function fetchNumeric()
{
try {
return $this->stmt->fetchNumeric();
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* {@inheritdoc}
*
* @throws DBALException
*/
public function fetchAssociative()
{
try {
return $this->stmt->fetchAssociative();
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* {@inheritdoc}
*/
public function fetchOne()
{
try {
return $this->stmt->fetchOne();
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* {@inheritdoc}
*
* @throws DBALException
*/
public function fetchAllNumeric(): array
{
try {
return $this->stmt->fetchAllNumeric();
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* {@inheritdoc}
*
* @throws DBALException
*/
public function fetchAllAssociative(): array
{
try {
return $this->stmt->fetchAllAssociative();
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* {@inheritdoc}
*
* @throws DBALException
*/
public function fetchFirstColumn(): array
{
try {
return $this->stmt->fetchFirstColumn();
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* {@inheritDoc}
*
* @return Traversable<int,array<int,mixed>>
*
* @throws DBALException
*/
public function iterateNumeric(): Traversable
{
try {
while (($row = $this->stmt->fetchNumeric()) !== false) {
yield $row;
}
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* {@inheritDoc}
*
* @return Traversable<int,array<string,mixed>>
*
* @throws DBALException
*/
public function iterateAssociative(): Traversable
{
try {
while (($row = $this->stmt->fetchAssociative()) !== false) {
yield $row;
}
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* {@inheritDoc}
*
* @return Traversable<int,mixed>
*
* @throws DBALException
*/
public function iterateColumn(): Traversable
{
try {
while (($value = $this->stmt->fetchOne()) !== false) {
yield $value;
}
} catch (DriverException $e) {
throw DBALException::driverException($this->conn->getDriver(), $e);
}
}
/**
* Returns the number of rows affected by the last execution of this statement.
*
* @return int The number of affected rows.
*/
public function rowCount(): int
{
return $this->stmt->rowCount();
}
public function free(): void
{
if ($this->stmt instanceof Result) {
$this->stmt->free();
return;
}
$this->stmt->closeCursor();
}
/**
* Gets the wrapped driver statement.
*
* @return \Doctrine\DBAL\Driver\Statement
* @return DriverStatement
*/
public function getWrappedStatement()
{
......
......@@ -15,8 +15,6 @@ final class LoggingTest extends TestCase
public function testLogExecuteQuery(): void
{
$driverConnection = $this->createStub(DriverConnection::class);
$driverConnection->method('query')
->willReturn($this->createStub(Statement::class));
$this->createConnection($driverConnection, 'SELECT * FROM table')
->executeQuery('SELECT * FROM table');
......
......@@ -4,7 +4,7 @@ namespace Doctrine\DBAL\Tests;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Cache\ArrayStatement;
use Doctrine\DBAL\Abstraction\Result;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection;
......@@ -13,7 +13,6 @@ use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Exception\InvalidArgumentException;
......@@ -545,140 +544,100 @@ class ConnectionTest extends TestCase
);
}
public function testFetchAssociative(): void
{
$statement = 'SELECT * FROM foo WHERE bar = ?';
$params = [666];
$types = [ParameterType::INTEGER];
$result = [];
$driverMock = $this->createMock(Driver::class);
$driverMock->expects(self::any())
->method('connect')
->will(self::returnValue(
$this->createMock(DriverConnection::class)
));
$driverStatementMock = $this->createMock(Statement::class);
$driverStatementMock->expects(self::once())
->method('fetchAssociative')
->will(self::returnValue($result));
$conn = $this->getMockBuilder(Connection::class)
->onlyMethods(['executeQuery'])
->setConstructorArgs([[], $driverMock])
->getMock();
$conn->expects(self::once())
->method('executeQuery')
->with($statement, $params, $types)
->will(self::returnValue($driverStatementMock));
self::assertSame($result, $conn->fetchAssociative($statement, $params, $types));
}
public function testFetchNumeric(): void
{
$statement = 'SELECT * FROM foo WHERE bar = ?';
$params = [666];
$types = [ParameterType::INTEGER];
$result = [];
$driverMock = $this->createMock(Driver::class);
$driverMock->expects(self::any())
->method('connect')
->will(self::returnValue(
$this->createMock(DriverConnection::class)
));
$driverStatementMock = $this->createMock(Statement::class);
$driverStatementMock->expects(self::once())
->method('fetchNumeric')
->will(self::returnValue($result));
$conn = $this->getMockBuilder(Connection::class)
->onlyMethods(['executeQuery'])
->setConstructorArgs([[], $driverMock])
->getMock();
$conn->expects(self::once())
->method('executeQuery')
->with($statement, $params, $types)
->will(self::returnValue($driverStatementMock));
self::assertSame($result, $conn->fetchNumeric($statement, $params, $types));
}
public function testFetchOne(): void
/**
* @param mixed $expected
*
* @dataProvider fetchModeProvider
*/
public function testFetch(string $method, callable $invoke, $expected): void
{
$statement = 'SELECT * FROM foo WHERE bar = ?';
$query = 'SELECT * FROM foo WHERE bar = ?';
$params = [666];
$types = [ParameterType::INTEGER];
$result = [];
$driverMock = $this->createMock(Driver::class);
$driverMock->expects(self::any())
->method('connect')
->will(self::returnValue(
$this->createMock(DriverConnection::class)
));
$driverStatementMock = $this->createMock(Statement::class);
$result = $this->createMock(Result::class);
$result->expects(self::once())
->method($method)
->willReturn($expected);
$driverStatementMock->expects(self::once())
->method('fetchOne')
->will(self::returnValue($result));
$driver = $this->createConfiguredMock(Driver::class, [
'connect' => $this->createMock(DriverConnection::class),
]);
$conn = $this->getMockBuilder(Connection::class)
->onlyMethods(['executeQuery'])
->setConstructorArgs([[], $driverMock])
->setConstructorArgs([[], $driver])
->getMock();
$conn->expects(self::once())
->method('executeQuery')
->with($statement, $params, $types)
->will(self::returnValue($driverStatementMock));
->with($query, $params, $types)
->willReturn($result);
self::assertSame($result, $conn->fetchOne($statement, $params, $types));
self::assertSame($expected, $invoke($conn, $query, $params, $types));
}
public function testFetchAllAssociative(): void
/**
* @return iterable<string,array<int,mixed>>
*/
public static function fetchModeProvider(): iterable
{
$statement = 'SELECT * FROM foo WHERE bar = ?';
$params = [666];
$types = [ParameterType::INTEGER];
$result = [];
$driverMock = $this->createMock(Driver::class);
$driverMock->expects(self::any())
->method('connect')
->will(self::returnValue(
$this->createMock(DriverConnection::class)
));
yield 'numeric' => [
'fetchNumeric',
static function (Connection $connection, string $query, array $params, array $types) {
return $connection->fetchNumeric($query, $params, $types);
},
['bar'],
];
$driverStatementMock = $this->createMock(Statement::class);
yield 'associative' => [
'fetchAssociative',
static function (Connection $connection, string $query, array $params, array $types) {
return $connection->fetchAssociative($query, $params, $types);
},
['foo' => 'bar'],
];
$driverStatementMock->expects(self::once())
->method('fetchAllAssociative')
->will(self::returnValue($result));
yield 'one' => [
'fetchOne',
static function (Connection $connection, string $query, array $params, array $types) {
return $connection->fetchOne($query, $params, $types);
},
'bar',
];
$conn = $this->getMockBuilder(Connection::class)
->onlyMethods(['executeQuery'])
->setConstructorArgs([[], $driverMock])
->getMock();
yield 'all-numeric' => [
'fetchAllNumeric',
static function (Connection $connection, string $query, array $params, array $types) {
return $connection->fetchAllNumeric($query, $params, $types);
},
[
['bar'],
['baz'],
],
];
$conn->expects(self::once())
->method('executeQuery')
->with($statement, $params, $types)
->will(self::returnValue($driverStatementMock));
yield 'all-associative' => [
'fetchAllAssociative',
static function (Connection $connection, string $query, array $params, array $types) {
return $connection->fetchAllAssociative($query, $params, $types);
},
[
['foo' => 'bar'],
['foo' => 'baz'],
],
];
self::assertSame($result, $conn->fetchAllAssociative($statement, $params, $types));
yield 'first-column' => [
'fetchFirstColumn',
static function (Connection $connection, string $query, array $params, array $types) {
return $connection->fetchFirstColumn($query, $params, $types);
},
[
'bar',
'baz',
],
];
}
public function testCallingDeleteWithNoDeletionCriteriaResultsInInvalidArgumentException(): void
......@@ -766,10 +725,7 @@ class ConnectionTest extends TestCase
$driver = $this->createMock(Driver::class);
self::assertInstanceOf(
ArrayStatement::class,
(new Connection($this->params, $driver))->executeCacheQuery($query, $params, $types, $queryCacheProfileMock)
);
(new Connection($this->params, $driver))->executeCacheQuery($query, $params, $types, $queryCacheProfileMock);
}
/**
......
......@@ -5,7 +5,7 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MySQL57Platform;
......@@ -26,9 +26,9 @@ class AbstractMySQLDriverTest extends AbstractDriverTest
'password' => 'bar',
];
$statement = $this->createMock(ResultStatement::class);
$result = $this->createMock(Result::class);
$statement->expects(self::once())
$result->expects(self::once())
->method('fetchOne')
->will(self::returnValue($database));
......@@ -40,7 +40,7 @@ class AbstractMySQLDriverTest extends AbstractDriverTest
$connection->expects(self::once())
->method('query')
->will(self::returnValue($statement));
->will(self::returnValue($result));
self::assertSame($database, $this->driver->getDatabase($connection));
}
......
......@@ -5,7 +5,7 @@ namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
......@@ -24,9 +24,9 @@ class AbstractPostgreSQLDriverTest extends AbstractDriverTest
'password' => 'bar',
];
$statement = $this->createMock(ResultStatement::class);
$result = $this->createMock(Result::class);
$statement->expects(self::once())
$result->expects(self::once())
->method('fetchOne')
->will(self::returnValue($database));
......@@ -38,7 +38,7 @@ class AbstractPostgreSQLDriverTest extends AbstractDriverTest
$connection->expects(self::once())
->method('query')
->will(self::returnValue($statement));
->will(self::returnValue($result));
self::assertSame($database, $this->driver->getDatabase($connection));
}
......
......@@ -58,9 +58,9 @@ class DataAccessTest extends FunctionalTestCase
$stmt->bindValue(1, 1);
$stmt->bindValue(2, 'foo');
$stmt->execute();
$row = $stmt->fetchAssociative();
$row = $stmt->execute()->fetchAssociative();
self::assertIsArray($row);
$row = array_change_key_case($row, CASE_LOWER);
self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
......@@ -77,9 +77,9 @@ class DataAccessTest extends FunctionalTestCase
$stmt->bindParam(1, $paramInt);
$stmt->bindParam(2, $paramStr);
$stmt->execute();
$row = $stmt->fetchAssociative();
$row = $stmt->execute()->fetchAssociative();
self::assertIsArray($row);
$row = array_change_key_case($row, CASE_LOWER);
self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
......@@ -96,9 +96,8 @@ class DataAccessTest extends FunctionalTestCase
$stmt->bindParam(1, $paramInt);
$stmt->bindParam(2, $paramStr);
$stmt->execute();
$rows = $stmt->fetchAllAssociative();
$rows = $stmt->execute()->fetchAllAssociative();
$rows[0] = array_change_key_case($rows[0], CASE_LOWER);
self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]);
}
......@@ -114,34 +113,11 @@ class DataAccessTest extends FunctionalTestCase
$stmt->bindParam(1, $paramInt);
$stmt->bindParam(2, $paramStr);
$stmt->execute();
$column = $stmt->fetchOne();
$column = $stmt->execute()->fetchOne();
self::assertEquals(1, $column);
}
public function testPrepareWithIterator(): void
{
$paramInt = 1;
$paramStr = 'foo';
$sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
$stmt = $this->connection->prepare($sql);
self::assertInstanceOf(Statement::class, $stmt);
$stmt->bindParam(1, $paramInt);
$stmt->bindParam(2, $paramStr);
$stmt->execute();
$rows = [];
foreach ($stmt->iterateAssociative() as $row) {
$rows[] = array_change_key_case($row, CASE_LOWER);
}
self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $rows[0]);
}
public function testPrepareWithQuoted(): void
{
$table = 'fetch_table';
......@@ -165,9 +141,9 @@ class DataAccessTest extends FunctionalTestCase
$sql = 'SELECT test_int, test_string FROM fetch_table WHERE test_int = ? AND test_string = ?';
$stmt = $this->connection->prepare($sql);
self::assertInstanceOf(Statement::class, $stmt);
$stmt->execute([$paramInt, $paramStr]);
$result = $stmt->execute([$paramInt, $paramStr]);
$row = $stmt->fetchAssociative();
$row = $result->fetchAssociative();
self::assertNotFalse($row);
$row = array_change_key_case($row, CASE_LOWER);
self::assertEquals(['test_int' => 1, 'test_string' => 'foo'], $row);
......@@ -392,14 +368,13 @@ class DataAccessTest extends FunctionalTestCase
*/
public function testExecuteQueryBindDateTimeType(): void
{
$sql = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?';
$stmt = $this->connection->executeQuery(
$sql,
$value = $this->connection->fetchOne(
'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?',
[1 => new DateTime('2010-01-01 10:10:10')],
[1 => Types::DATETIME_MUTABLE]
);
self::assertEquals(1, $stmt->fetchOne());
self::assertEquals(1, $value);
}
/**
......@@ -436,9 +411,9 @@ class DataAccessTest extends FunctionalTestCase
$sql = 'SELECT count(*) AS c FROM fetch_table WHERE test_datetime = ?';
$stmt = $this->connection->prepare($sql);
$stmt->bindValue(1, new DateTime('2010-01-01 10:10:10'), Types::DATETIME_MUTABLE);
$stmt->execute();
$result = $stmt->execute();
self::assertEquals(1, $stmt->fetchOne());
self::assertEquals(1, $result->fetchOne());
}
/**
......@@ -450,23 +425,23 @@ class DataAccessTest extends FunctionalTestCase
$this->connection->insert('fetch_table', ['test_int' => $i, 'test_string' => 'foo' . $i, 'test_datetime' => '2010-01-01 10:10:10']);
}
$stmt = $this->connection->executeQuery(
$result = $this->connection->executeQuery(
'SELECT test_int FROM fetch_table WHERE test_int IN (?)',
[[100, 101, 102, 103, 104]],
[Connection::PARAM_INT_ARRAY]
);
$data = $stmt->fetchAllNumeric();
$data = $result->fetchAllNumeric();
self::assertCount(5, $data);
self::assertEquals([[100], [101], [102], [103], [104]], $data);
$stmt = $this->connection->executeQuery(
$result = $this->connection->executeQuery(
'SELECT test_int FROM fetch_table WHERE test_string IN (?)',
[['foo100', 'foo101', 'foo102', 'foo103', 'foo104']],
[Connection::PARAM_STR_ARRAY]
);
$data = $stmt->fetchAllNumeric();
$data = $result->fetchAllNumeric();
self::assertCount(5, $data);
self::assertEquals([[100], [101], [102], [103], [104]], $data);
}
......@@ -670,8 +645,7 @@ class DataAccessTest extends FunctionalTestCase
. ', ' . $platform->getBitAndComparisonExpression('test_int', 2) . ' AS bit_and'
. ' FROM fetch_table';
$stmt = $this->connection->executeQuery($sql);
$data = $stmt->fetchAllAssociative();
$data = $this->connection->fetchAllAssociative($sql);
self::assertCount(4, $data);
self::assertEquals(count($bitmap), count($data));
......@@ -728,8 +702,7 @@ class DataAccessTest extends FunctionalTestCase
public function testEmptyParameters(): void
{
$sql = 'SELECT * FROM fetch_table WHERE test_int IN (?)';
$stmt = $this->connection->executeQuery($sql, [[]], [Connection::PARAM_INT_ARRAY]);
$rows = $stmt->fetchAllAssociative();
$rows = $this->connection->fetchAllAssociative($sql, [[]], [Connection::PARAM_INT_ARRAY]);
self::assertEquals([], $rows);
}
......
......@@ -48,12 +48,11 @@ class StatementTest extends FunctionalTestCase
*/
public function testStatementBindParameters(string $query, array $params, array $expected): void
{
$stmt = $this->connection->prepare($query);
$stmt->execute($params);
self::assertEquals(
$expected,
$stmt->fetchAssociative()
$this->connection->prepare($query)
->execute($params)
->fetchAssociative()
);
}
......
......@@ -85,8 +85,7 @@ class DriverTest extends AbstractDriverTest
$hash = microtime(true); // required to identify the record in the results uniquely
$sql = sprintf('SELECT * FROM pg_stat_activity WHERE %d = %d', $hash, $hash);
$statement = $connection->query($sql);
$records = $statement->fetchAllAssociative();
$records = $connection->query($sql)->fetchAllAssociative();
foreach ($records as $record) {
// The query column is named "current_query" on PostgreSQL < 9.2
......
......@@ -13,7 +13,8 @@ final class LikeWildcardsEscapingTest extends FunctionalTestCase
$string = '_25% off_ your next purchase \o/ [$̲̅(̲̅5̲̅)̲̅$̲̅] (^̮^)';
$escapeChar = '!';
$databasePlatform = $this->connection->getDatabasePlatform();
$stmt = $this->connection->prepare(
$result = $this->connection->prepare(
$databasePlatform->getDummySelectSQL(
sprintf(
"(CASE WHEN '%s' LIKE '%s' ESCAPE '%s' THEN 1 ELSE 0 END)",
......@@ -22,8 +23,8 @@ final class LikeWildcardsEscapingTest extends FunctionalTestCase
$escapeChar
)
)
);
$stmt->execute();
self::assertTrue((bool) $stmt->fetchOne());
)->execute();
self::assertTrue((bool) $result->fetchOne());
}
}
......@@ -3,7 +3,6 @@
namespace Doctrine\DBAL\Tests\Functional;
use Doctrine\DBAL\Connections\MasterSlaveConnection;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
......@@ -197,14 +196,12 @@ class MasterSlaveConnectionTest extends FunctionalTestCase
$query = 'SELECT count(*) as num FROM master_slave_table';
$statement = $conn->query($query);
self::assertInstanceOf(Statement::class, $statement);
$result = $conn->query($query);
//Query must be executed only on Master
self::assertTrue($conn->isConnectedToMaster());
$data = $statement->fetchAllAssociative();
$data = $result->fetchAllAssociative();
self::assertArrayHasKey(0, $data);
self::assertArrayHasKey('num', $data[0]);
......@@ -221,14 +218,12 @@ class MasterSlaveConnectionTest extends FunctionalTestCase
$query = 'SELECT count(*) as num FROM master_slave_table';
$statement = $conn->query($query);
self::assertInstanceOf(Statement::class, $statement);
$result = $conn->query($query);
//Query must be executed only on Master, even when we connect to the slave
self::assertTrue($conn->isConnectedToMaster());
$data = $statement->fetchAllAssociative();
$data = $result->fetchAllAssociative();
self::assertArrayHasKey(0, $data);
self::assertArrayHasKey('num', $data[0]);
......
......@@ -213,13 +213,12 @@ class NamedParametersTest extends FunctionalTestCase
*/
public function testTicket(string $query, array $params, array $types, array $expected): void
{
$stmt = $this->connection->executeQuery($query, $params, $types);
$result = $stmt->fetchAllAssociative();
$data = $this->connection->fetchAllAssociative($query, $params, $types);
foreach ($result as $k => $v) {
$result[$k] = array_change_key_case($v, CASE_LOWER);
foreach ($data as $k => $v) {
$data[$k] = array_change_key_case($v, CASE_LOWER);
}
self::assertEquals($result, $expected);
self::assertEquals($data, $expected);
}
}
......@@ -66,16 +66,17 @@ class PortabilityTest extends FunctionalTestCase
$rows = $this->getPortableConnection()->fetchAllAssociative('SELECT * FROM portability_table');
$this->assertFetchResultRows($rows);
$stmt = $this->getPortableConnection()->query('SELECT * FROM portability_table');
$result = $this->getPortableConnection()->query('SELECT * FROM portability_table');
while (($row = $stmt->fetchAssociative())) {
while (($row = $result->fetchAssociative())) {
$this->assertFetchResultRow($row);
}
$stmt = $this->getPortableConnection()->prepare('SELECT * FROM portability_table');
$stmt->execute();
$result = $this->getPortableConnection()
->prepare('SELECT * FROM portability_table')
->execute();
while (($row = $stmt->fetchAssociative())) {
while (($row = $result->fetchAssociative())) {
$this->assertFetchResultRow($row);
}
}
......@@ -87,14 +88,15 @@ class PortabilityTest extends FunctionalTestCase
$rows = $conn->fetchAllAssociative('SELECT * FROM portability_table');
$this->assertFetchResultRows($rows);
$stmt = $conn->query('SELECT * FROM portability_table');
while (($row = $stmt->fetchAssociative())) {
$result = $conn->query('SELECT * FROM portability_table');
while (($row = $result->fetchAssociative())) {
$this->assertFetchResultRow($row);
}
$stmt = $conn->prepare('SELECT * FROM portability_table');
$stmt->execute();
while (($row = $stmt->fetchAssociative())) {
$result = $conn->prepare('SELECT * FROM portability_table')
->execute();
while (($row = $result->fetchAssociative())) {
$this->assertFetchResultRow($row);
}
}
......@@ -134,9 +136,9 @@ class PortabilityTest extends FunctionalTestCase
public function testfetchColumn(string $field, array $expected): void
{
$conn = $this->getPortableConnection();
$stmt = $conn->query('SELECT ' . $field . ' FROM portability_table');
$result = $conn->query('SELECT ' . $field . ' FROM portability_table');
$column = $stmt->fetchFirstColumn();
$column = $result->fetchFirstColumn();
self::assertEquals($expected, $column);
}
......@@ -160,9 +162,9 @@ class PortabilityTest extends FunctionalTestCase
public function testFetchAllNullColumn(): void
{
$conn = $this->getPortableConnection();
$stmt = $conn->query('SELECT Test_Null FROM portability_table');
$result = $conn->query('SELECT Test_Null FROM portability_table');
$column = $stmt->fetchFirstColumn();
$column = $result->fetchFirstColumn();
self::assertSame([null, null], $column);
}
}
This diff is collapsed.
......@@ -1566,17 +1566,17 @@ abstract class SchemaManagerFunctionalTestCase extends FunctionalTestCase
$this->connection->insert('test_pk_auto_increment', ['text' => '1']);
$query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'1\'');
$query->execute();
$lastUsedIdBeforeDelete = (int) $query->fetchOne();
$result = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'1\'');
$lastUsedIdBeforeDelete = (int) $result->fetchOne();
$this->connection->query('DELETE FROM test_pk_auto_increment');
$this->connection->insert('test_pk_auto_increment', ['text' => '2']);
$query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'2\'');
$query->execute();
$lastUsedIdAfterDelete = (int) $query->fetchOne();
$result = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = \'2\'');
$lastUsedIdAfterDelete = (int) $result->fetchOne();
self::assertGreaterThan($lastUsedIdBeforeDelete, $lastUsedIdAfterDelete);
}
......
......@@ -273,9 +273,9 @@ SQL;
$this->connection->insert('test_pk_auto_increment', ['text' => '2']);
$query = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = "2"');
$query->execute();
$lastUsedIdAfterDelete = (int) $query->fetchOne();
$result = $this->connection->query('SELECT id FROM test_pk_auto_increment WHERE text = "2"');
$lastUsedIdAfterDelete = (int) $result->fetchOne();
// with an empty table, non autoincrement rowid is always 1
self::assertEquals(1, $lastUsedIdAfterDelete);
......
......@@ -3,11 +3,12 @@
namespace Doctrine\DBAL\Tests\Functional;
use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOracleDriver;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Types\Type;
use Throwable;
use function base64_decode;
use function stream_get_contents;
......@@ -24,7 +25,7 @@ class StatementTest extends FunctionalTestCase
$this->connection->getSchemaManager()->dropAndCreateTable($table);
}
public function testStatementIsReusableAfterClosingCursor(): void
public function testStatementIsReusableAfterFreeingResult(): void
{
if ($this->connection->getDriver() instanceof PDOOracleDriver) {
self::markTestIncomplete('See https://bugs.php.net/bug.php?id=77181');
......@@ -35,18 +36,16 @@ class StatementTest extends FunctionalTestCase
$stmt = $this->connection->prepare('SELECT id FROM stmt_test ORDER BY id');
$stmt->execute();
$result = $stmt->execute();
$id = $stmt->fetchOne();
$id = $result->fetchOne();
self::assertEquals(1, $id);
$stmt->closeCursor();
$result->free();
$stmt->execute();
$id = $stmt->fetchOne();
self::assertEquals(1, $id);
$id = $stmt->fetchOne();
self::assertEquals(2, $id);
$result = $stmt->execute();
self::assertEquals(1, $result->fetchOne());
self::assertEquals(2, $result->fetchOne());
}
public function testReuseStatementWithLongerResults(): void
......@@ -68,10 +67,10 @@ class StatementTest extends FunctionalTestCase
$this->connection->insert('stmt_longer_results', $row1);
$stmt = $this->connection->prepare('SELECT param, val FROM stmt_longer_results ORDER BY param');
$stmt->execute();
$result = $stmt->execute();
self::assertEquals([
['param1', 'X'],
], $stmt->fetchAllNumeric());
], $result->fetchAllNumeric());
$row2 = [
'param' => 'param2',
......@@ -79,11 +78,11 @@ class StatementTest extends FunctionalTestCase
];
$this->connection->insert('stmt_longer_results', $row2);
$stmt->execute();
$result = $stmt->execute();
self::assertEquals([
['param1', 'X'],
['param2', 'A bit longer value'],
], $stmt->fetchAllNumeric());
], $result->fetchAllNumeric());
}
public function testFetchLongBlob(): void
......@@ -122,12 +121,12 @@ EOF
$this->connection->insert('stmt_long_blob', ['contents' => $contents], [ParameterType::LARGE_OBJECT]);
$stmt = $this->connection->prepare('SELECT contents FROM stmt_long_blob');
$stmt->execute();
$result = $this->connection->prepare('SELECT contents FROM stmt_long_blob')
->execute();
$stream = Type::getType('blob')
->convertToPHPValue(
$stmt->fetchOne(),
$result->fetchOne(),
$this->connection->getDatabasePlatform()
);
......@@ -140,18 +139,19 @@ EOF
$this->connection->insert('stmt_test', ['id' => 2]);
$stmt1 = $this->connection->prepare('SELECT id FROM stmt_test');
$stmt1->execute();
$stmt1->fetchAssociative();
$stmt1->execute();
$result = $stmt1->execute();
$result->fetchAssociative();
$result = $stmt1->execute();
// fetching only one record out of two
$stmt1->fetchAssociative();
$result->fetchAssociative();
$stmt2 = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?');
$stmt2->execute([1]);
self::assertEquals(1, $stmt2->fetchOne());
$result = $stmt2->execute([1]);
self::assertEquals(1, $result->fetchOne());
}
public function testReuseStatementAfterClosingCursor(): void
public function testReuseStatementAfterFreeingResult(): void
{
if ($this->connection->getDriver() instanceof PDOOracleDriver) {
self::markTestIncomplete('See https://bugs.php.net/bug.php?id=77181');
......@@ -162,14 +162,16 @@ EOF
$stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?');
$stmt->execute([1]);
$id = $stmt->fetchOne();
$result = $stmt->execute([1]);
$id = $result->fetchOne();
self::assertEquals(1, $id);
$stmt->closeCursor();
$result->free();
$result = $stmt->execute([2]);
$stmt->execute([2]);
$id = $stmt->fetchOne();
$id = $result->fetchOne();
self::assertEquals(2, $id);
}
......@@ -182,12 +184,14 @@ EOF
$stmt->bindParam(1, $id);
$id = 1;
$stmt->execute();
self::assertEquals(1, $stmt->fetchOne());
$result = $stmt->execute();
self::assertEquals(1, $result->fetchOne());
$id = 2;
$stmt->execute();
self::assertEquals(2, $stmt->fetchOne());
$result = $stmt->execute();
self::assertEquals(2, $result->fetchOne());
}
public function testReuseStatementWithReboundValue(): void
......@@ -198,12 +202,12 @@ EOF
$stmt = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?');
$stmt->bindValue(1, 1);
$stmt->execute();
self::assertEquals(1, $stmt->fetchOne());
$result = $stmt->execute();
self::assertEquals(1, $result->fetchOne());
$stmt->bindValue(1, 2);
$stmt->execute();
self::assertEquals(2, $stmt->fetchOne());
$result = $stmt->execute();
self::assertEquals(2, $result->fetchOne());
}
public function testReuseStatementWithReboundParam(): void
......@@ -215,13 +219,13 @@ EOF
$x = 1;
$stmt->bindParam(1, $x);
$stmt->execute();
self::assertEquals(1, $stmt->fetchOne());
$result = $stmt->execute();
self::assertEquals(1, $result->fetchOne());
$y = 2;
$stmt->bindParam(1, $y);
$stmt->execute();
self::assertEquals(2, $stmt->fetchOne());
$result = $stmt->execute();
self::assertEquals(2, $result->fetchOne());
}
/**
......@@ -229,59 +233,25 @@ EOF
*
* @dataProvider emptyFetchProvider
*/
public function testFetchFromNonExecutedStatement(callable $fetch, $expected): void
public function testFetchFromExecutedStatementWithFreedResult(callable $fetch, $expected): void
{
$stmt = $this->connection->prepare('SELECT id FROM stmt_test');
self::assertSame($expected, $fetch($stmt));
}
$this->connection->insert('stmt_test', ['id' => 1]);
public function testCloseCursorOnNonExecutedStatement(): void
{
$stmt = $this->connection->prepare('SELECT id FROM stmt_test');
self::assertTrue($stmt->closeCursor());
}
$result = $stmt->execute();
$result->free();
/**
* @group DBAL-2637
*/
public function testCloseCursorAfterCursorEnd(): void
{
$stmt = $this->connection->prepare('SELECT name FROM stmt_test');
$stmt->execute();
$stmt->fetchAssociative();
try {
$value = $fetch($result);
} catch (Throwable $e) {
// The drivers that enforce the command sequencing internally will throw an exception
$this->expectNotToPerformAssertions();
self::assertTrue($stmt->closeCursor());
return;
}
/**
* @param mixed $expected
*
* @dataProvider emptyFetchProvider
*/
public function testFetchFromNonExecutedStatementWithClosedCursor(callable $fetch, $expected): void
{
$stmt = $this->connection->prepare('SELECT id FROM stmt_test');
$stmt->closeCursor();
self::assertSame($expected, $fetch($stmt));
}
/**
* @param mixed $expected
*
* @dataProvider emptyFetchProvider
*/
public function testFetchFromExecutedStatementWithClosedCursor(callable $fetch, $expected): void
{
$this->connection->insert('stmt_test', ['id' => 1]);
$stmt = $this->connection->prepare('SELECT id FROM stmt_test');
$stmt->execute();
$stmt->closeCursor();
self::assertSame($expected, $fetch($stmt));
// Other drivers will silently return an empty result
self::assertSame($expected, $value);
}
/**
......@@ -291,20 +261,20 @@ EOF
{
return [
'fetch' => [
static function (Statement $stmt) {
return $stmt->fetchAssociative();
static function (Result $result) {
return $result->fetchAssociative();
},
false,
],
'fetch-column' => [
static function (Statement $stmt) {
return $stmt->fetchOne();
static function (Result $result) {
return $result->fetchOne();
},
false,
],
'fetch-all' => [
static function (Statement $stmt): array {
return $stmt->fetchAllAssociative();
static function (Result $result): array {
return $result->fetchAllAssociative();
},
[],
],
......
......@@ -41,13 +41,10 @@ class DBAL421Test extends FunctionalTestCase
$guids = [];
for ($i = 0; $i < 99; $i++) {
$statement->execute();
$guid = $statement->fetchFirstColumn();
$guid = $statement->execute()->fetchFirstColumn();
self::assertNotContains($guid, $guids, 'Duplicate GUID detected');
$guids[] = $guid;
}
$statement->closeCursor();
}
private function getSelectGuidSql(): string
......
......@@ -74,24 +74,22 @@ class WriteTest extends FunctionalTestCase
$stmt->bindValue(1, 1);
$stmt->bindValue(2, 'foo');
$stmt->execute();
self::assertEquals(1, $stmt->rowCount());
self::assertEquals(1, $stmt->execute()->rowCount());
}
public function testPrepareWithPdoTypes(): void
public function testPrepareWithPrimitiveTypes(): void
{
$sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)';
$stmt = $this->connection->prepare($sql);
$stmt->bindValue(1, 1, ParameterType::INTEGER);
$stmt->bindValue(2, 'foo', ParameterType::STRING);
$stmt->execute();
self::assertEquals(1, $stmt->rowCount());
self::assertEquals(1, $stmt->execute()->rowCount());
}
public function testPrepareWithDbalTypes(): void
public function testPrepareWithDoctrineMappingTypes(): void
{
$sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)';
$stmt = $this->connection->prepare($sql);
......@@ -99,12 +97,11 @@ class WriteTest extends FunctionalTestCase
self::assertInstanceOf(Statement::class, $stmt);
$stmt->bindValue(1, 1, Type::getType('integer'));
$stmt->bindValue(2, 'foo', Type::getType('string'));
$stmt->execute();
self::assertEquals(1, $stmt->rowCount());
self::assertEquals(1, $stmt->execute()->rowCount());
}
public function testPrepareWithDbalTypeNames(): void
public function testPrepareWithDoctrineMappingTypeNames(): void
{
$sql = 'INSERT INTO write_table (test_int, test_string) VALUES (?, ?)';
$stmt = $this->connection->prepare($sql);
......@@ -112,9 +109,8 @@ class WriteTest extends FunctionalTestCase
self::assertInstanceOf(Statement::class, $stmt);
$stmt->bindValue(1, 1, 'integer');
$stmt->bindValue(2, 'foo', 'string');
$stmt->execute();
self::assertEquals(1, $stmt->rowCount());
self::assertEquals(1, $stmt->execute()->rowCount());
}
public function insertRows(): void
......@@ -178,8 +174,8 @@ class WriteTest extends FunctionalTestCase
return strtolower($sequence->getName()) === 'write_table_id_seq';
}));
$stmt = $this->connection->query($this->connection->getDatabasePlatform()->getSequenceNextValSQL('write_table_id_seq'));
$nextSequenceVal = $stmt->fetchOne();
$result = $this->connection->query($this->connection->getDatabasePlatform()->getSequenceNextValSQL('write_table_id_seq'));
$nextSequenceVal = $result->fetchOne();
$lastInsertId = $this->lastInsertId('write_table_id_seq');
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Tests\Portability;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Portability\Converter;
use Doctrine\DBAL\Portability\Result;
use PHPUnit\Framework\TestCase;
class ResultTest extends TestCase
{
public function testRowCount(): void
{
$driverResult = $this->createMock(DriverResult::class);
$driverResult->expects(self::once())
->method('rowCount')
->willReturn(666);
$result = $this->newResult($driverResult);
self::assertSame(666, $result->rowCount());
}
public function testColumnCount(): void
{
$driverResult = $this->createMock(DriverResult::class);
$driverResult->expects(self::once())
->method('columnCount')
->willReturn(666);
$result = $this->newResult($driverResult);
self::assertSame(666, $result->columnCount());
}
public function testFree(): void
{
$driverResult = $this->createMock(DriverResult::class);
$driverResult->expects(self::once())
->method('free');
$this->newResult($driverResult)->free();
}
private function newResult(DriverResult $driverResult): Result
{
return new Result(
$driverResult,
new Converter(false, false, null)
);
}
}
......@@ -57,26 +57,6 @@ class StatementTest extends TestCase
self::assertTrue($this->stmt->bindValue($param, $value, $type));
}
public function testCloseCursor(): void
{
$this->wrappedStmt->expects(self::once())
->method('closeCursor')
->will(self::returnValue(true));
self::assertTrue($this->stmt->closeCursor());
}
public function testColumnCount(): void
{
$columnCount = 666;
$this->wrappedStmt->expects(self::once())
->method('columnCount')
->will(self::returnValue($columnCount));
self::assertSame($columnCount, $this->stmt->columnCount());
}
public function testExecute(): void
{
$params = [
......@@ -86,21 +66,9 @@ class StatementTest extends TestCase
$this->wrappedStmt->expects(self::once())
->method('execute')
->with($params)
->will(self::returnValue(true));
self::assertTrue($this->stmt->execute($params));
}
public function testRowCount(): void
{
$rowCount = 666;
$this->wrappedStmt->expects(self::once())
->method('rowCount')
->will(self::returnValue($rowCount));
->with($params);
self::assertSame($rowCount, $this->stmt->rowCount());
$this->stmt->execute($params);
}
/**
......
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