Skip to content

Commit

Permalink
Merge branch 'fix/#2546-prepared-statement-close-cursor-invalidation-…
Browse files Browse the repository at this point in the history
…2.5' into 2.5

Backport #2546 to 2.5.x
  • Loading branch information
Ocramius committed Feb 4, 2017
2 parents fc376f7 + 658e325 commit b713ba7
Show file tree
Hide file tree
Showing 9 changed files with 499 additions and 37 deletions.
40 changes: 29 additions & 11 deletions lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -104,11 +111,14 @@ public function closeCursor()
}

$this->_bindParam = array();
db2_free_result($this->_stmt);
$ret = db2_free_stmt($this->_stmt);
$this->_stmt = false;

return $ret;
if (!db2_free_result($this->_stmt)) {
return false;
}

$this->result = false;

return true;
}

/**
Expand Down Expand Up @@ -151,22 +161,24 @@ public function execute($params = null)
return false;
}

/*$retval = true;
if ($params !== null) {
$retval = @db2_execute($this->_stmt, $params);
} else {
$retval = @db2_execute($this->_stmt);
}*/
if ($params === null) {
ksort($this->_bindParam);
$params = array_values($this->_bindParam);

$params = array();

foreach ($this->_bindParam as $column => $value) {
$params[] = $value;
}
}

$retval = @db2_execute($this->_stmt, $params);

if ($retval === false) {
throw new DB2Exception(db2_stmt_errormsg());
}

$this->result = true;

return $retval;
}

Expand Down Expand Up @@ -197,6 +209,12 @@ public function getIterator()
*/
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:
Expand Down
58 changes: 45 additions & 13 deletions lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -168,31 +175,49 @@ public function execute($params = null)
if (null === $this->_columnNames) {
$meta = $this->_stmt->result_metadata();
if (false !== $meta) {
// We have a result.
$this->_stmt->store_result();

$columnNames = array();
foreach ($meta->fetch_fields() as $col) {
$columnNames[] = $col->name;
}
$meta->free();

$this->_columnNames = $columnNames;
$this->_rowBindedValues = array_fill(0, count($columnNames), null);

$refs = array();
foreach ($this->_rowBindedValues as $key => &$value) {
$refs[$key] =& $value;
}

if (!call_user_func_array(array($this->_stmt, 'bind_result'), $refs)) {
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
}
} else {
$this->_columnNames = false;
}
}

if (false !== $this->_columnNames) {
// 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 = array();
foreach ($this->_rowBindedValues as $key => &$value) {
$refs[$key] =& $value;
}

if (!call_user_func_array(array($this->_stmt, 'bind_result'), $refs)) {
throw new MysqliException($this->_stmt->error, $this->_stmt->sqlstate, $this->_stmt->errno);
}
}

$this->result = true;

return true;
}

Expand Down Expand Up @@ -240,6 +265,12 @@ private function _fetch()
*/
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;
Expand Down Expand Up @@ -325,6 +356,7 @@ public function errorInfo()
public function closeCursor()
{
$this->_stmt->free_result();
$this->result = false;

return true;
}
Expand Down
42 changes: 41 additions & 1 deletion lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -176,7 +183,20 @@ public function bindParam($column, &$variable, $type = null, $length = null)
*/
public function closeCursor()
{
return oci_free_statement($this->_sth);
// 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;
}

/**
Expand Down Expand Up @@ -229,6 +249,8 @@ public function execute($params = null)
throw OCI8Exception::fromErrorInfo($this->errorInfo());
}

$this->result = true;

return $ret;
}

Expand Down Expand Up @@ -257,6 +279,12 @@ public function getIterator()
*/
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);
Expand Down Expand Up @@ -286,6 +314,12 @@ public function fetchAll($fetchMode = null)
$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);

Expand All @@ -302,6 +336,12 @@ public function fetchAll($fetchMode = null)
*/
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) {
Expand Down
9 changes: 9 additions & 0 deletions lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
*/
class Connection extends PDOConnection implements \Doctrine\DBAL\Driver\Connection
{
/**
* {@inheritdoc}
*/
public function __construct($dsn, $user = null, $password = null, array $options = null)
{
parent::__construct($dsn, $user, $password, $options);
$this->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array(Statement::class, array()));
}

/**
* @override
*/
Expand Down
49 changes: 49 additions & 0 deletions lib/Doctrine/DBAL/Driver/PDOSqlsrv/Statement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/

namespace Doctrine\DBAL\Driver\PDOSqlsrv;

use Doctrine\DBAL\Driver\PDOStatement;
use PDO;

/**
* PDO SQL Server Statement
*/
class Statement extends PDOStatement
{
/**
* {@inheritdoc}
*/
public function bindParam($column, &$variable, $type = PDO::PARAM_STR, $length = null, $driverOptions = null)
{
if ($type === PDO::PARAM_LOB && $driverOptions === null) {
$driverOptions = PDO::SQLSRV_ENCODING_BINARY;
}

return parent::bindParam($column, $variable, $type, $length, $driverOptions);
}

/**
* {@inheritdoc}
*/
public function bindValue($param, $value, $type = PDO::PARAM_STR)
{
return $this->bindParam($param, $value, $type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public function bindValue($param, $value, $type = null)
*/
public function closeCursor()
{
if ( ! sasql_stmt_free_result($this->stmt)) {
if (!sasql_stmt_reset($this->stmt)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
}

Expand Down
Loading

0 comments on commit b713ba7

Please sign in to comment.