forked from doctrine/mongodb-odm
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add $search stage to aggregation pipeline builder (doctrine#2516)
* Add $search stage to aggregation pipeline builder * Make AbstractSearchOperator::getExpression final * Make appendScore method private for scored operators * Add documentation links to search operator classes * Separate wildcard and regex search operators
- Loading branch information
Showing
60 changed files
with
3,829 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ODM\MongoDB\Aggregation\Stage; | ||
|
||
use Doctrine\ODM\MongoDB\Aggregation\Builder; | ||
use Doctrine\ODM\MongoDB\Aggregation\Stage; | ||
use Doctrine\ODM\MongoDB\Aggregation\Stage\Search\SearchOperator; | ||
use Doctrine\ODM\MongoDB\Aggregation\Stage\Search\SupportsAllSearchOperators; | ||
use Doctrine\ODM\MongoDB\Aggregation\Stage\Search\SupportsAllSearchOperatorsTrait; | ||
|
||
/** | ||
* @psalm-type CountType = 'lowerBound'|'total' | ||
* @psalm-type SearchStageExpression = array{ | ||
* '$search': object{ | ||
* index?: string, | ||
* count?: object{ | ||
* type: CountType, | ||
* threshold?: int, | ||
* }, | ||
* highlight?: object{ | ||
* path: string, | ||
* maxCharsToExamine?: int, | ||
* maxNumPassages?: int, | ||
* }, | ||
* returnStoredSource?: bool, | ||
* autocomplete?: object, | ||
* compound?: object, | ||
* embeddedDocument?: object, | ||
* equals?: object, | ||
* exists?: object, | ||
* geoShape?: object, | ||
* geoWithin?: object, | ||
* moreLikeThis?: object, | ||
* near?: object, | ||
* phrase?: object, | ||
* queryString?: object, | ||
* range?: object, | ||
* regex?: object, | ||
* text?: object, | ||
* wildcard?: object, | ||
* } | ||
* } | ||
*/ | ||
class Search extends Stage implements SupportsAllSearchOperators | ||
{ | ||
use SupportsAllSearchOperatorsTrait; | ||
|
||
private string $indexName = ''; | ||
private ?object $count = null; | ||
private ?object $highlight = null; | ||
private ?bool $returnStoredSource = null; | ||
private ?SearchOperator $operator = null; | ||
|
||
public function __construct(Builder $builder) | ||
{ | ||
parent::__construct($builder); | ||
} | ||
|
||
/** @psalm-return SearchStageExpression */ | ||
public function getExpression(): array | ||
{ | ||
$params = (object) []; | ||
|
||
if ($this->indexName) { | ||
$params->index = $this->indexName; | ||
} | ||
|
||
if ($this->count) { | ||
$params->count = $this->count; | ||
} | ||
|
||
if ($this->highlight) { | ||
$params->highlight = $this->highlight; | ||
} | ||
|
||
if ($this->returnStoredSource !== null) { | ||
$params->returnStoredSource = $this->returnStoredSource; | ||
} | ||
|
||
if ($this->operator !== null) { | ||
$operatorName = $this->operator->getOperatorName(); | ||
$params->$operatorName = $this->operator->getOperatorParams(); | ||
} | ||
|
||
return ['$search' => $params]; | ||
} | ||
|
||
public function index(string $name): static | ||
{ | ||
$this->indexName = $name; | ||
|
||
return $this; | ||
} | ||
|
||
/** @psalm-param CountType $type */ | ||
public function countDocuments(string $type, ?int $threshold = null): static | ||
{ | ||
$this->count = (object) ['type' => $type]; | ||
|
||
if ($threshold !== null) { | ||
$this->count->threshold = $threshold; | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
public function highlight(string $path, ?int $maxCharsToExamine = null, ?int $maxNumPassages = null): static | ||
{ | ||
$this->highlight = (object) ['path' => $path]; | ||
|
||
if ($maxCharsToExamine !== null) { | ||
$this->highlight->maxCharsToExamine = $maxCharsToExamine; | ||
} | ||
|
||
if ($maxNumPassages !== null) { | ||
$this->highlight->maxNumPassages = $maxNumPassages; | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
public function returnStoredSource(bool $returnStoredSource = true): static | ||
{ | ||
$this->returnStoredSource = $returnStoredSource; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @param T $operator | ||
* | ||
* @return T | ||
* | ||
* @template T of SearchOperator | ||
*/ | ||
protected function addOperator(SearchOperator $operator): SearchOperator | ||
{ | ||
return $this->operator = $operator; | ||
} | ||
|
||
protected function getSearchStage(): static | ||
{ | ||
return $this; | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/AbstractSearchOperator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ODM\MongoDB\Aggregation\Stage\Search; | ||
|
||
use Doctrine\ODM\MongoDB\Aggregation\Stage; | ||
use Doctrine\ODM\MongoDB\Aggregation\Stage\Search; | ||
|
||
/** @internal */ | ||
abstract class AbstractSearchOperator extends Stage implements SearchOperator | ||
{ | ||
public function __construct(private Search $search) | ||
{ | ||
parent::__construct($search->builder); | ||
} | ||
|
||
public function index(string $name): Search | ||
{ | ||
return $this->search->index($name); | ||
} | ||
|
||
public function countDocuments(string $type, ?int $threshold = null): Search | ||
{ | ||
return $this->search->countDocuments($type, $threshold); | ||
} | ||
|
||
public function highlight(string $path, ?int $maxCharsToExamine = null, ?int $maxNumPassages = null): Search | ||
{ | ||
return $this->search->highlight($path, $maxCharsToExamine, $maxNumPassages); | ||
} | ||
|
||
public function returnStoredSource(bool $returnStoredSource): Search | ||
{ | ||
return $this->search->returnStoredSource($returnStoredSource); | ||
} | ||
|
||
/** @return array<string, object> */ | ||
final public function getExpression(): array | ||
{ | ||
return [$this->getOperatorName() => $this->getOperatorParams()]; | ||
} | ||
|
||
protected function getSearchStage(): Search | ||
{ | ||
return $this->search; | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Search/Autocomplete.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\ODM\MongoDB\Aggregation\Stage\Search; | ||
|
||
use Doctrine\ODM\MongoDB\Aggregation\Stage\Search; | ||
|
||
use function array_values; | ||
|
||
/** | ||
* @internal | ||
* | ||
* @see https://www.mongodb.com/docs/atlas/atlas-search/autocomplete/ | ||
*/ | ||
class Autocomplete extends AbstractSearchOperator implements ScoredSearchOperator | ||
{ | ||
use ScoredSearchOperatorTrait; | ||
|
||
/** @var list<string> */ | ||
private array $query; | ||
private string $path; | ||
private string $tokenOrder = ''; | ||
private ?object $fuzzy = null; | ||
|
||
public function __construct(Search $search, string $path, string ...$query) | ||
{ | ||
parent::__construct($search); | ||
|
||
$this->query(...$query); | ||
$this->path($path); | ||
} | ||
|
||
public function query(string ...$query): static | ||
{ | ||
$this->query = array_values($query); | ||
|
||
return $this; | ||
} | ||
|
||
public function path(string $path): static | ||
{ | ||
$this->path = $path; | ||
|
||
return $this; | ||
} | ||
|
||
public function tokenOrder(string $order): static | ||
{ | ||
$this->tokenOrder = $order; | ||
|
||
return $this; | ||
} | ||
|
||
public function fuzzy(?int $maxEdits = null, ?int $prefixLength = null, ?int $maxExpansions = null): static | ||
{ | ||
$this->fuzzy = (object) []; | ||
if ($maxEdits !== null) { | ||
$this->fuzzy->maxEdits = $maxEdits; | ||
} | ||
|
||
if ($prefixLength !== null) { | ||
$this->fuzzy->prefixLength = $prefixLength; | ||
} | ||
|
||
if ($maxExpansions !== null) { | ||
$this->fuzzy->maxExpansions = $maxExpansions; | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
public function getOperatorName(): string | ||
{ | ||
return 'autocomplete'; | ||
} | ||
|
||
public function getOperatorParams(): object | ||
{ | ||
$params = (object) [ | ||
'query' => $this->query, | ||
'path' => $this->path, | ||
]; | ||
|
||
if ($this->tokenOrder) { | ||
$params->tokenOrder = $this->tokenOrder; | ||
} | ||
|
||
if ($this->fuzzy) { | ||
$params->fuzzy = $this->fuzzy; | ||
} | ||
|
||
return $this->appendScore($params); | ||
} | ||
} |
Oops, something went wrong.