Skip to content

Commit 38c3403

Browse files
authored
fix(SelectQuery): process the last partial chunk in runChunks() (#255)
1 parent c4d40ce commit 38c3403

2 files changed

Lines changed: 85 additions & 1 deletion

File tree

src/Query/SelectQuery.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ public function runChunks(int $limit, callable $callback): void
305305
$select->limit($limit);
306306

307307
$offset = 0;
308-
while ($offset + $limit <= $count) {
308+
while ($offset < $count) {
309309
$result = $callback(
310310
$select->offset($offset)->getIterator(),
311311
$offset,

tests/Database/Functional/Driver/Common/Driver/StatementTest.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,90 @@ function ($result) use (&$count) {
292292
$this->assertSame(5, $count);
293293
}
294294

295+
public function testChunksProcessLastPartialChunk(): void
296+
{
297+
$table = $this->database->table('sample_table');
298+
$this->fillData();
299+
300+
$select = $table->select()->orderBy('id');
301+
302+
// 10 rows split by chunks of 3: the last chunk holds the single remaining row (10 % 3 == 1)
303+
$visited = [];
304+
$select->runChunks(
305+
3,
306+
function (StatementInterface $result) use (&$visited): void {
307+
foreach ($result as $row) {
308+
$visited[] = $row['id'];
309+
}
310+
},
311+
);
312+
313+
$this->assertEquals(\range(1, 10), $visited);
314+
}
315+
316+
public function testChunksWithLimitGreaterThanCount(): void
317+
{
318+
$table = $this->database->table('sample_table');
319+
$this->fillData();
320+
321+
$select = $table->select()->orderBy('id');
322+
323+
// chunk size larger than the total row count must still yield every row in a single chunk
324+
$visited = [];
325+
$chunks = 0;
326+
$select->runChunks(
327+
100,
328+
function (StatementInterface $result) use (&$visited, &$chunks): void {
329+
$chunks++;
330+
foreach ($result as $row) {
331+
$visited[] = $row['id'];
332+
}
333+
},
334+
);
335+
336+
$this->assertSame(1, $chunks);
337+
$this->assertEquals(\range(1, 10), $visited);
338+
}
339+
340+
public function testChunksOnEmptyResultNeverInvokesCallback(): void
341+
{
342+
$table = $this->database->table('sample_table');
343+
// no data inserted
344+
345+
$select = $table->select();
346+
347+
$invoked = false;
348+
$select->runChunks(
349+
5,
350+
function () use (&$invoked): void {
351+
$invoked = true;
352+
},
353+
);
354+
355+
$this->assertFalse($invoked);
356+
}
357+
358+
public function testChunksPassOffsetAndCountToCallback(): void
359+
{
360+
$table = $this->database->table('sample_table');
361+
$this->fillData();
362+
363+
$select = $table->select();
364+
365+
$offsets = [];
366+
$counts = [];
367+
$select->runChunks(
368+
3,
369+
function (StatementInterface $result, int $offset, int $count) use (&$offsets, &$counts): void {
370+
$offsets[] = $offset;
371+
$counts[] = $count;
372+
},
373+
);
374+
375+
$this->assertSame([0, 3, 6, 9], $offsets);
376+
$this->assertSame([10, 10, 10, 10], $counts);
377+
}
378+
295379
public function testNativeParameters(): void
296380
{
297381
$this->fillData();

0 commit comments

Comments
 (0)