-
-
Notifications
You must be signed in to change notification settings - Fork 505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add sort operator to $search stage #2554
Changes from all commits
a5745bf
9490058
6fa3a0c
b0f2ba5
9ab1b97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,17 @@ | |
use Doctrine\ODM\MongoDB\Aggregation\Stage\Search\SupportsAllSearchOperators; | ||
use Doctrine\ODM\MongoDB\Aggregation\Stage\Search\SupportsAllSearchOperatorsTrait; | ||
|
||
use function in_array; | ||
use function is_array; | ||
use function is_string; | ||
use function strtolower; | ||
|
||
/** | ||
* @psalm-import-type SortDirectionKeywords from Sort | ||
* @psalm-type CountType = 'lowerBound'|'total' | ||
* @psalm-type SortMetaKeywords = 'searchScore' | ||
* @psalm-type SortMeta = array{'$meta': SortMetaKeywords} | ||
* @psalm-type SortShape = array<string, int|SortMeta|SortDirectionKeywords> | ||
* @psalm-type SearchStageExpression = array{ | ||
* '$search': object{ | ||
* index?: string, | ||
|
@@ -25,6 +34,7 @@ | |
* maxNumPassages?: int, | ||
* }, | ||
* returnStoredSource?: bool, | ||
* sort?: object, | ||
* autocomplete?: object, | ||
* compound?: object, | ||
* embeddedDocument?: object, | ||
|
@@ -53,6 +63,9 @@ class Search extends Stage implements SupportsAllSearchOperators | |
private ?bool $returnStoredSource = null; | ||
private ?SearchOperator $operator = null; | ||
|
||
/** @var array<string, -1|1|SortMeta> */ | ||
private array $sort = []; | ||
|
||
public function __construct(Builder $builder) | ||
{ | ||
parent::__construct($builder); | ||
|
@@ -79,6 +92,10 @@ public function getExpression(): array | |
$params->returnStoredSource = $this->returnStoredSource; | ||
} | ||
|
||
if ($this->sort) { | ||
$params->sort = (object) $this->sort; | ||
} | ||
|
||
if ($this->operator !== null) { | ||
$operatorName = $this->operator->getOperatorName(); | ||
$params->$operatorName = $this->operator->getOperatorParams(); | ||
|
@@ -128,6 +145,33 @@ public function returnStoredSource(bool $returnStoredSource = true): static | |
return $this; | ||
} | ||
|
||
/** | ||
* @param array<string, int|string>|string $fieldName Field name or array of field/order pairs | ||
* @param int|string $order Field order (if one field is specified) | ||
* @psalm-param SortShape|string $fieldName | ||
* @psalm-param int|SortMeta|SortDirectionKeywords|null $order | ||
*/ | ||
public function sort($fieldName, $order = null): static | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add PHPDoc, especially given multiple meanings of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This all grew out of the query builder, which allows for precisely that: $builder
->sort('foo', 1)
->sort('bar', -1);
// Equivalent to the above
$builder
->sort(['foo' => 1, 'bar' => -1]); I've kept this strategy when creating the egg builder, and now it continues in the In the new aggregation pipeline builder we're creating we're instead leveraging named arguments for this: Stage::sort(foo: 1, bar: -1) Long story short, this is going to change once the MongoDB-supported builder is stable enough for us to use it underneath and deprecate ODM's builder :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
this looks nice :) looking forward to it! |
||
{ | ||
$allowedMetaSort = ['searchScore']; | ||
|
||
$fields = is_array($fieldName) ? $fieldName : [$fieldName => $order]; | ||
|
||
foreach ($fields as $fieldName => $order) { | ||
if (is_string($order)) { | ||
if (in_array($order, $allowedMetaSort, true)) { | ||
$order = ['$meta' => $order]; | ||
} else { | ||
$order = strtolower($order) === 'asc' ? 1 : -1; | ||
} | ||
} | ||
|
||
$this->sort[$fieldName] = $order; | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* @param T $operator | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,16 @@ | |
|
||
use Doctrine\ODM\MongoDB\Aggregation\Stage; | ||
use Doctrine\ODM\MongoDB\Aggregation\Stage\Search; | ||
use Doctrine\ODM\MongoDB\Aggregation\Stage\Sort; | ||
|
||
/** @internal */ | ||
/** | ||
* @internal | ||
* | ||
* @psalm-import-type SortDirectionKeywords from Sort | ||
* @psalm-import-type SortMetaKeywords from Search | ||
* @psalm-import-type SortMeta from Search | ||
* @psalm-import-type SortShape from Search | ||
*/ | ||
abstract class AbstractSearchOperator extends Stage implements SearchOperator | ||
{ | ||
public function __construct(private Search $search) | ||
|
@@ -35,6 +43,17 @@ public function returnStoredSource(bool $returnStoredSource): Search | |
return $this->search->returnStoredSource($returnStoredSource); | ||
} | ||
|
||
/** | ||
* @param array<string, int|string>|string $fieldName Field name or array of field/order pairs | ||
* @param int|string $order Field order (if one field is specified) | ||
* @psalm-param SortShape|string $fieldName | ||
* @psalm-param int|SortMeta|SortDirectionKeywords|null $order | ||
*/ | ||
public function sort($fieldName, $order = null): Search | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PHPDoc is missing here too |
||
{ | ||
return $this->search->sort($fieldName, $order); | ||
} | ||
|
||
/** | ||
* @return array<string, object> | ||
* @psalm-return non-empty-array<non-empty-string, object> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks odd, name suggests there can be multiple keywords yet this is a string. Is this coming from MongoDB itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used the same strategy we use in the
Sort
stage itself, where there are multiple meta sort options. In the$search
aggregation pipeline stage, the only allowed meta sort is on the search score, hence the single keyword here. I can also change the naming toSortMetaKeywordType
or something similar.