diff --git a/src/Query/SelectQuery.php b/src/Query/SelectQuery.php index 92ce213a..0d80c809 100644 --- a/src/Query/SelectQuery.php +++ b/src/Query/SelectQuery.php @@ -305,7 +305,7 @@ public function runChunks(int $limit, callable $callback): void $select->limit($limit); $offset = 0; - while ($offset + $limit <= $count) { + while ($offset < $count) { $result = $callback( $select->offset($offset)->getIterator(), $offset, diff --git a/tests/Database/Functional/Driver/Common/Driver/StatementTest.php b/tests/Database/Functional/Driver/Common/Driver/StatementTest.php index fb782896..0785dc5b 100644 --- a/tests/Database/Functional/Driver/Common/Driver/StatementTest.php +++ b/tests/Database/Functional/Driver/Common/Driver/StatementTest.php @@ -292,6 +292,90 @@ function ($result) use (&$count) { $this->assertSame(5, $count); } + public function testChunksProcessLastPartialChunk(): void + { + $table = $this->database->table('sample_table'); + $this->fillData(); + + $select = $table->select()->orderBy('id'); + + // 10 rows split by chunks of 3: the last chunk holds the single remaining row (10 % 3 == 1) + $visited = []; + $select->runChunks( + 3, + function (StatementInterface $result) use (&$visited): void { + foreach ($result as $row) { + $visited[] = $row['id']; + } + }, + ); + + $this->assertEquals(\range(1, 10), $visited); + } + + public function testChunksWithLimitGreaterThanCount(): void + { + $table = $this->database->table('sample_table'); + $this->fillData(); + + $select = $table->select()->orderBy('id'); + + // chunk size larger than the total row count must still yield every row in a single chunk + $visited = []; + $chunks = 0; + $select->runChunks( + 100, + function (StatementInterface $result) use (&$visited, &$chunks): void { + $chunks++; + foreach ($result as $row) { + $visited[] = $row['id']; + } + }, + ); + + $this->assertSame(1, $chunks); + $this->assertEquals(\range(1, 10), $visited); + } + + public function testChunksOnEmptyResultNeverInvokesCallback(): void + { + $table = $this->database->table('sample_table'); + // no data inserted + + $select = $table->select(); + + $invoked = false; + $select->runChunks( + 5, + function () use (&$invoked): void { + $invoked = true; + }, + ); + + $this->assertFalse($invoked); + } + + public function testChunksPassOffsetAndCountToCallback(): void + { + $table = $this->database->table('sample_table'); + $this->fillData(); + + $select = $table->select(); + + $offsets = []; + $counts = []; + $select->runChunks( + 3, + function (StatementInterface $result, int $offset, int $count) use (&$offsets, &$counts): void { + $offsets[] = $offset; + $counts[] = $count; + }, + ); + + $this->assertSame([0, 3, 6, 9], $offsets); + $this->assertSame([10, 10, 10, 10], $counts); + } + public function testNativeParameters(): void { $this->fillData();