Added methods lazyById and lazyByIdDesc for lazy queries (#6822)

This commit is contained in:
zds 2024-06-03 10:13:15 +08:00 committed by GitHub
parent ee547bee24
commit 5d724aa501
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 168 additions and 0 deletions

View File

@ -9,6 +9,7 @@
- [#6816](https://github.com/hyperf/hyperf/pull/6816) Added methods `Hyperf\Database\Model\Builder::load*`.
- [#6820](https://github.com/hyperf/hyperf/pull/6820) Added method `Hyperf\Database\Model\Builder::valueOrFail()`.
- [#6821](https://github.com/hyperf/hyperf/pull/6821) Added method `Hyperf\Database\Concerns\BuildsQueries::chunkMap()`.
- [#6822](https://github.com/hyperf/hyperf/pull/6822) Added methods `lazyById` and `lazyByIdDesc` for lazy queries.
## Fixed

View File

@ -14,6 +14,7 @@ namespace Hyperf\Database\Concerns;
use Closure;
use Hyperf\Collection\Collection as BaseCollection;
use Hyperf\Collection\LazyCollection;
use Hyperf\Context\ApplicationContext;
use Hyperf\Contract\LengthAwarePaginatorInterface;
use Hyperf\Contract\PaginatorInterface;
@ -26,6 +27,7 @@ use Hyperf\Paginator\Cursor;
use Hyperf\Paginator\CursorPaginator;
use Hyperf\Paginator\Paginator;
use Hyperf\Stringable\Str;
use InvalidArgumentException;
use RuntimeException;
trait BuildsQueries
@ -102,6 +104,22 @@ trait BuildsQueries
});
}
/**
* Query lazily, by chunking the results of a query by comparing IDs.
*/
public function lazyById(int $chunkSize = 1000, ?string $column = null, ?string $alias = null): LazyCollection
{
return $this->orderedLazyById($chunkSize, $column, $alias);
}
/**
* Query lazily, by chunking the results of a query by comparing IDs in descending order.
*/
public function lazyByIdDesc(int $chunkSize = 1000, ?string $column = null, ?string $alias = null): LazyCollection
{
return $this->orderedLazyById($chunkSize, $column, $alias, true);
}
/**
* Execute the query and get the first result.
*
@ -165,6 +183,48 @@ trait BuildsQueries
return $this;
}
/**
* Query lazily, by chunking the results of a query by comparing IDs in a given order.
*/
protected function orderedLazyById(int $chunkSize = 1000, ?string $column = null, ?string $alias = null, bool $descending = false): LazyCollection
{
if ($chunkSize < 1) {
throw new InvalidArgumentException('The chunk size should be at least 1');
}
$column ??= $this->defaultKeyName();
$alias ??= $column;
return LazyCollection::make(function () use ($chunkSize, $column, $alias, $descending) {
$lastId = null;
while (true) {
$clone = clone $this;
if ($descending) {
$results = $clone->forPageBeforeId($chunkSize, $lastId, $column)->get();
} else {
$results = $clone->forPageAfterId($chunkSize, $lastId, $column)->get();
}
foreach ($results as $result) {
yield $result;
}
if ($results->count() < $chunkSize) {
return;
}
$lastId = $results->last()->{$alias};
if ($lastId === null) {
throw new RuntimeException("The lazyById operation was aborted because the [{$alias}] column is not present in the query result.");
}
}
});
}
/**
* Create a new length-aware paginator instance.
*/

View File

@ -1206,6 +1206,14 @@ class Builder
return isset(static::$macros[$name]);
}
/**
* Get the default key name of the table.
*/
protected function defaultKeyName(): string
{
return $this->getModel()->getKeyName();
}
/**
* Ensure the proper order by required for cursor pagination.
*/

View File

@ -2919,6 +2919,14 @@ class Builder
});
}
/**
* Get the default key name of the table.
*/
protected function defaultKeyName(): string
{
return 'id';
}
/**
* Ensure the proper order by required for cursor pagination.
*/

View File

