diff --git a/UPGRADE.md b/UPGRADE.md index acf1846c16c..cf712fedb7b 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,16 @@ # 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. diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 390aa2c76d9..a9607273e9d 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -97,7 +97,7 @@ - src/Driver/SQLSrv/SQLSrvStatement.php + src/Driver/SQLSrv/Result.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c38e5eacdfa..b7e33c6fd16 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -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, string given\.$~' diff --git a/src/Cache/ArrayStatement.php b/src/Cache/ArrayResult.php similarity index 85% rename from src/Cache/ArrayStatement.php rename to src/Cache/ArrayResult.php index 288e30b3221..f8fa83442c8 100644 --- a/src/Cache/ArrayStatement.php +++ b/src/Cache/ArrayResult.php @@ -4,16 +4,15 @@ 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 @@ public function __construct(array $data) $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 @@ public function fetchFirstColumn(): array 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 = []; diff --git a/src/Cache/ResultCacheStatement.php b/src/Cache/CachingResult.php similarity index 72% rename from src/Cache/ResultCacheStatement.php rename to src/Cache/CachingResult.php index d1180f499bf..c7c208e7e03 100644 --- a/src/Cache/ResultCacheStatement.php +++ b/src/Cache/CachingResult.php @@ -6,14 +6,11 @@ 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 @@ * 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>|null */ private $data; @@ -51,38 +48,13 @@ class ResultCacheStatement implements ResultStatement, Result * @param string $realKey * @param int $lifetime */ - public function __construct(ResultStatement $stmt, Cache $resultCache, $cacheKey, $realKey, $lifetime) - { - $this->statement = $stmt; - $this->resultCache = $resultCache; - $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() + public function __construct(Result $result, Cache $cache, $cacheKey, $realKey, $lifetime) { - return $this->statement->columnCount(); - } - - public function rowCount(): int - { - return $this->statement->rowCount(); + $this->result = $result; + $this->cache = $cache; + $this->cacheKey = $cacheKey; + $this->realKey = $realKey; + $this->lifetime = $lifetime; } /** @@ -121,7 +93,7 @@ public function fetchOne() public function fetchAllNumeric(): array { $this->store( - $this->statement->fetchAllAssociative() + $this->result->fetchAllAssociative() ); return array_map('array_values', $this->data); @@ -133,7 +105,7 @@ public function fetchAllNumeric(): array public function fetchAllAssociative(): array { $this->store( - $this->statement->fetchAllAssociative() + $this->result->fetchAllAssociative() ); return $this->data; @@ -147,6 +119,16 @@ public function fetchFirstColumn(): array 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 @@ private function fetch() $this->data = []; } - $row = $this->statement->fetchAssociative(); + $row = $this->result->fetchAssociative(); if ($row !== false) { $this->data[] = $row; @@ -192,7 +174,7 @@ private function saveToCache(): void return; } - $data = $this->resultCache->fetch($this->cacheKey); + $data = $this->cache->fetch($this->cacheKey); if ($data === false) { $data = []; @@ -200,6 +182,6 @@ private function saveToCache(): void $data[$this->realKey] = $this->data; - $this->resultCache->save($this->cacheKey, $data, $this->lifetime); + $this->cache->save($this->cacheKey, $data, $this->lifetime); } } diff --git a/src/Connection.php b/src/Connection.php index 1297c201609..6e430e7fca0 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -4,13 +4,14 @@ 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 @@ public function fetchFirstColumn(string $query, array $params = [], array $types 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 @@ public function iterateNumeric(string $query, array $params = [], array $types = 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 @@ public function iterateAssociative(string $query, array $params = [], array $typ 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 @@ public function prepare(string $sql): DriverStatement * @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 @@ public function executeQuery(string $query, array $params = [], $types = [], ?Qu $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(); + } } - - if ($logger !== null) { - $logger->stopQuery(); - } - - return $stmt; } /** @@ -1036,8 +1039,9 @@ public function executeQuery(string $query, array $params = [], $types = [], ?Qu * @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 @@ public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qc 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 @@ public function query(string $sql): ResultStatement } 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(); + } } - - if ($logger !== null) { - $logger->stopQuery(); - } - - return $statement; } /** @@ -1120,24 +1128,23 @@ public function executeUpdate(string $query, array $params = [], array $types = 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(); + } } - - if ($logger !== null) { - $logger->stopQuery(); - } - - return $result; } public function exec(string $statement): int @@ -1150,16 +1157,14 @@ public function exec(string $statement): int } 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(); + } } - - if ($logger !== null) { - $logger->stopQuery(); - } - - return $result; } /** @@ -1643,8 +1648,8 @@ private function getBindingInfo($value, $type) * @internal This is a purely internal method. If you rely on this method, you are advised to * copy/paste the code as this method may change, or be removed without prior notice. * - * @param mixed[] $params - * @param int[]|string[] $types + * @param mixed[] $params + * @param array $types * * @return mixed[] */ diff --git a/src/Connections/MasterSlaveConnection.php b/src/Connections/MasterSlaveConnection.php index 9bcea1e8563..25d5ac99a80 100644 --- a/src/Connections/MasterSlaveConnection.php +++ b/src/Connections/MasterSlaveConnection.php @@ -7,7 +7,7 @@ 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 @@ public function rollbackSavepoint($savepoint) parent::rollbackSavepoint($savepoint); } - public function query(string $sql): ResultStatement + public function query(string $sql): Result { $this->connect('master'); assert($this->_conn instanceof DriverConnection); diff --git a/src/Driver/Connection.php b/src/Driver/Connection.php index 3f1840ea972..e972c896dd5 100644 --- a/src/Driver/Connection.php +++ b/src/Driver/Connection.php @@ -23,7 +23,7 @@ public function prepare(string $sql): Statement; * * @throws DBALException */ - public function query(string $sql): ResultStatement; + public function query(string $sql): Result; /** * Quotes a string for use in a query. diff --git a/src/Driver/IBMDB2/DB2Connection.php b/src/Driver/IBMDB2/DB2Connection.php index 93565bab70c..c6a120cc916 100644 --- a/src/Driver/IBMDB2/DB2Connection.php +++ b/src/Driver/IBMDB2/DB2Connection.php @@ -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 @@ public function prepare(string $sql): DriverStatement 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(); } /** diff --git a/src/Driver/IBMDB2/DB2Statement.php b/src/Driver/IBMDB2/DB2Statement.php index 0bab901c7b4..57c566c561d 100644 --- a/src/Driver/IBMDB2/DB2Statement.php +++ b/src/Driver/IBMDB2/DB2Statement.php @@ -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_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 @@ private function bind($position, &$variable, int $parameterType, int $dataType): /** * {@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 @@ public function execute($params = null) $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 @@ public function execute($params = null) $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); } /** diff --git a/src/Driver/IBMDB2/Result.php b/src/Driver/IBMDB2/Result.php new file mode 100644 index 00000000000..2cb756fd0d9 --- /dev/null +++ b/src/Driver/IBMDB2/Result.php @@ -0,0 +1,111 @@ +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); + } +} diff --git a/src/Driver/Mysqli/MysqliConnection.php b/src/Driver/Mysqli/MysqliConnection.php index b2fcd70be6f..6da2e729152 100644 --- a/src/Driver/Mysqli/MysqliConnection.php +++ b/src/Driver/Mysqli/MysqliConnection.php @@ -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 @@ public function prepare(string $sql): DriverStatement 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(); } /** diff --git a/src/Driver/Mysqli/MysqliStatement.php b/src/Driver/Mysqli/MysqliStatement.php index 30af1604e7b..44168f8e251 100644 --- a/src/Driver/Mysqli/MysqliStatement.php +++ b/src/Driver/Mysqli/MysqliStatement.php @@ -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 @@ public function bindValue($param, $value, $type = ParameterType::STRING) /** * {@inheritdoc} */ - public function execute($params = null) + public function execute($params = null): ResultInterface { if ($this->_bindedValues !== null) { if ($params !== null) { @@ -147,57 +131,7 @@ public function execute($params = null) 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 @@ private function bindUntypedValues(array $values) 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; - } } diff --git a/src/Driver/Mysqli/Result.php b/src/Driver/Mysqli/Result.php new file mode 100644 index 00000000000..0833503a227 --- /dev/null +++ b/src/Driver/Mysqli/Result.php @@ -0,0 +1,186 @@ + + */ + 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(); + } +} diff --git a/src/Driver/OCI8/OCI8Connection.php b/src/Driver/OCI8/OCI8Connection.php index e7460df289d..b02199789c2 100644 --- a/src/Driver/OCI8/OCI8Connection.php +++ b/src/Driver/OCI8/OCI8Connection.php @@ -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 @@ public function prepare(string $sql): DriverStatement 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 @@ public function quote($value, $type = ParameterType::STRING) public function exec(string $statement): int { - $stmt = $this->prepare($statement); - $stmt->execute(); - - return $stmt->rowCount(); + return $this->prepare($statement)->execute()->rowCount(); } /** diff --git a/src/Driver/OCI8/OCI8Statement.php b/src/Driver/OCI8/OCI8Statement.php index dea91df3e0b..87619bbd247 100644 --- a/src/Driver/OCI8/OCI8Statement.php +++ b/src/Driver/OCI8/OCI8Statement.php @@ -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 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 @@ /** * 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. * @@ -315,31 +296,7 @@ private function convertParameterType(int $type): int * * @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 +313,6 @@ public function execute($params = null) 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 - */ - 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); } } diff --git a/src/Driver/OCI8/Result.php b/src/Driver/OCI8/Result.php new file mode 100644 index 00000000000..f3e69e1f242 --- /dev/null +++ b/src/Driver/OCI8/Result.php @@ -0,0 +1,137 @@ +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 + */ + 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; + } +} diff --git a/src/Driver/PDO/Result.php b/src/Driver/PDO/Result.php new file mode 100644 index 00000000000..bacf335837e --- /dev/null +++ b/src/Driver/PDO/Result.php @@ -0,0 +1,131 @@ +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 + * + * @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; + } +} diff --git a/src/Driver/PDOConnection.php b/src/Driver/PDOConnection.php index 5d6c16982c4..e604c03457b 100644 --- a/src/Driver/PDOConnection.php +++ b/src/Driver/PDOConnection.php @@ -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 @@ public function prepare(string $sql): Statement } } - 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); } diff --git a/src/Driver/PDOSqlsrv/Connection.php b/src/Driver/PDOSqlsrv/Connection.php index 60f77f6947f..4d22a98e139 100644 --- a/src/Driver/PDOSqlsrv/Connection.php +++ b/src/Driver/PDOSqlsrv/Connection.php @@ -23,10 +23,9 @@ public function lastInsertId($name = null) 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(); } /** diff --git a/src/Driver/PDOStatement.php b/src/Driver/PDOStatement.php index 38bf60d5ce5..617019f1cb9 100644 --- a/src/Driver/PDOStatement.php +++ b/src/Driver/PDOStatement.php @@ -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 @@ public function bindParam($column, &$variable, $type = ParameterType::STRING, $l /** * {@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 - * - * @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); } /** diff --git a/src/Driver/Result.php b/src/Driver/Result.php index 3818755b11e..c38ebc2df14 100644 --- a/src/Driver/Result.php +++ b/src/Driver/Result.php @@ -5,7 +5,7 @@ namespace Doctrine\DBAL\Driver; /** - * Driver-level result statement execution result. + * Driver-level statement execution result. */ interface Result { @@ -72,7 +72,7 @@ public function fetchFirstColumn(): array; * * @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 @@ public function rowCount(); * @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. diff --git a/src/Driver/ResultStatement.php b/src/Driver/ResultStatement.php deleted file mode 100644 index dabe08bb82c..00000000000 --- a/src/Driver/ResultStatement.php +++ /dev/null @@ -1,94 +0,0 @@ -|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|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> - * - * @throws DriverException - */ - public function fetchAllNumeric(): array; - - /** - * Returns an array containing all of the result set rows represented as associative arrays. - * - * @return array> - * - * @throws DriverException - */ - public function fetchAllAssociative(): array; - - /** - * Returns an array containing the values of the first column of the result set. - * - * @return array - * - * @throws DriverException - */ - public function fetchFirstColumn(): array; -} diff --git a/src/Driver/SQLAnywhere/Result.php b/src/Driver/SQLAnywhere/Result.php new file mode 100644 index 00000000000..1881513d082 --- /dev/null +++ b/src/Driver/SQLAnywhere/Result.php @@ -0,0 +1,96 @@ +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); + } +} diff --git a/src/Driver/SQLAnywhere/SQLAnywhereConnection.php b/src/Driver/SQLAnywhere/SQLAnywhereConnection.php index b29954598be..9690203941e 100644 --- a/src/Driver/SQLAnywhere/SQLAnywhereConnection.php +++ b/src/Driver/SQLAnywhere/SQLAnywhereConnection.php @@ -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 @@ public function prepare(string $sql): Statement 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(); } /** diff --git a/src/Driver/SQLAnywhere/SQLAnywhereStatement.php b/src/Driver/SQLAnywhere/SQLAnywhereStatement.php index 9d764590973..54043ec9477 100644 --- a/src/Driver/SQLAnywhere/SQLAnywhereStatement.php +++ b/src/Driver/SQLAnywhere/SQLAnywhereStatement.php @@ -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 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 @@ public function bindValue($param, $value, $type = ParameterType::STRING) * * @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 @@ public function execute($params = null) 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> - * - * @throws DriverException - */ - public function fetchAllNumeric(): array - { - return FetchUtils::fetchAllNumeric($this); - } - - /** - * @return array> - * - * @throws DriverException - */ - public function fetchAllAssociative(): array - { - return FetchUtils::fetchAllAssociative($this); - } - - /** - * @return array - * - * @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 diff --git a/src/Driver/SQLSrv/Result.php b/src/Driver/SQLSrv/Result.php new file mode 100644 index 00000000000..4e7a383e568 --- /dev/null +++ b/src/Driver/SQLSrv/Result.php @@ -0,0 +1,126 @@ +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; + } +} diff --git a/src/Driver/SQLSrv/SQLSrvConnection.php b/src/Driver/SQLSrv/SQLSrvConnection.php index 2824919f672..30f27272f0b 100644 --- a/src/Driver/SQLSrv/SQLSrvConnection.php +++ b/src/Driver/SQLSrv/SQLSrvConnection.php @@ -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 @@ public function prepare(string $sql): DriverStatement 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 @@ public function exec(string $statement): int 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(); } /** diff --git a/src/Driver/SQLSrv/SQLSrvStatement.php b/src/Driver/SQLSrv/SQLSrvStatement.php index 5556ff8bc2e..a89b77cc77d 100644 --- a/src/Driver/SQLSrv/SQLSrvStatement.php +++ b/src/Driver/SQLSrv/SQLSrvStatement.php @@ -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_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. * @@ -145,35 +132,7 @@ public function bindParam($column, &$variable, $type = ParameterType::STRING, $l * * @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 +158,7 @@ public function execute($params = null) $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0)); } - $this->result = true; - - return true; + return new Result($this->stmt); } /** @@ -248,98 +205,4 @@ private function prepare() 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; - } } diff --git a/src/Driver/Statement.php b/src/Driver/Statement.php index 2bd1e879470..ed8b2774c5f 100644 --- a/src/Driver/Statement.php +++ b/src/Driver/Statement.php @@ -5,12 +5,9 @@ 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 @@ public function bindParam($column, &$variable, $type = ParameterType::STRING, $l * @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; } diff --git a/src/Portability/Connection.php b/src/Portability/Connection.php index 8e2f607af00..78ba390846b 100644 --- a/src/Portability/Connection.php +++ b/src/Portability/Connection.php @@ -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 @@ public function connect() /** * {@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 @@ public function prepare(string $sql): DriverStatement return new Statement(parent::prepare($sql), $this->converter); } - public function query(string $sql): ResultStatement + public function query(string $sql): DriverResult { - return new Statement(parent::query($sql), $this->converter); + return $this->wrapResult( + parent::query($sql) + ); + } + + private function wrapResult(DriverResult $result): AbstractionResult + { + return new DBALResult( + new Result($result, $this->converter), + $this + ); } } diff --git a/src/Portability/Result.php b/src/Portability/Result.php new file mode 100644 index 00000000000..dd56c327d8d --- /dev/null +++ b/src/Portability/Result.php @@ -0,0 +1,97 @@ +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(); + } +} diff --git a/src/Portability/Statement.php b/src/Portability/Statement.php index eb01fd6dd81..c50dd30b722 100644 --- a/src/Portability/Statement.php +++ b/src/Portability/Statement.php @@ -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 Statement 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 @@ public function __construct($stmt, Converter $converter) */ 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 @@ public function bindParam($column, &$variable, $type = ParameterType::STRING, $l */ 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 ); } } diff --git a/src/Query/QueryBuilder.php b/src/Query/QueryBuilder.php index edf348f83ba..5884ef6e48f 100644 --- a/src/Query/QueryBuilder.php +++ b/src/Query/QueryBuilder.php @@ -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 @@ public function getState() * 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() { diff --git a/src/Result.php b/src/Result.php new file mode 100644 index 00000000000..f015eba032f --- /dev/null +++ b/src/Result.php @@ -0,0 +1,170 @@ +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> + * + * @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> + * + * @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 + * + * @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(); + } +} diff --git a/src/Statement.php b/src/Statement.php index 053aa783794..8b9196942f0 100644 --- a/src/Statement.php +++ b/src/Statement.php @@ -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 @@ * 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 @@ public function bindParam($name, &$var, $type = ParameterType::STRING, $length = * * @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 @@ public function execute($params = null) } 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) ); - } - - 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> - * - * @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> - * - * @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 - * - * @throws DBALException - */ - public function iterateColumn(): Traversable - { - try { - while (($value = $this->stmt->fetchOne()) !== false) { - yield $value; + } finally { + if ($logger !== null) { + $logger->stopQuery(); } - } 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->params = []; + $this->types = []; } - - $this->stmt->closeCursor(); } /** * Gets the wrapped driver statement. * - * @return \Doctrine\DBAL\Driver\Statement + * @return DriverStatement */ public function getWrappedStatement() { diff --git a/tests/Connection/LoggingTest.php b/tests/Connection/LoggingTest.php index 7ffc32b6e06..1f75cc49b3a 100644 --- a/tests/Connection/LoggingTest.php +++ b/tests/Connection/LoggingTest.php @@ -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'); diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 010e635c757..7a35d980b8c 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -4,7 +4,7 @@ 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\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 @@ public function testDeleteWithIsNull(): void ); } - 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 = ?'; - $params = [666]; - $types = [ParameterType::INTEGER]; - $result = []; - - $driverMock = $this->createMock(Driver::class); - - $driverMock->expects(self::any()) - ->method('connect') - ->will(self::returnValue( - $this->createMock(DriverConnection::class) - )); + $query = 'SELECT * FROM foo WHERE bar = ?'; + $params = [666]; + $types = [ParameterType::INTEGER]; - $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> + */ + 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 @@ public function testConnectionParamsArePassedToTheQueryCacheProfileInExecuteCach $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); } /** diff --git a/tests/Driver/AbstractMySQLDriverTest.php b/tests/Driver/AbstractMySQLDriverTest.php index a03e637d4ed..c6c1e1ebd12 100644 --- a/tests/Driver/AbstractMySQLDriverTest.php +++ b/tests/Driver/AbstractMySQLDriverTest.php @@ -5,7 +5,7 @@ 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 @@ public function testReturnsDatabaseName(): void '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 @@ public function testReturnsDatabaseName(): void $connection->expects(self::once()) ->method('query') - ->will(self::returnValue($statement)); + ->will(self::returnValue($result)); self::assertSame($database, $this->driver->getDatabase($connection)); } diff --git a/tests/Driver/AbstractPostgreSQLDriverTest.php b/tests/Driver/AbstractPostgreSQLDriverTest.php index ca429405750..d00bd11c847 100644 --- a/tests/Driver/AbstractPostgreSQLDriverTest.php +++ b/tests/Driver/AbstractPostgreSQLDriverTest.php @@ -5,7 +5,7 @@ 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 @@ public function testReturnsDatabaseName(): void '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 @@ public function testReturnsDatabaseName(): void $connection->expects(self::once()) ->method('query') - ->will(self::returnValue($statement)); + ->will(self::returnValue($result)); self::assertSame($database, $this->driver->getDatabase($connection)); } diff --git a/tests/Functional/DataAccessTest.php b/tests/Functional/DataAccessTest.php index f62b5d09aca..bd312a161bb 100644 --- a/tests/Functional/DataAccessTest.php +++ b/tests/Functional/DataAccessTest.php @@ -58,9 +58,9 @@ public function testPrepareWithBindValue(): void $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 @@ public function testPrepareWithBindParam(): void $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 @@ public function testPrepareWithFetchAllAssociative(): void $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 @@ public function testPrepareWithFetchOne(): void $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 @@ public function testPrepareWithExecuteParams(): void $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 @@ public function testFetchOneWithMissingTypes(): void */ 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 @@ public function testPrepareQueryBindValueDateTimeType(): void $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 @@ public function testNativeArrayListSupport(): void $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 @@ public function testBitComparisonExpressionSupport(): void . ', ' . $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 @@ public function testEmptyFetchOneReturnsFalse(): void 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); } diff --git a/tests/Functional/Driver/OCI8/StatementTest.php b/tests/Functional/Driver/OCI8/StatementTest.php index b5d42c0df5d..680d272a9d7 100644 --- a/tests/Functional/Driver/OCI8/StatementTest.php +++ b/tests/Functional/Driver/OCI8/StatementTest.php @@ -48,12 +48,11 @@ public function testQueryConversion(string $query, array $params, array $expecte */ 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() ); } diff --git a/tests/Functional/Driver/PDOPgSql/DriverTest.php b/tests/Functional/Driver/PDOPgSql/DriverTest.php index a05b8133cd8..e5db1a085c2 100644 --- a/tests/Functional/Driver/PDOPgSql/DriverTest.php +++ b/tests/Functional/Driver/PDOPgSql/DriverTest.php @@ -83,10 +83,9 @@ public function testConnectsWithApplicationNameParameter(): void $connection = $this->driver->connect($parameters, $user, $password); - $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(); + $hash = microtime(true); // required to identify the record in the results uniquely + $sql = sprintf('SELECT * FROM pg_stat_activity WHERE %d = %d', $hash, $hash); + $records = $connection->query($sql)->fetchAllAssociative(); foreach ($records as $record) { // The query column is named "current_query" on PostgreSQL < 9.2 diff --git a/tests/Functional/LikeWildcardsEscapingTest.php b/tests/Functional/LikeWildcardsEscapingTest.php index 5a7078e06b4..d283a2ad63f 100644 --- a/tests/Functional/LikeWildcardsEscapingTest.php +++ b/tests/Functional/LikeWildcardsEscapingTest.php @@ -13,7 +13,8 @@ public function testFetchLikeExpressionResult(): void $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 @@ public function testFetchLikeExpressionResult(): void $escapeChar ) ) - ); - $stmt->execute(); - self::assertTrue((bool) $stmt->fetchOne()); + )->execute(); + + self::assertTrue((bool) $result->fetchOne()); } } diff --git a/tests/Functional/MasterSlaveConnectionTest.php b/tests/Functional/MasterSlaveConnectionTest.php index 6e442fecc44..b465d531f7c 100644 --- a/tests/Functional/MasterSlaveConnectionTest.php +++ b/tests/Functional/MasterSlaveConnectionTest.php @@ -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 @@ public function testQueryOnMaster(): void $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 @@ public function testQueryOnSlave(): void $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]); diff --git a/tests/Functional/NamedParametersTest.php b/tests/Functional/NamedParametersTest.php index fc6f50f384a..07ef1063b46 100644 --- a/tests/Functional/NamedParametersTest.php +++ b/tests/Functional/NamedParametersTest.php @@ -213,13 +213,12 @@ protected function setUp(): void */ 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); } } diff --git a/tests/Functional/PortabilityTest.php b/tests/Functional/PortabilityTest.php index 1b9a47e8db3..7c9b597c3f4 100644 --- a/tests/Functional/PortabilityTest.php +++ b/tests/Functional/PortabilityTest.php @@ -66,16 +66,17 @@ public function testFullFetchMode(): void $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 @@ public function testConnFetchMode(): void $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); } } @@ -133,10 +135,10 @@ public function assertFetchResultRow(array $row): void */ public function testfetchColumn(string $field, array $expected): void { - $conn = $this->getPortableConnection(); - $stmt = $conn->query('SELECT ' . $field . ' FROM portability_table'); + $conn = $this->getPortableConnection(); + $result = $conn->query('SELECT ' . $field . ' FROM portability_table'); - $column = $stmt->fetchFirstColumn(); + $column = $result->fetchFirstColumn(); self::assertEquals($expected, $column); } @@ -159,10 +161,10 @@ public static function fetchColumnProvider(): iterable public function testFetchAllNullColumn(): void { - $conn = $this->getPortableConnection(); - $stmt = $conn->query('SELECT Test_Null FROM portability_table'); + $conn = $this->getPortableConnection(); + $result = $conn->query('SELECT Test_Null FROM portability_table'); - $column = $stmt->fetchFirstColumn(); + $column = $result->fetchFirstColumn(); self::assertSame([null, null], $column); } } diff --git a/tests/Functional/ResultCacheTest.php b/tests/Functional/ResultCacheTest.php index 1be834fe23d..56f84a8eb1b 100644 --- a/tests/Functional/ResultCacheTest.php +++ b/tests/Functional/ResultCacheTest.php @@ -4,7 +4,7 @@ use Doctrine\Common\Cache\ArrayCache; use Doctrine\DBAL\Cache\QueryCacheProfile; -use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Tests\FunctionalTestCase; @@ -61,8 +61,8 @@ public function testCacheFetchAssociative(): void { $this->assertCacheNonCacheSelectSameFetchModeAreEqual( $this->expectedResult, - static function (ResultStatement $stmt) { - return $stmt->fetchAssociative(); + static function (Result $result) { + return $result->fetchAssociative(); } ); } @@ -76,8 +76,8 @@ public function testFetchNumeric(): void $this->assertCacheNonCacheSelectSameFetchModeAreEqual( $expectedResult, - static function (ResultStatement $stmt) { - return $stmt->fetchNumeric(); + static function (Result $result) { + return $result->fetchNumeric(); } ); } @@ -91,8 +91,8 @@ public function testFetchOne(): void $this->assertCacheNonCacheSelectSameFetchModeAreEqual( $expectedResult, - static function (ResultStatement $stmt) { - return $stmt->fetchOne(); + static function (Result $result) { + return $result->fetchOne(); } ); } @@ -106,16 +106,16 @@ public function testMixingFetch(): void $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); - $data = $this->hydrateViaFetchAll($stmt, static function (ResultStatement $stmt) { - return $stmt->fetchAllAssociative(); + $data = $this->hydrateViaFetchAll($stmt, static function (Result $result) { + return $result->fetchAllAssociative(); }); self::assertEquals($this->expectedResult, $data); $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); - $data = $this->hydrateViaFetchAll($stmt, static function (ResultStatement $stmt) { - return $stmt->fetchAllNumeric(); + $data = $this->hydrateViaFetchAll($stmt, static function (Result $result) { + return $result->fetchAllNumeric(); }); self::assertEquals($numExpectedResult, $data); @@ -137,19 +137,15 @@ public function testFetchViaIteration(callable $fetch, callable $fetchAll): void public function testFetchAndFinishSavesCache(): void { - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); - - $data = []; + $result = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); - while (($row = $stmt->fetchAssociative()) !== false) { + while (($row = $result->fetchAssociative()) !== false) { $data[] = $row; } - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); + $result = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); - $data = []; - - while (($row = $stmt->fetchNumeric()) !== false) { + while (($row = $result->fetchNumeric()) !== false) { $data[] = $row; } @@ -158,14 +154,14 @@ public function testFetchAndFinishSavesCache(): void public function testDontFinishNoCache(): void { - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); + $result = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); - $stmt->fetchAssociative(); + $result->fetchAssociative(); - $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); + $result = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey')); - $this->hydrateViaIteration($stmt, static function (ResultStatement $stmt) { - return $stmt->fetchNumeric(); + $this->hydrateViaIteration($result, static function (Result $result) { + return $result->fetchNumeric(); }); self::assertCount(2, $this->sqlLogger->queries); @@ -174,8 +170,13 @@ public function testDontFinishNoCache(): void public function testFetchAllSavesCache(): void { $layerCache = new ArrayCache(); - $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'testcachekey', $layerCache)); - $stmt->fetchAllAssociative(); + $result = $this->connection->executeQuery( + 'SELECT * FROM caching WHERE test_int > 500', + [], + [], + new QueryCacheProfile(0, 'testcachekey', $layerCache) + ); + $result->fetchAllAssociative(); self::assertCount(1, $layerCache->fetch('testcachekey')); } @@ -187,12 +188,12 @@ public function testFetchColumn(): void $qcp = new QueryCacheProfile(0, 0, new ArrayCache()); - $stmt = $this->connection->executeCacheQuery($query, [], [], $qcp); - $stmt->fetchFirstColumn(); + $result = $this->connection->executeCacheQuery($query, [], [], $qcp); + $result->fetchFirstColumn(); - $stmt = $this->connection->executeCacheQuery($query, [], [], $qcp); + $query = $this->connection->executeCacheQuery($query, [], [], $qcp); - self::assertEquals([1], $stmt->fetchFirstColumn()); + self::assertEquals([1], $query->fetchFirstColumn()); } /** @@ -217,13 +218,13 @@ private function assertCacheNonCacheSelectSameFetchModeAreEqual(array $expectedR public function testEmptyResultCache(): void { $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'emptycachekey')); - $this->hydrateViaIteration($stmt, static function (ResultStatement $stmt) { - return $stmt->fetchAssociative(); + $this->hydrateViaIteration($stmt, static function (Result $result) { + return $result->fetchAssociative(); }); $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'emptycachekey')); - $this->hydrateViaIteration($stmt, static function (ResultStatement $stmt) { - return $stmt->fetchAssociative(); + $this->hydrateViaIteration($stmt, static function (Result $result) { + return $result->fetchAssociative(); }); self::assertCount(1, $this->sqlLogger->queries, 'just one dbal hit'); @@ -232,15 +233,15 @@ public function testEmptyResultCache(): void public function testChangeCacheImpl(): void { $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'emptycachekey')); - $this->hydrateViaIteration($stmt, static function (ResultStatement $stmt) { - return $stmt->fetchAssociative(); + $this->hydrateViaIteration($stmt, static function (Result $result) { + return $result->fetchAssociative(); }); $secondCache = new ArrayCache(); $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'emptycachekey', $secondCache)); - $this->hydrateViaIteration($stmt, static function (ResultStatement $stmt) { - return $stmt->fetchAssociative(); + $this->hydrateViaIteration($stmt, static function (Result $result) { + return $result->fetchAssociative(); }); self::assertCount(2, $this->sqlLogger->queries, 'two hits'); @@ -253,29 +254,29 @@ public function testChangeCacheImpl(): void public static function fetchProvider(): iterable { yield 'associative' => [ - static function (ResultStatement $stmt) { - return $stmt->fetchAssociative(); + static function (Result $result) { + return $result->fetchAssociative(); }, - static function (ResultStatement $stmt) { - return $stmt->fetchAllAssociative(); + static function (Result $result) { + return $result->fetchAllAssociative(); }, ]; yield 'numeric' => [ - static function (ResultStatement $stmt) { - return $stmt->fetchNumeric(); + static function (Result $result) { + return $result->fetchNumeric(); }, - static function (ResultStatement $stmt) { - return $stmt->fetchAllNumeric(); + static function (Result $result) { + return $result->fetchAllNumeric(); }, ]; yield 'column' => [ - static function (ResultStatement $stmt) { - return $stmt->fetchOne(); + static function (Result $result) { + return $result->fetchOne(); }, - static function (ResultStatement $stmt) { - return $stmt->fetchFirstColumn(); + static function (Result $result) { + return $result->fetchFirstColumn(); }, ]; } @@ -283,11 +284,11 @@ static function (ResultStatement $stmt) { /** * @return array */ - private function hydrateViaFetchAll(ResultStatement $stmt, callable $fetchAll): array + private function hydrateViaFetchAll(Result $result, callable $fetchAll): array { $data = []; - foreach ($fetchAll($stmt) as $row) { + foreach ($fetchAll($result) as $row) { $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row; } @@ -297,11 +298,11 @@ private function hydrateViaFetchAll(ResultStatement $stmt, callable $fetchAll): /** * @return array */ - private function hydrateViaIteration(ResultStatement $stmt, callable $fetch): array + private function hydrateViaIteration(Result $result, callable $fetch): array { $data = []; - while (($row = $fetch($stmt)) !== false) { + while (($row = $fetch($result)) !== false) { $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row; } diff --git a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php index 28f00a0eaa1..aa92bf9ab0d 100644 --- a/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php +++ b/tests/Functional/Schema/SchemaManagerFunctionalTestCase.php @@ -1566,17 +1566,17 @@ public function testPrimaryKeyAutoIncrement(): void $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); } diff --git a/tests/Functional/Schema/SqliteSchemaManagerTest.php b/tests/Functional/Schema/SqliteSchemaManagerTest.php index caa4ddb8a3f..0ea1a4e4433 100644 --- a/tests/Functional/Schema/SqliteSchemaManagerTest.php +++ b/tests/Functional/Schema/SqliteSchemaManagerTest.php @@ -273,9 +273,9 @@ public function testPrimaryKeyNoAutoIncrement(): void $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); diff --git a/tests/Functional/StatementTest.php b/tests/Functional/StatementTest.php index abf2356b36f..ee01f17eb8c 100644 --- a/tests/Functional/StatementTest.php +++ b/tests/Functional/StatementTest.php @@ -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 @@ protected function setUp(): void $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 @@ public function testStatementIsReusableAfterClosingCursor(): void $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 @@ -67,11 +66,11 @@ public function testReuseStatementWithLongerResults(): void ]; $this->connection->insert('stmt_longer_results', $row1); - $stmt = $this->connection->prepare('SELECT param, val FROM stmt_longer_results ORDER BY param'); - $stmt->execute(); + $stmt = $this->connection->prepare('SELECT param, val FROM stmt_longer_results ORDER BY param'); + $result = $stmt->execute(); self::assertEquals([ ['param1', 'X'], - ], $stmt->fetchAllNumeric()); + ], $result->fetchAllNumeric()); $row2 = [ 'param' => 'param2', @@ -79,11 +78,11 @@ public function testReuseStatementWithLongerResults(): void ]; $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 @@ public function testFetchLongBlob(): void $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() ); @@ -139,19 +138,20 @@ public function testIncompletelyFetchedStatementDoesNotBlockConnection(): void $this->connection->insert('stmt_test', ['id' => 1]); $this->connection->insert('stmt_test', ['id' => 2]); - $stmt1 = $this->connection->prepare('SELECT id FROM stmt_test'); - $stmt1->execute(); - $stmt1->fetchAssociative(); - $stmt1->execute(); + $stmt1 = $this->connection->prepare('SELECT id FROM stmt_test'); + $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()); + $stmt2 = $this->connection->prepare('SELECT id FROM stmt_test WHERE id = ?'); + $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 @@ public function testReuseStatementAfterClosingCursor(): void $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 @@ public function testReuseStatementWithParameterBoundByReference(): void $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 @@ public function testReuseStatementWithReboundValue(): void $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,44 +219,13 @@ public function testReuseStatementWithReboundParam(): void $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()); - } - - /** - * @param mixed $expected - * - * @dataProvider emptyFetchProvider - */ - public function testFetchFromNonExecutedStatement(callable $fetch, $expected): void - { - $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); - - self::assertSame($expected, $fetch($stmt)); - } - - public function testCloseCursorOnNonExecutedStatement(): void - { - $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); - self::assertTrue($stmt->closeCursor()); - } - - /** - * @group DBAL-2637 - */ - public function testCloseCursorAfterCursorEnd(): void - { - $stmt = $this->connection->prepare('SELECT name FROM stmt_test'); - - $stmt->execute(); - $stmt->fetchAssociative(); - - self::assertTrue($stmt->closeCursor()); + $result = $stmt->execute(); + self::assertEquals(2, $result->fetchOne()); } /** @@ -260,28 +233,25 @@ public function testCloseCursorAfterCursorEnd(): void * * @dataProvider emptyFetchProvider */ - public function testFetchFromNonExecutedStatementWithClosedCursor(callable $fetch, $expected): void + public function testFetchFromExecutedStatementWithFreedResult(callable $fetch, $expected): void { - $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); - $stmt->closeCursor(); + $this->connection->insert('stmt_test', ['id' => 1]); - self::assertSame($expected, $fetch($stmt)); - } + $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); + $result = $stmt->execute(); + $result->free(); - /** - * @param mixed $expected - * - * @dataProvider emptyFetchProvider - */ - public function testFetchFromExecutedStatementWithClosedCursor(callable $fetch, $expected): void - { - $this->connection->insert('stmt_test', ['id' => 1]); + try { + $value = $fetch($result); + } catch (Throwable $e) { + // The drivers that enforce the command sequencing internally will throw an exception + $this->expectNotToPerformAssertions(); - $stmt = $this->connection->prepare('SELECT id FROM stmt_test'); - $stmt->execute(); - $stmt->closeCursor(); + return; + } - self::assertSame($expected, $fetch($stmt)); + // Other drivers will silently return an empty result + self::assertSame($expected, $value); } /** @@ -291,20 +261,20 @@ public static function emptyFetchProvider(): iterable { 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(); }, [], ], diff --git a/tests/Functional/Ticket/DBAL421Test.php b/tests/Functional/Ticket/DBAL421Test.php index 9779f016dbe..8ed1bd45021 100644 --- a/tests/Functional/Ticket/DBAL421Test.php +++ b/tests/Functional/Ticket/DBAL421Test.php @@ -41,13 +41,10 @@ public function testGuidShouldBeRandom(): void $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 diff --git a/tests/Functional/WriteTest.php b/tests/Functional/WriteTest.php index 793c5631e67..51b0ec58a6a 100644 --- a/tests/Functional/WriteTest.php +++ b/tests/Functional/WriteTest.php @@ -74,24 +74,22 @@ public function testPrepareRowCountReturnsAffectedRows(): void $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 @@ public function testPrepareWithDbalTypes(): void 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 @@ public function testPrepareWithDbalTypeNames(): void 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 @@ public function testLastInsertIdSequence(): void 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'); diff --git a/tests/Portability/ResultTest.php b/tests/Portability/ResultTest.php new file mode 100644 index 00000000000..30a3bfb22c4 --- /dev/null +++ b/tests/Portability/ResultTest.php @@ -0,0 +1,54 @@ +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) + ); + } +} diff --git a/tests/Portability/StatementTest.php b/tests/Portability/StatementTest.php index c34f2ddf25f..d69074db52d 100644 --- a/tests/Portability/StatementTest.php +++ b/tests/Portability/StatementTest.php @@ -57,26 +57,6 @@ public function testBindValue(): void 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 @@ public function testExecute(): void $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); } /**