Commit b713ba74 authored by Marco Pivetta's avatar Marco Pivetta

Merge branch 'fix/#2546-prepared-statement-close-cursor-invalidation-2.5' into 2.5

Backport #2546 to 2.5.x
parents fc376f7a 658e3257
...@@ -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
* *
...@@ -104,11 +111,14 @@ class DB2Statement implements \IteratorAggregate, Statement ...@@ -104,11 +111,14 @@ class DB2Statement implements \IteratorAggregate, Statement
} }
$this->_bindParam = array(); $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;
} }
/** /**
...@@ -151,22 +161,24 @@ class DB2Statement implements \IteratorAggregate, Statement ...@@ -151,22 +161,24 @@ class DB2Statement implements \IteratorAggregate, Statement
return false; return false;
} }
/*$retval = true;
if ($params !== null) {
$retval = @db2_execute($this->_stmt, $params);
} else {
$retval = @db2_execute($this->_stmt);
}*/
if ($params === null) { if ($params === null) {
ksort($this->_bindParam); ksort($this->_bindParam);
$params = array_values($this->_bindParam);
$params = array();
foreach ($this->_bindParam as $column => $value) {
$params[] = $value;
}
} }
$retval = @db2_execute($this->_stmt, $params); $retval = @db2_execute($this->_stmt, $params);
if ($retval === false) { if ($retval === false) {
throw new DB2Exception(db2_stmt_errormsg()); throw new DB2Exception(db2_stmt_errormsg());
} }
$this->result = true;
return $retval; return $retval;
} }
...@@ -197,6 +209,12 @@ class DB2Statement implements \IteratorAggregate, Statement ...@@ -197,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
...@@ -168,9 +175,6 @@ class MysqliStatement implements \IteratorAggregate, Statement ...@@ -168,9 +175,6 @@ class MysqliStatement implements \IteratorAggregate, Statement
if (null === $this->_columnNames) { if (null === $this->_columnNames) {
$meta = $this->_stmt->result_metadata(); $meta = $this->_stmt->result_metadata();
if (false !== $meta) { if (false !== $meta) {
// We have a result.
$this->_stmt->store_result();
$columnNames = array(); $columnNames = array();
foreach ($meta->fetch_fields() as $col) { foreach ($meta->fetch_fields() as $col) {
$columnNames[] = $col->name; $columnNames[] = $col->name;
...@@ -178,21 +182,42 @@ class MysqliStatement implements \IteratorAggregate, Statement ...@@ -178,21 +182,42 @@ class MysqliStatement implements \IteratorAggregate, Statement
$meta->free(); $meta->free();
$this->_columnNames = $columnNames; $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 { } else {
$this->_columnNames = false; $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; return true;
} }
...@@ -240,6 +265,12 @@ class MysqliStatement implements \IteratorAggregate, Statement ...@@ -240,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;
...@@ -325,6 +356,7 @@ class MysqliStatement implements \IteratorAggregate, Statement ...@@ -325,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,7 +183,20 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -176,7 +183,20 @@ class OCI8Statement implements \IteratorAggregate, Statement
*/ */
public function closeCursor() 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;
} }
/** /**
...@@ -229,6 +249,8 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -229,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;
} }
...@@ -257,6 +279,12 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -257,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);
...@@ -286,6 +314,12 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -286,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);
...@@ -302,6 +336,12 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -302,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) {
......
...@@ -28,6 +28,15 @@ use Doctrine\DBAL\Driver\PDOConnection; ...@@ -28,6 +28,15 @@ use Doctrine\DBAL\Driver\PDOConnection;
*/ */
class Connection extends PDOConnection implements \Doctrine\DBAL\Driver\Connection 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 * @override
*/ */
......
<?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);
}
}
...@@ -131,7 +131,7 @@ class SQLAnywhereStatement implements IteratorAggregate, Statement ...@@ -131,7 +131,7 @@ class SQLAnywhereStatement implements IteratorAggregate, Statement
*/ */
public function closeCursor() public function closeCursor()
{ {
if ( ! sasql_stmt_free_result($this->stmt)) { if (!sasql_stmt_reset($this->stmt)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
} }
......
...@@ -53,11 +53,18 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -53,11 +53,18 @@ class SQLSrvStatement implements IteratorAggregate, Statement
private $stmt; private $stmt;
/** /**
* Parameters to bind. * References to the variables bound as statement parameters.
* *
* @var array * @var array
*/ */
private $params = array(); private $variables = array();
/**
* Bound parameter types.
*
* @var array
*/
private $types = array();
/** /**
* Translations. * Translations.
...@@ -98,6 +105,13 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -98,6 +105,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.
* *
...@@ -138,11 +152,8 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -138,11 +152,8 @@ class SQLSrvStatement implements IteratorAggregate, Statement
throw new SQLSrvException("sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead."); throw new SQLSrvException("sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead.");
} }
if ($type === \PDO::PARAM_LOB) { $this->variables[$column] =& $variable;
$this->params[$column-1] = array($variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('max')); $this->types[$column] = $type;
} else {
$this->params[$column-1] = $variable;
}
} }
/** /**
...@@ -150,9 +161,20 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -150,9 +161,20 @@ class SQLSrvStatement implements IteratorAggregate, Statement
*/ */
public function closeCursor() public function closeCursor()
{ {
if ($this->stmt) { // not having the result means there's nothing to close
sqlsrv_free_stmt($this->stmt); 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;
} }
/** /**
...@@ -193,12 +215,16 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -193,12 +215,16 @@ class SQLSrvStatement implements IteratorAggregate, Statement
$hasZeroIndex = array_key_exists(0, $params); $hasZeroIndex = array_key_exists(0, $params);
foreach ($params as $key => $val) { foreach ($params as $key => $val) {
$key = ($hasZeroIndex && is_numeric($key)) ? $key + 1 : $key; $key = ($hasZeroIndex && is_numeric($key)) ? $key + 1 : $key;
$this->bindValue($key, $val); $this->variables[$key] = $val;
$this->types[$key] = null;
} }
} }
$this->stmt = sqlsrv_query($this->conn, $this->sql, $this->params);
if ( ! $this->stmt) { if ( ! $this->stmt) {
$this->stmt = $this->prepare();
}
if (!sqlsrv_execute($this->stmt)) {
throw SQLSrvException::fromSqlSrvErrors(); throw SQLSrvException::fromSqlSrvErrors();
} }
...@@ -207,6 +233,40 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -207,6 +233,40 @@ 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;
}
/**
* Prepares SQL Server statement resource
*
* @return resource
* @throws SQLSrvException
*/
private function prepare()
{
$params = array();
foreach ($this->variables as $column => &$variable) {
if ($this->types[$column] === \PDO::PARAM_LOB) {
$params[$column - 1] = array(
&$variable,
SQLSRV_PARAM_IN,
SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY),
SQLSRV_SQLTYPE_VARBINARY('max'),
);
} else {
$params[$column - 1] =& $variable;
}
}
$stmt = sqlsrv_prepare($this->conn, $this->sql, $params);
if (!$stmt) {
throw SQLSrvException::fromSqlSrvErrors();
}
return $stmt;
} }
/** /**
...@@ -236,6 +296,12 @@ class SQLSrvStatement implements IteratorAggregate, Statement ...@@ -236,6 +296,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;
......
...@@ -29,6 +29,13 @@ class PDOConnectionTest extends DbalFunctionalTestCase ...@@ -29,6 +29,13 @@ class PDOConnectionTest extends DbalFunctionalTestCase
} }
} }
protected function tearDown()
{
$this->resetSharedConn();
parent::tearDown();
}
public function testDoesNotRequireQueryForServerVersion() public function testDoesNotRequireQueryForServerVersion()
{ {
$this->assertFalse($this->driverConnection->requiresQueryForServerVersion()); $this->assertFalse($this->driverConnection->requiresQueryForServerVersion());
......
<?php
namespace Doctrine\Tests\DBAL\Functional;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type;
class StatementTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
$table = new Table('stmt_test');
$table->addColumn('id', 'integer');
$this->_conn->getSchemaManager()->dropAndCreateTable($table);
}
public function testStatementIsReusableAfterClosingCursor()
{
$this->_conn->insert('stmt_test', array('id' => 1));
$this->_conn->insert('stmt_test', array('id' => 2));
$stmt = $this->_conn->prepare('SELECT id FROM stmt_test ORDER BY id');
$stmt->execute();
$id = $stmt->fetchColumn();
$this->assertEquals(1, $id);
$stmt->closeCursor();
$stmt->execute();
$id = $stmt->fetchColumn();
$this->assertEquals(1, $id);
$id = $stmt->fetchColumn();
$this->assertEquals(2, $id);
}
public function testReuseStatementWithLongerResults()
{
$sm = $this->_conn->getSchemaManager();
$table = new Table('stmt_longer_results');
$table->addColumn('param', 'string');
$table->addColumn('val', 'text');
$sm->createTable($table);
$row1 = array(
'param' => 'param1',
'val' => 'X',
);
$this->_conn->insert('stmt_longer_results', $row1);
$stmt = $this->_conn->prepare('SELECT param, val FROM stmt_longer_results ORDER BY param');
$stmt->execute();
$this->assertArraySubset(array(
array('param1', 'X'),
), $stmt->fetchAll(\PDO::FETCH_NUM));
$row2 = array(
'param' => 'param2',
'val' => 'A bit longer value',
);
$this->_conn->insert('stmt_longer_results', $row2);
$stmt->execute();
$this->assertArraySubset(array(
array('param1', 'X'),
array('param2', 'A bit longer value'),
), $stmt->fetchAll(\PDO::FETCH_NUM));
}
public function testFetchLongBlob()
{
// make sure memory limit is large enough to not cause false positives,
// but is still not enough to store a LONGBLOB of the max possible size
$this->iniSet('memory_limit', '4G');
$sm = $this->_conn->getSchemaManager();
$table = new Table('stmt_long_blob');
$table->addColumn('contents', 'blob', array(
'length' => 0xFFFFFFFF,
));
$sm->createTable($table);
$contents = base64_decode(<<<EOF
H4sICJRACVgCA2RvY3RyaW5lLmljbwDtVNtLFHEU/ia1i9fVzVWxvJSrZmoXS6pd0zK7QhdNc03z
lrpppq1pWqJCFERZkUFEDybYBQqJhB6iUOqhh+whgl4qkF6MfGh+s87O7GVmO6OlBfUfdIZvznxn
fpzznW9gAI4unQ50XwirH2AAkEygEuIwU58ODnPBzXGv14sEq4BrwzKKL4sY++SGTz6PodcutN5x
IPvsFCa+K9CXMfS/cOL5OxesN0Wceygho0WAXVLwcUJBdDVDaqOAij4Rrz640XlXQmAxQ16PHU63
iqdvXbg4JOHLpILBUSdM7XZEVDDcfuZEbI2ASaYguUGAroSh97GMngcSeFFFerMdI+/dyGy1o+GW
Ax5FxfAbFwoviajuc+DCIwn+RTwGRmRIThXxdQJyu+z4/NUDYz2DKCsILuERWsoQfoQhqpLhyhMZ
XfcknBmU0NLvQArpTm0SsI5mqKqKuFoGc8cUcjrtqLohom1AgtujQnapmJJU+BbwCLIwhJXyiKlh
MB4TkFgvIK3JjrRmAefJm+77Eiqvi+SvCq/qJahQyWuVuEpcIa7QLh7Kbsourb9b66/pZdAd1voz
fCNfwsp46OnZQPojSX9UFcNy+mYJNDeJPHtJfqeR/nSaPTzmwlXar5dQ1adpd+B//I9/hi0xuCPQ
Nkvb5um37Wtc+auQXZsVxEVYD5hnCilxTaYYjsuxLlsxXUitzd2hs3GWHLM5UOM7Fy8t3xiat4fb
sneNxmNb/POO1pRXc7vnF2nc13Rq0cFWiyXkuHmzxuOtzUYfC7fEmK/3mx4QZd5u4E7XJWz6+dey
Za4tXHUiPyB8Vm781oaT+3fN6Y/eUFDfPkcNWetNxb+tlxEZsPqPdZMOzS4rxwJ8CDC+ABj1+Tu0
d+N0hqezcjblboJ3Bj8ARJilHX4FAAA=
EOF
);
$this->_conn->insert('stmt_long_blob', array(
'contents' => $contents,
), array(\PDO::PARAM_LOB));
$stmt = $this->_conn->prepare('SELECT contents FROM stmt_long_blob');
$stmt->execute();
$stream = Type::getType('blob')
->convertToPHPValue(
$stmt->fetchColumn(),
$this->_conn->getDatabasePlatform()
);
if ($this->_conn->getDriver()->getName() === 'pdo_sqlsrv') {
$this->markTestSkipped('Skipping on pdo_sqlsrv due to https://github.com/Microsoft/msphpsql/issues/270');
}
$this->assertSame($contents, stream_get_contents($stream));
}
public function testIncompletelyFetchedStatementDoesNotBlockConnection()
{
$this->_conn->insert('stmt_test', array('id' => 1));
$this->_conn->insert('stmt_test', array('id' => 2));
$stmt1 = $this->_conn->prepare('SELECT id FROM stmt_test');
$stmt1->execute();
$stmt1->fetch();
$stmt1->execute();
// fetching only one record out of two
$stmt1->fetch();
$stmt2 = $this->_conn->prepare('SELECT id FROM stmt_test WHERE id = ?');
$stmt2->execute(array(1));
$this->assertEquals(1, $stmt2->fetchColumn());
}
public function testReuseStatementAfterClosingCursor()
{
$this->_conn->insert('stmt_test', array('id' => 1));
$this->_conn->insert('stmt_test', array('id' => 2));
$stmt = $this->_conn->prepare('SELECT id FROM stmt_test WHERE id = ?');
$stmt->execute(array(1));
$id = $stmt->fetchColumn();
$this->assertEquals(1, $id);
$stmt->closeCursor();
$stmt->execute(array(2));
$id = $stmt->fetchColumn();
$this->assertEquals(2, $id);
}
public function testReuseStatementWithParameterBoundByReference()
{
$this->_conn->insert('stmt_test', array('id' => 1));
$this->_conn->insert('stmt_test', array('id' => 2));
$stmt = $this->_conn->prepare('SELECT id FROM stmt_test WHERE id = ?');
$stmt->bindParam(1, $id);
$id = 1;
$stmt->execute();
$this->assertEquals(1, $stmt->fetchColumn());
$id = 2;
$stmt->execute();
$this->assertEquals(2, $stmt->fetchColumn());
}
/**
* @dataProvider emptyFetchProvider
*/
public function testFetchFromNonExecutedStatement(callable $fetch, $expected)
{
$stmt = $this->_conn->prepare('SELECT id FROM stmt_test');
$this->assertSame($expected, $fetch($stmt));
}
public function testCloseCursorOnNonExecutedStatement()
{
$stmt = $this->_conn->prepare('SELECT id FROM stmt_test');
$this->assertTrue($stmt->closeCursor());
}
/**
* @dataProvider emptyFetchProvider
*/
public function testFetchFromNonExecutedStatementWithClosedCursor(callable $fetch, $expected)
{
$stmt = $this->_conn->prepare('SELECT id FROM stmt_test');
$stmt->closeCursor();
$this->assertSame($expected, $fetch($stmt));
}
/**
* @dataProvider emptyFetchProvider
*/
public function testFetchFromExecutedStatementWithClosedCursor(callable $fetch, $expected)
{
$this->_conn->insert('stmt_test', array('id' => 1));
$stmt = $this->_conn->prepare('SELECT id FROM stmt_test');
$stmt->execute();
$stmt->closeCursor();
$this->assertSame($expected, $fetch($stmt));
}
public static function emptyFetchProvider()
{
return array(
'fetch' => array(
function (Statement $stmt) {
return $stmt->fetch();
},
false,
),
'fetch-column' => array(
function (Statement $stmt) {
return $stmt->fetchColumn();
},
false,
),
'fetch-all' => array(
function (Statement $stmt) {
return $stmt->fetchAll();
},
array(),
),
);
}
}
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