From b1f281b92e80455667aff86df1e664c9db370c9b Mon Sep 17 00:00:00 2001 From: Oleh Isaiev <3649525+Malezha@users.noreply.github.com> Date: Thu, 14 Nov 2024 17:28:32 +0200 Subject: [PATCH] Fix for using pagination with custom query in Scout Builder. (#290) * Add limit handling to SearchFactory Enhanced the SearchFactory to handle 'limit' from the builder and set the search size accordingly. Also included unit tests to verify limit handling and compatibility with pagination options. * Refactor search options handling in SearchFactory Simplify and centralize option preparation by introducing the `prepareOptions` and `supportedOptions` methods in `SearchFactory`. Adjust tests to align with the new logic, ensuring size and from parameters are correctly set and prioritized. * Fix CI report * Add changelog and update documentation * Update changelog --- CHANGELOG.md | 11 ++- README.md | 19 +++++ src/ElasticSearch/SearchFactory.php | 24 +++++- .../Unit/ElasticSearch/SearchFactoryTest.php | 78 +++++++++++++++++++ 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 tests/Unit/ElasticSearch/SearchFactoryTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b16f21..011328f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ## [Unreleased] +## [7.9.0] - 2024-11-14 +### Fixed +- [Using pagination with custom query in Scout Builder](https://github.com/matchish/laravel-scout-elasticsearch/pull/290). +### Added +- [Using `options()` of a builder](https://github.com/matchish/laravel-scout-elasticsearch/issues/252) for set `from` parameter. +- Supporting `take()` method of builder for setting response `size`. + ## [7.8.0] - 2024-06-24 ### Added -- [Added supports of whereNotIn condition]([https://github.com/matchish/laravel-scout-elasticsearch/pull/282](https://github.com/matchish/laravel-scout-elasticsearch/pull/286). -- +- [Added supports of whereNotIn condition](https://github.com/matchish/laravel-scout-elasticsearch/pull/282). + ## [7.6.2] - 2024-06-24 ### Fixed - [Change if conditions order in soft deletes check for compatibility](https://github.com/matchish/laravel-scout-elasticsearch/pull/282). diff --git a/README.md b/README.md index dbb8d29c..4c0c83ab 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,25 @@ Product::search() Full list of ElasticSearch terms is in `vendor/handcraftedinthealps/elasticsearch-dsl/src/Query/TermLevel`. +### Pagination +The engine supports [Elasticsearch pagination](https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html) +with [Scout Builder pagination](https://laravel.com/docs/11.x/scout#pagination) or by setting page sizes +and offsets using the `->take($size)` method and `->options(['from' => $from])`. + +> Caution : Builder pagination takes precedence over the `take()` and `options()` setting. + +For example: + +```php +Product::search() + ->take(20) + ->options([ + 'from' => 20, + ]) + ->paginate(50); +``` +This will return the first 50 results, ignoring the specified offset. + ### Search amongst multiple models You can do it with `MixedSearch` class, just pass indices names separated by commas to the `within` method. ```php diff --git a/src/ElasticSearch/SearchFactory.php b/src/ElasticSearch/SearchFactory.php index 8b20d88d..b0ba367b 100644 --- a/src/ElasticSearch/SearchFactory.php +++ b/src/ElasticSearch/SearchFactory.php @@ -2,6 +2,7 @@ namespace Matchish\ScoutElasticSearch\ElasticSearch; +use Illuminate\Support\Arr; use Laravel\Scout\Builder; use ONGR\ElasticsearchDSL\BuilderInterface; use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery; @@ -15,11 +16,12 @@ final class SearchFactory { /** * @param Builder $builder - * @param array $options + * @param array $enforceOptions * @return Search */ - public static function create(Builder $builder, array $options = []): Search + public static function create(Builder $builder, array $enforceOptions = []): Search { + $options = static::prepareOptions($builder, $enforceOptions); $search = new Search(); $query = new QueryStringQuery($builder->query); if (static::hasWhereFilters($builder)) { @@ -135,4 +137,22 @@ private static function hasWhereNotIns($builder): bool { return isset($builder->whereNotIns) && ! empty($builder->whereNotIns); } + + private static function prepareOptions(Builder $builder, array $enforceOptions = []): array + { + $options = []; + + if (isset($builder->limit)) { + $options['size'] = $builder->limit; + } + + return array_merge($options, self::supportedOptions($builder), $enforceOptions); + } + + private static function supportedOptions(Builder $builder): array + { + return Arr::only($builder->options, [ + 'from', + ]); + } } diff --git a/tests/Unit/ElasticSearch/SearchFactoryTest.php b/tests/Unit/ElasticSearch/SearchFactoryTest.php new file mode 100644 index 00000000..731d7a13 --- /dev/null +++ b/tests/Unit/ElasticSearch/SearchFactoryTest.php @@ -0,0 +1,78 @@ +take($expectedSize = 50); + + $search = SearchFactory::create($builder); + + $this->assertEquals($expectedSize, $search->getSize()); + } + + public function test_limit_compatible_with_pagination(): void + { + $builder = new Builder(new Product(), '*'); + $builder->take(30); + + $search = SearchFactory::create($builder, [ + 'from' => 0, + 'size' => $expectedSize = 50, + ]); + + $this->assertEquals($expectedSize, $search->getSize()); + } + + public function test_size_set_in_options_dont_take_effect(): void + { + $builder = new Builder(new Product(), '*'); + $builder->take($expectedSize = 30) + ->options([ + 'size' => 100, + ]); + + $search = SearchFactory::create($builder); + + $this->assertEquals($expectedSize, $search->getSize()); + } + + public function test_from_set_in_options_take_effect(): void + { + $builder = new Builder(new Product(), '*'); + $builder->options([ + 'from' => $expectedFrom = 100, + ]); + + $search = SearchFactory::create($builder); + + $this->assertEquals($expectedFrom, $search->getFrom()); + } + + public function test_both_parameters_dont_take_effect_on_pagination(): void + { + $builder = new Builder(new Product(), '*'); + $builder->options([ + 'from' => 250, + ]) + ->take(30); + + $search = SearchFactory::create($builder, [ + 'from' => $expectedFrom = 100, + 'size' => $expectedSize = 50, + ]); + + $this->assertEquals($expectedSize, $search->getSize()); + $this->assertEquals($expectedFrom, $search->getFrom()); + } +}