From 3e7a1c3af0bd642004731d38069030ce91a10037 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sun, 27 Jun 2021 15:57:37 -0500 Subject: [PATCH 1/6] implement collection engine --- src/EngineManager.php | 13 +- src/Engines/CollectionEngine.php | 236 +++++++++++++++++++++++++ tests/Feature/CollectionEngineTest.php | 81 +++++++++ 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 src/Engines/CollectionEngine.php create mode 100644 tests/Feature/CollectionEngineTest.php diff --git a/src/EngineManager.php b/src/EngineManager.php index 6b5c989f..d1349cb1 100644 --- a/src/EngineManager.php +++ b/src/EngineManager.php @@ -8,6 +8,7 @@ use Exception; use Illuminate\Support\Manager; use Laravel\Scout\Engines\AlgoliaEngine; +use Laravel\Scout\Engines\CollectionEngine; use Laravel\Scout\Engines\MeiliSearchEngine; use Laravel\Scout\Engines\NullEngine; use MeiliSearch\Client as MeiliSearch; @@ -124,7 +125,17 @@ protected function ensureMeiliSearchClientIsInstalled() } /** - * Create a Null engine instance. + * Create a database engine instance. + * + * @return \Laravel\Scout\Engines\CollectionEngine + */ + public function createCollectionDriver() + { + return new CollectionEngine; + } + + /** + * Create a null engine instance. * * @return \Laravel\Scout\Engines\NullEngine */ diff --git a/src/Engines/CollectionEngine.php b/src/Engines/CollectionEngine.php new file mode 100644 index 00000000..914c115b --- /dev/null +++ b/src/Engines/CollectionEngine.php @@ -0,0 +1,236 @@ +searchModels($builder); + + return [ + 'results' => $models->take($builder->limit)->all(), + 'total' => count($models), + ]; + } + + /** + * Perform the given search on the engine. + * + * @param \Laravel\Scout\Builder $builder + * @param int $perPage + * @param int $page + * @return mixed + */ + public function paginate(Builder $builder, $perPage, $page) + { + $models = $this->searchModels($builder); + + return [ + 'results' => $models->forPage($page - 1, $perPage)->all(), + 'total' => count($models), + ]; + } + + /** + * Get the Eloquent models for the given builder. + * + * @param \Laravel\Scout\Builder $builder + * @return \Illuminate\Database\Eloquent\Collection + */ + protected function searchModels(Builder $builder) + { + $models = $builder->model->query()->get(); + + foreach ($builder->wheres as $key => $value) { + $models = $models->where($key, $value); + } + + $models = $models->values(); + + if (count($models) === 0) { + return $models; + } + + $columns = array_keys($models[0]->toSearchableArray()); + + return $models->filter(function ($model) use ($builder, $columns) { + foreach ($columns as $column) { + $attribute = $model->{$column}; + + if (Str::contains($attribute, $builder->query)) { + return true; + } + } + + return false; + })->values(); + } + + /** + * Pluck and return the primary keys of the given results. + * + * @param mixed $results + * @return \Illuminate\Support\Collection + */ + public function mapIds($results) + { + $results = $results['results']; + + return count($results) > 0 + ? collect($results)->pluck($results[0]->getKeyName())->values() + : collect(); + } + + /** + * Map the given results to instances of the given model. + * + * @param \Laravel\Scout\Builder $builder + * @param mixed $results + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Collection + */ + public function map(Builder $builder, $results, $model) + { + $results = $results['results']; + + if (count($results) === 0) { + return $model->newCollection(); + } + + $objectIds = collect($results) + ->pluck($model->getKeyName()) + ->values() + ->all(); + + $objectIdPositions = array_flip($objectIds); + + return $model->getScoutModelsByIds( + $builder, $objectIds + )->filter(function ($model) use ($objectIds) { + return in_array($model->getScoutKey(), $objectIds); + })->sortBy(function ($model) use ($objectIdPositions) { + return $objectIdPositions[$model->getScoutKey()]; + })->values(); + } + + /** + * Map the given results to instances of the given model via a lazy collection. + * + * @param \Laravel\Scout\Builder $builder + * @param mixed $results + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Support\LazyCollection + */ + public function lazyMap(Builder $builder, $results, $model) + { + $results = $results['results']; + + if (count($results) === 0) { + return LazyCollection::make($model->newCollection()); + } + + $objectIds = collect($results) + ->pluck($model->getKeyName()) + ->values()->all(); + + $objectIdPositions = array_flip($objectIds); + + return $model->queryScoutModelsByIds( + $builder, $objectIds + )->cursor()->filter(function ($model) use ($objectIds) { + return in_array($model->getScoutKey(), $objectIds); + })->sortBy(function ($model) use ($objectIdPositions) { + return $objectIdPositions[$model->getScoutKey()]; + })->values(); + } + + /** + * Get the total count from a raw result returned by the engine. + * + * @param mixed $results + * @return int + */ + public function getTotalCount($results) + { + return $results['total']; + } + + /** + * Flush all of the model's records from the engine. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return void + */ + public function flush($model) + { + // + } + + /** + * Create a search index. + * + * @param string $name + * @param array $options + * @return mixed + * + * @throws \Exception + */ + public function createIndex($name, array $options = []) + { + // + } + + /** + * Delete a search index. + * + * @param string $name + * @return mixed + */ + public function deleteIndex($name) + { + // + } +} diff --git a/tests/Feature/CollectionEngineTest.php b/tests/Feature/CollectionEngineTest.php new file mode 100644 index 00000000..db1fc129 --- /dev/null +++ b/tests/Feature/CollectionEngineTest.php @@ -0,0 +1,81 @@ +make('config')->set('scout.driver', 'collection'); + } + + protected function defineDatabaseMigrations() + { + $this->setUpFaker(); + $this->loadLaravelMigrations(); + + UserFactory::new()->create([ + 'name' => 'Taylor Otwell', + 'email' => 'taylor@laravel.com', + ]); + + UserFactory::new()->create([ + 'name' => 'Abigail Otwell', + 'email' => 'abigail@laravel.com', + ]); + } + + public function test_it_can_retrieve_results() + { + $models = SearchableUserModel::search('Taylor')->where('email', 'taylor@laravel.com')->get(); + $this->assertCount(1, $models); + $this->assertEquals(1, $models[0]->id); + + $models = SearchableUserModel::search('Abigail')->where('email', 'abigail@laravel.com')->get(); + $this->assertCount(1, $models); + $this->assertEquals(2, $models[0]->id); + + $models = SearchableUserModel::search('Taylor')->where('email', 'abigail@laravel.com')->get(); + $this->assertCount(0, $models); + + $models = SearchableUserModel::search('Taylor')->where('email', 'taylor@laravel.com')->get(); + $this->assertCount(1, $models); + + $models = SearchableUserModel::search('laravel')->get(); + $this->assertCount(2, $models); + + $models = SearchableUserModel::search('foo')->get(); + $this->assertCount(0, $models); + + $models = SearchableUserModel::search('Abigail')->where('email', 'taylor@laravel.com')->get(); + $this->assertCount(0, $models); + } + + public function test_it_can_paginate_results() + { + $models = SearchableUserModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); + $this->assertCount(1, $models); + + $models = SearchableUserModel::search('Taylor')->where('email', 'abigail@laravel.com')->paginate(); + $this->assertCount(0, $models); + + $models = SearchableUserModel::search('Taylor')->where('email', 'taylor@laravel.com')->paginate(); + $this->assertCount(1, $models); + + $models = SearchableUserModel::search('laravel')->paginate(); + $this->assertCount(2, $models); + } +} From c9013717b6d46ab5b3064098218e36009e2e1ae8 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sun, 27 Jun 2021 16:30:50 -0500 Subject: [PATCH 2/6] use cursor --- src/Engines/CollectionEngine.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Engines/CollectionEngine.php b/src/Engines/CollectionEngine.php index 914c115b..12507e9d 100644 --- a/src/Engines/CollectionEngine.php +++ b/src/Engines/CollectionEngine.php @@ -51,7 +51,7 @@ public function search(Builder $builder) $models = $this->searchModels($builder); return [ - 'results' => $models->take($builder->limit)->all(), + 'results' => $models->all(), 'total' => count($models), ]; } @@ -82,7 +82,9 @@ public function paginate(Builder $builder, $perPage, $page) */ protected function searchModels(Builder $builder) { - $models = $builder->model->query()->get(); + $models = $builder->model->query() + ->orderBy($builder->model->getKeyName(), 'desc') + ->cursor(); foreach ($builder->wheres as $key => $value) { $models = $models->where($key, $value); @@ -94,7 +96,7 @@ protected function searchModels(Builder $builder) return $models; } - $columns = array_keys($models[0]->toSearchableArray()); + $columns = array_keys($models->first()->toSearchableArray()); return $models->filter(function ($model) use ($builder, $columns) { foreach ($columns as $column) { From 1bb7cc512a6cd60688072ee7576955436e4db75e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sun, 27 Jun 2021 16:39:49 -0500 Subject: [PATCH 3/6] apply where clauses in database --- src/Engines/CollectionEngine.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Engines/CollectionEngine.php b/src/Engines/CollectionEngine.php index 12507e9d..4dfec36a 100644 --- a/src/Engines/CollectionEngine.php +++ b/src/Engines/CollectionEngine.php @@ -83,13 +83,14 @@ public function paginate(Builder $builder, $perPage, $page) protected function searchModels(Builder $builder) { $models = $builder->model->query() + ->when(count($builder->wheres) > 0, function ($query) use ($builder) { + foreach ($builder->wheres as $key => $value) { + $query->where($key, $value); + } + }) ->orderBy($builder->model->getKeyName(), 'desc') ->cursor(); - foreach ($builder->wheres as $key => $value) { - $models = $models->where($key, $value); - } - $models = $models->values(); if (count($models) === 0) { From 463897f5e0f5affeaee6674c88e1b4c3a4efbe2c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 28 Jun 2021 09:52:55 -0500 Subject: [PATCH 4/6] Update src/EngineManager.php Co-authored-by: Nuno Maduro --- src/EngineManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EngineManager.php b/src/EngineManager.php index d1349cb1..fd2f141e 100644 --- a/src/EngineManager.php +++ b/src/EngineManager.php @@ -125,7 +125,7 @@ protected function ensureMeiliSearchClientIsInstalled() } /** - * Create a database engine instance. + * Create a collection engine instance. * * @return \Laravel\Scout\Engines\CollectionEngine */ From 25f0bb07e4842262bc34fc38ada876542082cebf Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 28 Jun 2021 10:36:23 -0500 Subject: [PATCH 5/6] Update src/Engines/CollectionEngine.php Co-authored-by: Joseph Silber --- src/Engines/CollectionEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engines/CollectionEngine.php b/src/Engines/CollectionEngine.php index 4dfec36a..3354600e 100644 --- a/src/Engines/CollectionEngine.php +++ b/src/Engines/CollectionEngine.php @@ -172,7 +172,7 @@ public function lazyMap(Builder $builder, $results, $model) $results = $results['results']; if (count($results) === 0) { - return LazyCollection::make($model->newCollection()); + return LazyCollection::empty(); } $objectIds = collect($results) From 8be6c4e4d040d262e5da0e10a1b13e0b087931ce Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 28 Jun 2021 13:37:52 -0500 Subject: [PATCH 6/6] dont use lazy collection --- src/Engines/CollectionEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engines/CollectionEngine.php b/src/Engines/CollectionEngine.php index 3354600e..b421492a 100644 --- a/src/Engines/CollectionEngine.php +++ b/src/Engines/CollectionEngine.php @@ -89,7 +89,7 @@ protected function searchModels(Builder $builder) } }) ->orderBy($builder->model->getKeyName(), 'desc') - ->cursor(); + ->get(); $models = $models->values();