Commit 7e93a36e authored by Sergei Morozov's avatar Sergei Morozov Committed by Marco Pivetta

[DBAL-2546] Prevent exception when fetching from closed cursor

parent be045c2c
......@@ -48,6 +48,13 @@ class DB2Statement implements \IteratorAggregate, Statement
*/
private $_defaultFetchMode = \PDO::FETCH_BOTH;
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
*
......@@ -105,7 +112,13 @@ class DB2Statement implements \IteratorAggregate, Statement
$this->_bindParam = array();
return db2_free_result($this->_stmt);
if (!db2_free_result($this->_stmt)) {
return false;
}
$this->result = false;
return true;
}
/**
......@@ -164,6 +177,8 @@ class DB2Statement implements \IteratorAggregate, Statement
throw new DB2Exception(db2_stmt_errormsg());
}
$this->result = true;
return $retval;
}
......@@ -194,6 +209,12 @@ class DB2Statement implements \IteratorAggregate, Statement
*/
public function fetch($fetchMode = null)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (!$this->result) {
return false;
}
$fetchMode = $fetchMode ?: $this->_defaultFetchMode;
switch ($fetchMode) {
case \PDO::FETCH_BOTH:
......
......@@ -80,6 +80,13 @@ class MysqliStatement implements \IteratorAggregate, Statement
*/
protected $_defaultFetchMode = PDO::FETCH_BOTH;
/**
* Indicates whether the statement is in the state when fetching results is possible
*
* @var bool
*/
private $result = false;
/**
* @param \mysqli $conn
* @param string $prepareString
......@@ -209,6 +216,8 @@ class MysqliStatement implements \IteratorAggregate, Statement
}
}
$this->result = true;
return true;
}
......@@ -256,6 +265,12 @@ class MysqliStatement implements \IteratorAggregate, Statement
*/
public function fetch($fetchMode = null)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (!$this->result) {
return false;
}
$values = $this->_fetch();
if (null === $values) {
return false;
......@@ -341,6 +356,7 @@ class MysqliStatement implements \IteratorAggregate, Statement
public function closeCursor()
{
$this->_stmt->free_result();
$this->result = false;
return true;
}
......
......@@ -80,6 +80,13 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/
private $boundValues = array();
/**
* 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.
*
......@@ -176,12 +183,19 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/
public function closeCursor()
{
// not having the result means there's nothing to close
if (!$this->result) {
return true;
}
// 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 (oci_fetch($this->_sth));
$this->result = false;
return true;
}
......@@ -235,6 +249,8 @@ class OCI8Statement implements \IteratorAggregate, Statement
throw OCI8Exception::fromErrorInfo($this->errorInfo());
}
$this->result = true;
return $ret;
}
......@@ -263,6 +279,12 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/
public function fetch($fetchMode = null)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (!$this->result) {
return false;
}
$fetchMode = $fetchMode ?: $this->_defaultFetchMode;
if ( ! isset(self::$fetchModeMap[$fetchMode])) {
throw new \InvalidArgumentException("Invalid fetch style: " . $fetchMode);
......@@ -292,6 +314,12 @@ class OCI8Statement implements \IteratorAggregate, Statement
$fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN;
}
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (!$this->result) {
return array();
}
oci_fetch_all($this->_sth, $result, 0, -1,
self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS);
......@@ -308,6 +336,12 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/
public function fetchColumn($columnIndex = 0)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (!$this->result) {
return false;
}
$row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
if (false === $row) {
......
......@@ -98,6 +98,13 @@ class SQLSrvStatement implements IteratorAggregate, Statement
*/
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.
*
......@@ -150,12 +157,19 @@ class SQLSrvStatement implements IteratorAggregate, Statement
*/
public function closeCursor()
{
// not having the result means there's nothing to close
if (!$this->result) {
return true;
}
// 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 true;
}
......@@ -211,6 +225,8 @@ class SQLSrvStatement implements IteratorAggregate, Statement
sqlsrv_fetch($this->stmt);
$this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
}
$this->result = true;
}
/**
......@@ -240,6 +256,12 @@ class SQLSrvStatement implements IteratorAggregate, Statement
*/
public function fetch($fetchMode = null)
{
// do not try fetching from the statement if it's not expected to contain result
// in order to prevent exceptional situation
if (!$this->result) {
return false;
}
$args = func_get_args();
$fetchMode = $fetchMode ?: $this->defaultFetchMode;
......
......@@ -44,15 +44,7 @@ class StatementTest extends \Doctrine\Tests\DbalFunctionalTestCase
$stmt->execute();
$stmt->closeCursor();
try {
$value = $stmt->fetchColumn();
} catch (\Exception $e) {
// some adapters trigger PHP error or throw adapter-specific exception in case of fetching
// from a closed cursor, which still proves that it has been closed
return;
}
$this->assertFalse($value);
$this->assertFalse($stmt->fetchColumn());
}
public function testReuseStatementWithLongerResults()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment