Skip to content

Commit 75c5e4d

Browse files
committed
Move $search and $vectorSearch to beginning of pipelines
1 parent 4d3ab60 commit 75c5e4d

File tree

3 files changed

+35
-3
lines changed

3 files changed

+35
-3
lines changed

docs/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Development
1616
- BREAKING CHANGE: wrap _document_registry (normally not used by end users) with _DocumentRegistry which acts as a singleton to access the registry
1717
- Log a warning in case users creates multiple Document classes with the same name as it can lead to unexpected behavior #1778
1818
- Fix use of $geoNear or $collStats in aggregate #2493
19+
- Fix use of $search or $vectorSearch in aggregate #2878
1920
- BREAKING CHANGE: Further to the deprecation warning, remove ability to use an unpacked list to `Queryset.aggregate(*pipeline)`, a plain list must be provided instead `Queryset.aggregate(pipeline)`, as it's closer to pymongo interface
2021
- BREAKING CHANGE: Further to the deprecation warning, remove `full_response` from `QuerySet.modify` as it wasn't supported with Pymongo 3+
2122
- Fixed stacklevel of many warnings (to point places emitting the warning more accurately)

mongoengine/queryset/base.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1398,9 +1398,10 @@ def aggregate(self, pipeline, **kwargs):
13981398
first_step = []
13991399
new_user_pipeline = []
14001400
for step_step in pipeline:
1401-
if "$geoNear" in step_step:
1402-
first_step.append(step_step)
1403-
elif "$collStats" in step_step:
1401+
if any(
1402+
el in step_step
1403+
for el in ["$geoNear", "$collStats", "$search", "$vectorSearch"]
1404+
):
14041405
first_step.append(step_step)
14051406
else:
14061407
new_user_pipeline.append(step_step)

tests/queryset/test_queryset_aggregation.py

+30
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import sys
2+
from unittest.mock import patch
3+
14
import pytest
25
from pymongo.read_preferences import ReadPreference
36

@@ -373,3 +376,30 @@ class SomeDoc(Document):
373376
res = list(SomeDoc.objects.aggregate(pipeline))
374377
assert len(res) == 1
375378
assert res[0]["count"] == 2
379+
380+
def test_aggregate_search_used_as_initial_step_before_cls_implicit_step(self):
381+
class SearchableDoc(Document):
382+
first_name = StringField()
383+
last_name = StringField()
384+
385+
privileged_step = {
386+
"$search": {
387+
"index": "default",
388+
"autocomplete": {
389+
"query": "foo",
390+
"path": "first_name",
391+
},
392+
}
393+
}
394+
pipeline = [privileged_step]
395+
396+
# Search requires an Atlas instance, so we instead mock the aggregation call to inspect the final pipeline
397+
with patch.object(SearchableDoc, "_collection") as coll_mk:
398+
SearchableDoc.objects(last_name="bar").aggregate(pipeline)
399+
if sys.version_info < (3, 8):
400+
final_pipeline = coll_mk.aggregate.call_args_list[0][0][0]
401+
else:
402+
final_pipeline = coll_mk.aggregate.call_args_list[0].args[0]
403+
assert len(final_pipeline) == 2
404+
# $search moved before the $match on last_name
405+
assert final_pipeline[0] == privileged_step

0 commit comments

Comments
 (0)