@ -26,6 +26,7 @@ use Hyperf\Database\Connectors\MySqlConnector;
use Hyperf\Database\Events\QueryExecuted;
use Hyperf\Database\Model\EnumCollector;
use Hyperf\Database\Model\Events\Saved;
use Hyperf\Database\Model\Model;
use Hyperf\Database\MySqlBitConnection;
use Hyperf\Database\Query\Builder as QueryBuilder;
use Hyperf\Database\Query\Expression;
@ -1184,6 +1185,91 @@ class ModelRealBuilderTest extends TestCase
Schema::drop('accounting_test');
}
public function testOrderedLazyById(): void
{
$container = $this->getContainer();
$container->shouldReceive('get')->with(Db::class)->andReturn(new Db($container));
Schema::create('lazy_users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
$now = Carbon::now();
Db::table('lazy_users')->insert([
['name' => 'Hyperf1', 'created_at' => $now, 'updated_at' => $now],
['name' => 'Hyperf2', 'created_at' => $now->addMinutes(), 'updated_at' => $now->addMinutes()],
['name' => 'Hyperf3', 'created_at' => $now->addMinutes(2), 'updated_at' => $now->addMinutes(2)],
['name' => 'Hyperf4', 'created_at' => $now->addMinutes(3), 'updated_at' => $now->addMinutes(3)],
['name' => 'Hyperf5', 'created_at' => $now->addMinutes(4), 'updated_at' => $now->addMinutes(4)],
['name' => 'Hyperf6', 'created_at' => $now->addMinutes(5), 'updated_at' => $now->addMinutes(5)],
['name' => 'Hyperf7', 'created_at' => $now->addMinutes(6), 'updated_at' => $now->addMinutes(6)],
['name' => 'Hyperf8', 'created_at' => $now->addMinutes(7), 'updated_at' => $now->addMinutes(7)],
['name' => 'Hyperf9', 'created_at' => $now->addMinutes(8), 'updated_at' => $now->addMinutes(8)],
['name' => 'Hyperf10', 'created_at' => $now->addMinutes(9), 'updated_at' => $now->addMinutes(9)],
]);
$results = LazyUserModel::query()->lazyById(10);
$this->assertCount(10, $results);
foreach ($results as $index => $value) {
$this->assertSame('Hyperf' . ($index + 1), $value->name);
}
$dbResults = Db::table('lazy_users')->lazyById(10);
$this->assertCount(10, $dbResults);
foreach ($dbResults as $index => $value) {
$this->assertSame('Hyperf' . ($index + 1), $value->name);
}
$results = LazyUserModel::query()->lazyById(5);
$dbResults = Db::table('lazy_users')->lazyById(5);
$this->assertCount(10, $results);
foreach ($results as $index => $value) {
$this->assertSame('Hyperf' . ($index + 1), $value->name);
}
$this->assertCount(10, $dbResults);
foreach ($dbResults as $index => $value) {
$this->assertSame('Hyperf' . ($index + 1), $value->name);
}
$results = LazyUserModel::query()->lazyByIdDesc(10);
$this->assertCount(10, $results);
foreach ($results as $index => $value) {
$this->assertSame('Hyperf' . (10 - $index), $value->name);
}
$dbResults = Db::table('lazy_users')->lazyByIdDesc(10);
$this->assertCount(10, $dbResults);
foreach ($dbResults as $index => $value) {
$this->assertSame('Hyperf' . (10 - $index), $value->name);
}
$results = LazyUserModel::query()->lazyByIdDesc(5);
$dbResults = Db::table('lazy_users')->lazyByIdDesc(5);
$this->assertCount(10, $dbResults);
foreach ($dbResults as $index => $value) {
$this->assertSame('Hyperf' . (10 - $index), $value->name);
}
$this->assertCount(10, $results);
foreach ($results as $index => $value) {
$this->assertSame('Hyperf' . (10 - $index), $value->name);
}
$results = LazyUserModel::query()->select(['id', 'name', 'created_at as create_date', 'updated_at'])->lazyByIdDesc(10, 'created_at', 'create_date');
$dbResults = Db::table('lazy_users')->select(['id', 'name', 'created_at as create_date', 'updated_at'])->lazyByIdDesc(10, 'created_at', 'create_date');
$this->assertCount(10, $results);
foreach ($results as $index => $value) {
$this->assertSame('Hyperf' . ($index + 1), $value->name);
}
$this->assertCount(10, $dbResults);
foreach ($dbResults as $index => $value) {
$this->assertSame('Hyperf' . ($index + 1), $value->name);
}
$results = LazyUserModel::query()->select(['id', 'name', 'created_at as create_date', 'updated_at'])->lazyById(10, 'created_at', 'create_date');
$dbResults = Db::table('lazy_users')->select(['id', 'name', 'created_at as create_date', 'updated_at'])->lazyById(10, 'created_at', 'create_date');
$this->assertCount(10, $results);
foreach ($results as $index => $value) {
$this->assertSame('Hyperf' . ($index + 1), $value->name);
}
$this->assertCount(10, $dbResults);
foreach ($dbResults as $index => $value) {
$this->assertSame('Hyperf' . ($index + 1), $value->name);
}
Schema::dropIfExists('lazy_users');
}
protected function getContainer()
{
$dispatcher = Mockery::mock(EventDispatcherInterface::class);
@ -1198,3 +1284,8 @@ class ModelRealBuilderTest extends TestCase
return $container;
}
}
class LazyUserModel extends Model
{
protected ?string $table = 'lazy_users';
}