FIX: Fix MySQLQuery::seek() and Query::rewind() to fix repeated iteration

API: Query::seek() and Query::rewind() no longer return a value.

Although breaking an API inside a patch release may seem odd, this in
fact is correcting a long-standing bug in our implementation of 
Iterator::rewind(), so I think it’s appropriate.

https://github.com/silverstripe/silverstripe-framework/issues/9097
This commit is contained in:
Sam Minnee 2019-06-28 10:57:51 +12:00
parent ad050997fd
commit 96e7914f23
4 changed files with 33 additions and 6 deletions

View File

@ -48,8 +48,11 @@ class MySQLQuery extends Query
public function seek($row) public function seek($row)
{ {
if (is_object($this->handle)) { if (is_object($this->handle)) {
// Fix for https://github.com/silverstripe/silverstripe-framework/issues/9097 without breaking the seek() API
$this->handle->data_seek($row); $this->handle->data_seek($row);
return $this->nextRecord(); $result = $this->nextRecord();
$this->handle->data_seek($row);
return $result;
} }
return null; return null;
} }

View File

@ -105,8 +105,12 @@ class MySQLStatement extends Query
public function seek($row) public function seek($row)
{ {
$this->rowNum = $row - 1; $this->rowNum = $row - 1;
// Fix for https://github.com/silverstripe/silverstripe-framework/issues/9097 without breaking the seek() API
$this->statement->data_seek($row); $this->statement->data_seek($row);
return $this->next(); $result = $this->next();
$this->statement->data_seek($row);
return $result;
} }
public function numRecords() public function numRecords()
@ -135,6 +139,6 @@ class MySQLStatement extends Query
public function rewind() public function rewind()
{ {
return $this->seek(0); $this->seek(0);
} }
} }

View File

@ -168,16 +168,15 @@ abstract class Query implements Iterator
* Iterator function implementation. Rewind the iterator to the first item and return it. * Iterator function implementation. Rewind the iterator to the first item and return it.
* Makes use of {@link seek()} and {@link numRecords()}, takes care of the plumbing. * Makes use of {@link seek()} and {@link numRecords()}, takes care of the plumbing.
* *
* @return array * @return void
*/ */
public function rewind() public function rewind()
{ {
if ($this->queryHasBegun && $this->numRecords() > 0) { if ($this->queryHasBegun && $this->numRecords() > 0) {
$this->queryHasBegun = false; $this->queryHasBegun = false;
$this->currentRecord = null; $this->currentRecord = null;
return $this->seek(0); $this->seek(0);
} }
return null;
} }
/** /**

View File

@ -272,4 +272,25 @@ class DatabaseTest extends SapphireTest
$result = DB::query('SELECT TRUE')->first(); $result = DB::query('SELECT TRUE')->first();
$this->assertInternalType('int', reset($result)); $this->assertInternalType('int', reset($result));
} }
/**
* Test that repeated iteration of a query returns all records.
* See https://github.com/silverstripe/silverstripe-framework/issues/9097
*/
public function testRepeatedIteration()
{
$inputData = ['one', 'two', 'three', 'four'];
foreach ($inputData as $i => $text) {
$x = new MyObject();
$x->MyField = $text;
$x->MyInt = $i;
$x->write();
}
$query = DB::query('SELECT "MyInt", "MyField" FROM "DatabaseTest_MyObject" ORDER BY "MyInt"');
$this->assertEquals($inputData, $query->map());
$this->assertEquals($inputData, $query->map());
}
} }