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()); + } +}