From e16140f8f4ab7bd5d551d1e16c05876078354045 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 26 Mar 2021 09:09:00 +0800 Subject: [PATCH] Fixes missing `lazy()` and `lazyById()` on BelongsToMany and HasManyThrough relation query builder. Signed-off-by: Mior Muhammad Zaki --- .../Eloquent/Relations/BelongsToMany.php | 58 +++++++- .../Eloquent/Relations/HasManyThrough.php | 28 ++++ ...abaseEloquentBelongsToManyLazyByIdTest.php | 134 ++++++++++++++++++ ...eEloquentHasManyThroughIntegrationTest.php | 46 ++++++ ...seEloquentHasOneThroughIntegrationTest.php | 19 +++ 5 files changed, 278 insertions(+), 7 deletions(-) create mode 100644 tests/Database/DatabaseEloquentBelongsToManyLazyByIdTest.php diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 9e7ed7a53a66..bc4d660a9b18 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -865,9 +865,7 @@ public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'p */ public function chunk($count, callable $callback) { - $this->query->addSelect($this->shouldSelect()); - - return $this->query->chunk($count, function ($results, $page) use ($callback) { + return $this->prepareQueryBuilder()->chunk($count, function ($results, $page) use ($callback) { $this->hydratePivotRelation($results->all()); return $callback($results, $page); @@ -885,7 +883,7 @@ public function chunk($count, callable $callback) */ public function chunkById($count, callable $callback, $column = null, $alias = null) { - $this->query->addSelect($this->shouldSelect()); + $this->prepareQueryBuilder(); $column = $column ?? $this->getRelated()->qualifyColumn( $this->getRelatedKeyName() @@ -918,6 +916,44 @@ public function each(callable $callback, $count = 1000) }); } + /** + * Query lazily, by chunks of the given size. + * + * @param int $chunkSize + * @return \Illuminate\Support\LazyCollection + */ + public function lazy($chunkSize = 1000) + { + return $this->prepareQueryBuilder()->lazy($chunkSize)->map(function ($model) { + $this->hydratePivotRelation([$model]); + + return $model; + }); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs. + * + * @param int $count + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + */ + public function lazyById($chunkSize = 1000, $column = null, $alias = null) + { + $column = $column ?? $this->getRelated()->qualifyColumn( + $this->getRelatedKeyName() + ); + + $alias = $alias ?? $this->getRelatedKeyName(); + + return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias)->map(function ($model) { + $this->hydratePivotRelation([$model]); + + return $model; + }); + } + /** * Get a lazy collection for the given query. * @@ -925,15 +961,23 @@ public function each(callable $callback, $count = 1000) */ public function cursor() { - $this->query->addSelect($this->shouldSelect()); - - return $this->query->cursor()->map(function ($model) { + return $this->prepareQueryBuilder()->cursor()->map(function ($model) { $this->hydratePivotRelation([$model]); return $model; }); } + /** + * Prepare the query builder for query execution. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function prepareQueryBuilder() + { + return $this->query->addSelect($this->shouldSelect()); + } + /** * Hydrate the pivot table relationship on the models. * diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 6285cf230b7f..8b3a2992658a 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -501,6 +501,34 @@ public function each(callable $callback, $count = 1000) }); } + /** + * Query lazily, by chunks of the given size. + * + * @param int $chunkSize + * @return \Illuminate\Support\LazyCollection + */ + public function lazy($chunkSize = 1000) + { + return $this->prepareQueryBuilder()->lazy($chunkSize); + } + + /** + * Query lazily, by chunking the results of a query by comparing IDs. + * + * @param int $count + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + */ + public function lazyById($chunkSize = 1000, $column = null, $alias = null) + { + $column = $column ?? $this->getRelated()->getQualifiedKeyName(); + + $alias = $alias ?? $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias); + } + /** * Prepare the query builder for query execution. * diff --git a/tests/Database/DatabaseEloquentBelongsToManyLazyByIdTest.php b/tests/Database/DatabaseEloquentBelongsToManyLazyByIdTest.php new file mode 100644 index 000000000000..cbdf1ffbda19 --- /dev/null +++ b/tests/Database/DatabaseEloquentBelongsToManyLazyByIdTest.php @@ -0,0 +1,134 @@ +addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); + + $db->bootEloquent(); + $db->setAsGlobal(); + + $this->createSchema(); + } + + /** + * Setup the database schema. + * + * @return void + */ + public function createSchema() + { + $this->schema()->create('users', function ($table) { + $table->increments('id'); + $table->string('email')->unique(); + }); + + $this->schema()->create('articles', function ($table) { + $table->increments('aid'); + $table->string('title'); + }); + + $this->schema()->create('article_user', function ($table) { + $table->integer('article_id')->unsigned(); + $table->foreign('article_id')->references('aid')->on('articles'); + $table->integer('user_id')->unsigned(); + $table->foreign('user_id')->references('id')->on('users'); + }); + } + + public function testBelongsToLazyById() + { + $this->seedData(); + + $user = BelongsToManyLazyByIdTestTestUser::query()->first(); + $i = 0; + + $user->articles()->lazyById(1)->each(function ($model) use (&$i) { + $i++; + $this->assertEquals($i, $model->aid); + }); + + $this->assertSame(3, $i); + } + + /** + * Tear down the database schema. + * + * @return void + */ + protected function tearDown(): void + { + $this->schema()->drop('users'); + $this->schema()->drop('articles'); + $this->schema()->drop('article_user'); + } + + /** + * Helpers... + */ + protected function seedData() + { + $user = BelongsToManyLazyByIdTestTestUser::create(['id' => 1, 'email' => 'taylorotwell@gmail.com']); + BelongsToManyLazyByIdTestTestArticle::query()->insert([ + ['aid' => 1, 'title' => 'Another title'], + ['aid' => 2, 'title' => 'Another title'], + ['aid' => 3, 'title' => 'Another title'], + ]); + + $user->articles()->sync([3, 1, 2]); + } + + /** + * Get a database connection instance. + * + * @return \Illuminate\Database\ConnectionInterface + */ + protected function connection() + { + return Eloquent::getConnectionResolver()->connection(); + } + + /** + * Get a schema builder instance. + * + * @return \Illuminate\Database\Schema\Builder + */ + protected function schema() + { + return $this->connection()->getSchemaBuilder(); + } +} + +class BelongsToManyLazyByIdTestTestUser extends Eloquent +{ + protected $table = 'users'; + protected $fillable = ['id', 'email']; + public $timestamps = false; + + public function articles() + { + return $this->belongsToMany(BelongsToManyLazyByIdTestTestArticle::class, 'article_user', 'user_id', 'article_id'); + } +} + +class BelongsToManyLazyByIdTestTestArticle extends Eloquent +{ + protected $primaryKey = 'aid'; + protected $table = 'articles'; + protected $keyType = 'string'; + public $incrementing = false; + public $timestamps = false; + protected $fillable = ['aid', 'title']; +} diff --git a/tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php b/tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php index d1572adcaade..b4c207efa820 100644 --- a/tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php +++ b/tests/Database/DatabaseEloquentHasManyThroughIntegrationTest.php @@ -321,6 +321,52 @@ public function testEachReturnsCorrectModels() }); } + public function testLazyReturnsCorrectModels() + { + $this->seedData(); + $this->seedDataExtended(); + $country = HasManyThroughTestCountry::find(2); + + $country->posts()->lazy(10)->each(function ($post) { + $this->assertEquals([ + 'id', + 'user_id', + 'title', + 'body', + 'email', + 'created_at', + 'updated_at', + 'laravel_through_key', + ], array_keys($post->getAttributes())); + }); + } + + public function testLazyById() + { + $this->seedData(); + $this->seedDataExtended(); + $country = HasManyThroughTestCountry::find(2); + + $i = 0; + + $country->posts()->lazyById(2)->each(function ($post) use (&$i, &$count) { + $i++; + + $this->assertEquals([ + 'id', + 'user_id', + 'title', + 'body', + 'email', + 'created_at', + 'updated_at', + 'laravel_through_key', + ], array_keys($post->getAttributes())); + }); + + $this->assertEquals(6, $i); + } + public function testIntermediateSoftDeletesAreIgnored() { $this->seedData(); diff --git a/tests/Database/DatabaseEloquentHasOneThroughIntegrationTest.php b/tests/Database/DatabaseEloquentHasOneThroughIntegrationTest.php index 67b9824f98e3..0b2bf287bcdb 100644 --- a/tests/Database/DatabaseEloquentHasOneThroughIntegrationTest.php +++ b/tests/Database/DatabaseEloquentHasOneThroughIntegrationTest.php @@ -234,6 +234,25 @@ public function testEachReturnsCorrectModels() }); } + public function testLazyReturnsCorrectModels() + { + $this->seedData(); + $this->seedDataExtended(); + $position = HasOneThroughTestPosition::find(1); + + $position->contract()->lazy()->each(function ($contract) { + $this->assertEquals([ + 'id', + 'user_id', + 'title', + 'body', + 'email', + 'created_at', + 'updated_at', + 'laravel_through_key', ], array_keys($contract->getAttributes())); + }); + } + public function testIntermediateSoftDeletesAreIgnored() { $this->seedData();