Commit dc3cfb2f authored by Sergei Morozov's avatar Sergei Morozov Committed by Marco Pivetta

[DBAL-2546] Bind result variables after each execution in order to prevent string truncation

parent 7cd0589f
......@@ -168,9 +168,6 @@ class MysqliStatement implements \IteratorAggregate, Statement
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;
......@@ -178,7 +175,29 @@ class MysqliStatement implements \IteratorAggregate, Statement
$meta->free();
$this->_columnNames = $columnNames;
$this->_rowBindedValues = array_fill(0, count($columnNames), null);
} 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) {
......@@ -188,9 +207,6 @@ class MysqliStatement implements \IteratorAggregate, Statement
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;
}
}
return true;
......
......@@ -53,4 +53,37 @@ class StatementTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->assertFalse($value);
}
public function testReuseStatementWithLongerResults()
{
$sm = $this->_conn->getSchemaManager();
$table = new Table('stmt_test_longer_results');
$table->addColumn('param', 'string');
$table->addColumn('val', 'text');
$sm->createTable($table);
$row1 = array(
'param' => 'param1',
'val' => 'X',
);
$this->_conn->insert('stmt_test_longer_results', $row1);
$stmt = $this->_conn->prepare('SELECT param, val FROM stmt_test_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_test_longer_results', $row2);
$stmt->execute();
$this->assertArraySubset(array(
array('param1', 'X'),
array('param2', 'A bit longer value'),
), $stmt->fetchAll(\PDO::FETCH_NUM));
}
}
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