Skip to content

Commit baf7164

Browse files
committed
ResultSet: a row taken by fetch() is not handed out again by the iterator
fetch() stored the row in $lastRow, which valid() reads as "the iterator already has a current row" - so fetch() followed by fetchAll() or foreach replayed the row the caller had just consumed. The other fetch methods never touched $lastRow, so after them the iteration carried on correctly, making the behaviour inconsistent as well as wrong. $lastRow now belongs to the iterator alone and is filled in by valid(), the only place that fetches on the iterator's behalf.
1 parent d87c8f5 commit baf7164

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

‎src/Database/ResultSet.php‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,15 @@ public function valid(): bool
187187
return true;
188188
}
189189

190-
return $this->fetch() !== null;
190+
// only a row fetched here belongs to the iterator, one taken by the caller's fetch() is gone;
191+
// assigning the result directly would turn the false left by next() into null and lose the rewind guard
192+
$row = $this->fetch();
193+
if ($row === null) {
194+
return false;
195+
}
196+
197+
$this->lastRow = $row;
198+
return true;
191199
}
192200

193201

@@ -233,7 +241,7 @@ public function fetch(): ?Row
233241
}
234242

235243
$this->lastRowKey++;
236-
return $this->lastRow = Arrays::toObject($data, new Row);
244+
return Arrays::toObject($data, new Row);
237245
}
238246

239247

‎tests/Database/ResultSet.fetch().phpt‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,39 @@ test('returns null for empty result set', function () use ($connection, $driverN
7373

7474
Assert::null($res->fetch());
7575
});
76+
77+
78+
test('a row taken by fetch() is not handed out again by the iterator', function () use ($connection) {
79+
$query = 'SELECT id FROM author ORDER BY id';
80+
$ids = fn(array $rows) => array_map(fn($row) => $row->id, $rows);
81+
82+
$res = $connection->query($query);
83+
Assert::same(11, $res->fetch()->id);
84+
Assert::same([12, 13], $ids($res->fetchAll()));
85+
86+
$res = $connection->query($query);
87+
$res->fetch();
88+
$collected = [];
89+
foreach ($res as $row) {
90+
$collected[] = $row->id;
91+
}
92+
93+
Assert::same([12, 13], $collected);
94+
95+
$res = $connection->query($query);
96+
while ($res->fetch());
97+
Assert::same([], iterator_to_array($res));
98+
});
99+
100+
101+
test('the iterator carries on after the other fetch methods too', function () use ($connection) {
102+
$query = 'SELECT id FROM author ORDER BY id';
103+
104+
$res = $connection->query($query);
105+
$res->fetchAssoc();
106+
Assert::same([12, 13], array_map(fn($row) => $row->id, $res->fetchAll()));
107+
108+
$res = $connection->query($query);
109+
$res->fetchField();
110+
Assert::same([12, 13], array_map(fn($row) => $row->id, $res->fetchAll()));
111+
});

0 commit comments

Comments
 (0)