Skip to content

Commit e332bbd

Browse files
authored
feat(database): chunked results (#855)
1 parent 61e7abb commit e332bbd

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

src/Tempest/Database/src/Builder/ModelQueryBuilder.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Tempest\Database\Builder;
66

7+
use Closure;
78
use Tempest\Database\DatabaseModel;
89
use Tempest\Database\Id;
910
use Tempest\Database\Query;
@@ -22,6 +23,8 @@ final class ModelQueryBuilder
2223

2324
private ?int $limit = null;
2425

26+
private ?int $offset = null;
27+
2528
private array $raw = [];
2629

2730
private array $relations = [];
@@ -67,6 +70,22 @@ public function all(mixed ...$bindings): array
6770
return map($this->build($bindings))->collection()->to($this->modelClass);
6871
}
6972

73+
/**
74+
* @param \Closure(TModelClass[] $models): void $closure
75+
*/
76+
public function chunk(Closure $closure, int $amountPerChunk = 200): void
77+
{
78+
$offset = 0;
79+
80+
do {
81+
$data = $this->clone()->limit($amountPerChunk)->offset($offset)->all();
82+
83+
$offset += count($data);
84+
85+
$closure($data);
86+
} while ($data !== []);
87+
}
88+
7089
/** @return self<TModelClass> */
7190
public function where(string $where, mixed ...$bindings): self
7291
{
@@ -93,6 +112,14 @@ public function limit(int $limit): self
93112
return $this;
94113
}
95114

115+
/** @return self<TModelClass> */
116+
public function offset(int $offset): self
117+
{
118+
$this->offset = $offset;
119+
120+
return $this;
121+
}
122+
96123
/** @return self<TModelClass> */
97124
public function raw(string $raw): self
98125
{
@@ -177,6 +204,10 @@ private function build(array $bindings): Query
177204
$statements[] = sprintf('LIMIT %s', $this->limit);
178205
}
179206

207+
if ($this->offset) {
208+
$statements[] = sprintf('OFFSET %s', $this->offset);
209+
}
210+
180211
if ($this->raw !== []) {
181212
$statements[] = implode(', ', $this->raw);
182213
}
@@ -197,4 +228,9 @@ private function getRelations(ModelDefinition $modelDefinition): array
197228

198229
return $relations;
199230
}
231+
232+
private function clone(): self
233+
{
234+
return clone $this;
235+
}
200236
}

tests/Integration/Database/Builder/ModelQueryBuilderTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,52 @@ public function test_limit(): void
7171
$this->assertSame('B', $books[1]->title);
7272
}
7373

74+
public function test_offset(): void
75+
{
76+
$this->migrate(
77+
CreateMigrationsTable::class,
78+
CreateAuthorTable::class,
79+
CreateBookTable::class,
80+
);
81+
82+
(Book::new(title: 'A'))->save();
83+
(Book::new(title: 'B'))->save();
84+
(Book::new(title: 'C'))->save();
85+
(Book::new(title: 'D'))->save();
86+
87+
$books = Book::query()->limit(2)->offset(2)->all();
88+
89+
$this->assertCount(2, $books);
90+
$this->assertSame('C', $books[0]->title);
91+
$this->assertSame('D', $books[1]->title);
92+
}
93+
94+
public function test_chunk(): void
95+
{
96+
$this->migrate(
97+
CreateMigrationsTable::class,
98+
CreateAuthorTable::class,
99+
CreateBookTable::class,
100+
);
101+
102+
(Book::new(title: 'A'))->save();
103+
(Book::new(title: 'B'))->save();
104+
(Book::new(title: 'C'))->save();
105+
(Book::new(title: 'D'))->save();
106+
107+
$results = [];
108+
Book::query()->chunk(function (array $chunk) use (&$results) {
109+
$results = [...$results, ...$chunk];
110+
}, 2);
111+
$this->assertCount(4, $results);
112+
113+
$results = [];
114+
Book::query()->where('title <> "A"')->chunk(function (array $chunk) use (&$results) {
115+
$results = [...$results, ...$chunk];
116+
}, 2);
117+
$this->assertCount(3, $results);
118+
}
119+
74120
public function test_raw(): void
75121
{
76122
$this->migrate(

0 commit comments

Comments
 (0)