diff --git a/src/DefaultSearchFactory.php b/src/DefaultSearchFactory.php new file mode 100644 index 0000000..9d4c1a0 --- /dev/null +++ b/src/DefaultSearchFactory.php @@ -0,0 +1,114 @@ +query ? new QueryStringQuery($builder->query) : null; + + $filters = $this->mappedWheres( + array_merge($builder->wheres, $builder->whereIns) + ); + + if (!empty($filters)) { + $boolQuery = new BoolQuery(); + + foreach ($filters as $filter) { + $boolQuery->add($filter, BoolQuery::FILTER); + } + + if ($query) { + $boolQuery->add($query); + } + } + + $sorts = $this->mappedSorts($builder, $cursor); + + /**@var FieldSort $sort */ + foreach ($sorts as $sort) { + $search->addSort($sort); + } + + $this->setOptions($search, $options); + + return empty($boolQuery) + ? $search->addQuery($query) + : $search->addQuery($boolQuery); + } + + /** + * @param array $wheres + * @return (TermsQuery|TermQuery)[] + */ + protected function mappedWheres(array $wheres): array + { + $clauses = []; + + foreach ($wheres as $field => $value) { + $clauses[] = is_array($value) + ? new TermsQuery((string) $field, $value) + : new TermQuery((string) $field, $value); + } + + return $clauses; + } + + /** + * @param Builder $builder + * @return FieldSort[] + */ + protected function mappedSorts(Builder $builder, ?Cursor $cursor): array + { + $sorts = array_map( + function ($order) { + return is_array($order) + ? new FieldSort($order['column'], $order['direction']) + : $order; + }, + $builder->orders + ); + + if ($cursor && $cursor->pointsToPreviousItems()) { + array_walk($sorts, [$this, "invertOrder"]); + } + + return $sorts; + } + + protected function invertOrder(FieldSort $sort): void + { + $direction = $sort->getOrder() ?? 'asc'; + + $sort->setOrder( + $direction === 'asc' ? 'desc' : 'asc' + ); + } + + protected function setOptions(Search $search, array $options): void + { + if ($from = $options["from"] ?? null) { + $search->setFrom($from); + } + + if ($size = $options["size"] ?? null) { + $search->setSize($size); + } + + if ($searchAfter = $options["searchAfter"] ?? null) { + $search->setSearchAfter($searchAfter); + } + } +} diff --git a/src/Engines/OpenSearchEngine.php b/src/Engines/OpenSearchEngine.php index c3b81da..4e24c0b 100644 --- a/src/Engines/OpenSearchEngine.php +++ b/src/Engines/OpenSearchEngine.php @@ -17,8 +17,10 @@ class OpenSearchEngine extends Engine { - public function __construct(public Client $opensearch) - { + public function __construct( + public Client $opensearch, + private SearchFactory $searchFactory + ) { // } @@ -114,7 +116,7 @@ protected function performSearch( array $options = [], Cursor $cursor = null ) { - $searchBody = SearchFactory::create($builder, $options, $cursor); + $searchBody = $this->searchFactory->create($builder, $options, $cursor); if ($builder->callback) { /** @var callable */ $callback = $builder->callback; diff --git a/src/Providers/OpenSearchServiceProvider.php b/src/Providers/OpenSearchServiceProvider.php index b56ad81..2b892e5 100644 --- a/src/Providers/OpenSearchServiceProvider.php +++ b/src/Providers/OpenSearchServiceProvider.php @@ -2,6 +2,7 @@ namespace CloudMediaSolutions\LaravelScoutOpenSearch\Providers; +use CloudMediaSolutions\LaravelScoutOpenSearch\DefaultSearchFactory; use CloudMediaSolutions\LaravelScoutOpenSearch\Engines\OpenSearchEngine; use Illuminate\Pagination\CursorPaginator; use Illuminate\Support\ServiceProvider; @@ -10,6 +11,7 @@ use ONGR\ElasticsearchDSL\Sort\FieldSort; use OpenSearch\Client; use OpenSearch\ClientBuilder; +use SearchFactory; class OpenSearchServiceProvider extends ServiceProvider { @@ -26,12 +28,16 @@ public function boot() $this->app->make(EngineManager::class)->extend(OpenSearchEngine::class, function () { $opensearch = app(Client::class); + $searchFactory = app(SearchFactory::class); - return new OpenSearchEngine($opensearch); + return new OpenSearchEngine($opensearch, $searchFactory); }); $this->app->bind(Client::class, function () { return ClientBuilder::fromConfig(config('opensearch.client')); }); + $this->app->bind(SearchFactory::class, function () { + return new DefaultSearchFactory; + }); Builder::macro('cursorPaginate', function (int $perPage = null, string $cursorName = 'cursor', $cursor = null): CursorPaginator { /** diff --git a/src/SearchFactory.php b/src/SearchFactory.php index e1f941f..6d6bb73 100644 --- a/src/SearchFactory.php +++ b/src/SearchFactory.php @@ -4,114 +4,14 @@ use Illuminate\Pagination\Cursor; use Laravel\Scout\Builder; -use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery; -use ONGR\ElasticsearchDSL\Query\FullText\QueryStringQuery; -use ONGR\ElasticsearchDSL\Query\TermLevel\TermQuery; -use ONGR\ElasticsearchDSL\Query\TermLevel\TermsQuery; use ONGR\ElasticsearchDSL\Search; -use ONGR\ElasticsearchDSL\Sort\FieldSort; -final class SearchFactory +interface SearchFactory { - public static function create(Builder $builder, array $options = [], Cursor $cursor = null): Search - { - $search = new Search(); - - $query = $builder->query ? new QueryStringQuery($builder->query) : null; - - if (static::hasWhereFilters($builder)) { - $boolQuery = new BoolQuery(); - $boolQuery = static::addWheres($builder, $boolQuery); - $boolQuery = static::addWhereIns($builder, $boolQuery); - - if ($query) { - $boolQuery->add($query); - } - $search->addQuery($boolQuery); - } elseif($query) { - $search->addQuery($query); - } - - if (array_key_exists('from', $options)) { - $search->setFrom($options['from']); - } - - if (array_key_exists('size', $options)) { - $search->setSize($options['size']); - } - - if (array_key_exists('searchAfter', $options)) { - $search->setSearchAfter($options['searchAfter']); - } - - if (! empty($builder->orders)) { - $search = static::addOrders($builder, $cursor, $search); - } - - return $search; - } - - private static function hasWhereFilters(Builder $builder): bool - { - return static::hasWheres($builder) || static::hasWhereIns($builder); - } - - private static function hasWheres(Builder $builder): bool - { - return ! empty($builder->wheres); - } - - private static function hasWhereIns(Builder $builder): bool - { - return ! empty($builder->whereIns); - } - - private static function addWheres(Builder $builder, BoolQuery $boolQuery): BoolQuery - { - if (static::hasWheres($builder)) { - foreach ($builder->wheres as $field => $value) { - $boolQuery->add(new TermQuery((string) $field, $value), BoolQuery::FILTER); - } - } - - return $boolQuery; - } - - private static function addWhereIns($builder, $boolQuery): BoolQuery - { - if (static::hasWhereIns($builder)) { - foreach ($builder->whereIns as $field => $arrayOfValues) { - $boolQuery->add(new TermsQuery((string) $field, $arrayOfValues), BoolQuery::FILTER); - } - } - - return $boolQuery; - } - - private static function addOrders(Builder $builder, ?Cursor $cursor, Search $search): Search - { - $sorts = array_map( - function ($order) use ($cursor) { - $sort = is_array($order) - ? new FieldSort($order['column'], $order['direction']) - : $order; - - $direction = $sort->getOrder() ?? 'asc'; - - if ($cursor && $cursor->pointsToPreviousItems()) { - $direction = $direction === 'asc' ? 'desc' : 'asc'; - $sort->setOrder($direction); - } - - return $sort; - }, - $builder->orders - ); - - foreach ($sorts as $sort) { - $search->addSort($sort); - } - - return $search; - } + public function create( + Builder $builder, + array $options = [], + ?Cursor $cursor + ): Search; } + diff --git a/tests/Engines/OpenSearchEngineTest.php b/tests/Engines/OpenSearchEngineTest.php index 4d8162d..8a4d06a 100644 --- a/tests/Engines/OpenSearchEngineTest.php +++ b/tests/Engines/OpenSearchEngineTest.php @@ -2,6 +2,7 @@ namespace Tests\Engines; +use CloudMediaSolutions\LaravelScoutOpenSearch\DefaultSearchFactory; use CloudMediaSolutions\LaravelScoutOpenSearch\Engines\OpenSearchEngine; use CloudMediaSolutions\LaravelScoutOpenSearch\Providers\OpenSearchServiceProvider; use Illuminate\Pagination\Cursor; @@ -24,7 +25,7 @@ protected function setUp(): void { parent::setUp(); $this->client = Mockery::mock(Client::class); - $this->engine = new OpenSearchEngine($this->client); + $this->engine = new OpenSearchEngine($this->client, new DefaultSearchFactory); }