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 ...@@ -48,6 +48,13 @@ class DB2Statement implements \IteratorAggregate, Statement
*/ */
private $_defaultFetchMode = \PDO::FETCH_BOTH; 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 * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG
* *
...@@ -105,7 +112,13 @@ class DB2Statement implements \IteratorAggregate, Statement ...@@ -105,7 +112,13 @@ class DB2Statement implements \IteratorAggregate, Statement
$this->_bindParam = array(); $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 ...@@ -164,6 +177,8 @@ class DB2Statement implements \IteratorAggregate, Statement
throw new DB2Exception(db2_stmt_errormsg()); throw new DB2Exception(db2_stmt_errormsg());
} }
$this->result = true;
return $retval; return $retval;
} }
...@@ -194,6 +209,12 @@ class DB2Statement implements \IteratorAggregate, Statement ...@@ -194,6 +209,12 @@ class DB2Statement implements \IteratorAggregate, Statement
*/ */
public function fetch($fetchMode = null) 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; $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
switch ($fetchMode) { switch ($fetchMode) {
case \PDO::FETCH_BOTH: case \PDO::FETCH_BOTH:
......
...@@ -80,6 +80,13 @@ class MysqliStatement implements \IteratorAggregate, Statement ...@@ -80,6 +80,13 @@ class MysqliStatement implements \IteratorAggregate, Statement
*/ */
protected $_defaultFetchMode = PDO::FETCH_BOTH; 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 \mysqli $conn
* @param string $prepareString * @param string $prepareString
...@@ -209,6 +216,8 @@ class MysqliStatement implements \IteratorAggregate, Statement ...@@ -209,6 +216,8 @@ class MysqliStatement implements \IteratorAggregate, Statement
} }
} }
$this->result = true;
return true; return true;
} }
...@@ -256,6 +265,12 @@ class MysqliStatement implements \IteratorAggregate, Statement ...@@ -256,6 +265,12 @@ class MysqliStatement implements \IteratorAggregate, Statement
*/ */
public function fetch($fetchMode = null) 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(); $values = $this->_fetch();
if (null === $values) { if (null === $values) {
return false; return false;
...@@ -341,6 +356,7 @@ class MysqliStatement implements \IteratorAggregate, Statement ...@@ -341,6 +356,7 @@ class MysqliStatement implements \IteratorAggregate, Statement
public function closeCursor() public function closeCursor()
{ {
$this->_stmt->free_result(); $this->_stmt->free_result();
$this->result = false;
return true; return true;
} }
......
...@@ -80,6 +80,13 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -80,6 +80,13 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/ */
private $boundValues = array(); 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. * Creates a new OCI8Statement that uses the given connection handle and SQL statement.
* *
...@@ -176,12 +183,19 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -176,12 +183,19 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/ */
public function closeCursor() 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 // 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 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 // @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 // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
while (oci_fetch($this->_sth)); while (oci_fetch($this->_sth));
$this->result = false;
return true; return true;
} }
...@@ -235,6 +249,8 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -235,6 +249,8 @@ class OCI8Statement implements \IteratorAggregate, Statement
throw OCI8Exception::fromErrorInfo($this->errorInfo()); throw OCI8Exception::fromErrorInfo($this->errorInfo());
} }
$this->result = true;
return $ret; return $ret;
} }
...@@ -263,6 +279,12 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -263,6 +279,12 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/ */
public function fetch($fetchMode = null) 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; $fetchMode = $fetchMode ?: $this->_defaultFetchMode;
if ( ! isset(self::$fetchModeMap[$fetchMode])) { if ( ! isset(self::$fetchModeMap[$fetchMode])) {
throw new \InvalidArgumentException("Invalid fetch style: " . $fetchMode); throw new \InvalidArgumentException("Invalid fetch style: " . $fetchMode);
...@@ -292,6 +314,12 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -292,6 +314,12 @@ class OCI8Statement implements \IteratorAggregate, Statement
$fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN; $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, oci_fetch_all($this->_sth, $result, 0, -1,
self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS); self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS);
...@@ -308,6 +336,12 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -308,6 +336,12 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/ */
public function fetchColumn($columnIndex = 0) 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); $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS);
if (false === $row) { if (false === $row) {
......
...@@ -98,6 +98,13 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -98,6 +98,13 @@ class SQLSrvStatement implements IteratorAggregate, Statement
*/ */
private $lastInsertId; 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. * Append to any INSERT query to retrieve the last insert id.
* *
...@@ -150,12 +157,19 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -150,12 +157,19 @@ class SQLSrvStatement implements IteratorAggregate, Statement
*/ */
public function closeCursor() 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 // 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 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 // @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 // deliberately do not consider multiple result sets, since doctrine/dbal doesn't support them
while (sqlsrv_fetch($this->stmt)); while (sqlsrv_fetch($this->stmt));
$this->result = false;
return true; return true;
} }
...@@ -211,6 +225,8 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -211,6 +225,8 @@ class SQLSrvStatement implements IteratorAggregate, Statement
sqlsrv_fetch($this->stmt); sqlsrv_fetch($this->stmt);
$this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0)); $this->lastInsertId->setId(sqlsrv_get_field($this->stmt, 0));
} }
$this->result = true;
} }
/** /**
...@@ -240,6 +256,12 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -240,6 +256,12 @@ class SQLSrvStatement implements IteratorAggregate, Statement
*/ */
public function fetch($fetchMode = null) 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(); $args = func_get_args();
$fetchMode = $fetchMode ?: $this->defaultFetchMode; $fetchMode = $fetchMode ?: $this->defaultFetchMode;
......
...@@ -44,15 +44,7 @@ class StatementTest extends \Doctrine\Tests\DbalFunctionalTestCase ...@@ -44,15 +44,7 @@ class StatementTest extends \Doctrine\Tests\DbalFunctionalTestCase
$stmt->execute(); $stmt->execute();
$stmt->closeCursor(); $stmt->closeCursor();
try { $this->assertFalse($stmt->fetchColumn());
$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);
} }
public function testReuseStatementWithLongerResults() 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