Skip to content

Commit b491d6e

Browse files
committed
Docstring and minor changes.
1 parent 2baafcf commit b491d6e

File tree

4 files changed

+80
-33
lines changed

4 files changed

+80
-33
lines changed

django_mongodb_backend/compiler.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@ def _prepare_search_expressions_for_pipeline(self, expression, search_idx, repla
116116
replacements[sub_expr] = self._get_replace_expr(sub_expr, searches, alias)
117117

118118
def _prepare_search_query_for_aggregation_pipeline(self, order_by):
119+
"""
120+
Prepare expressions for the search pipeline.
121+
122+
Handle the computation of search functions used by various
123+
expressions. Separate and create intermediate columns, and replace
124+
nodes to simulate a search operation.
125+
126+
MongoDB's $search or $searchVector are stages. To apply operations over them,
127+
compute the $search or $vectorSearch first, then apply additional operations in a subsequent
128+
stage by replacing the aggregate expressions with new document field prefixed
129+
by `__search_expr.search#`.
130+
"""
119131
replacements = {}
120132
annotation_group_idx = itertools.count(start=1)
121133
for expr in self.query.annotation_select.values():
@@ -237,6 +249,15 @@ def _build_aggregation_pipeline(self, ids, group):
237249
return pipeline
238250

239251
def _compound_searches_queries(self, search_replacements):
252+
"""
253+
Builds a query pipeline from a mapping of search expressions to result columns.
254+
255+
Currently, only a single `$search` or `$vectorSearch` expression is supported.
256+
Combining multiple search expressions is not yet allowed and will raise a ValueError.
257+
258+
This method will eventually support hybrid search by allowing the combination of
259+
`$search` and `$vectorSearch` operations.
260+
"""
240261
if not search_replacements:
241262
return []
242263
if len(search_replacements) > 1:
@@ -250,7 +271,7 @@ def _compound_searches_queries(self, search_replacements):
250271
"If you need to combine them, consider restructuring your query logic or "
251272
"running them as separate queries."
252273
)
253-
if not has_search:
274+
if has_vector_search:
254275
raise ValueError(
255276
"Cannot combine two `$vectorSearch` operator. "
256277
"If you need to combine them, consider restructuring your query logic or "
@@ -312,8 +333,7 @@ def pre_sql_setup(self, with_col_aliases=False):
312333
}
313334
self.order_by_objs = [expr.replace_expressions(all_replacements) for expr, _ in order_by]
314335
if (where := self.get_where()) and search_replacements:
315-
where = where.replace_expressions(search_replacements)
316-
self.set_where(where)
336+
self.set_where(where.replace_expressions(search_replacements))
317337
return extra_select, order_by, group_by
318338

319339
def execute_sql(
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from .search import (
2+
CombinedSearchExpression,
3+
CompoundExpression,
4+
SearchAutocomplete,
5+
SearchEquals,
6+
SearchExists,
7+
SearchGeoShape,
8+
SearchGeoWithin,
9+
SearchIn,
10+
SearchMoreLikeThis,
11+
SearchPhrase,
12+
SearchQueryString,
13+
SearchRange,
14+
SearchRegex,
15+
SearchScoreOption,
16+
SearchText,
17+
SearchVector,
18+
SearchWildcard,
19+
)
20+
21+
__all__ = [
22+
"CombinedSearchExpression",
23+
"CompoundExpression",
24+
"SearchAutocomplete",
25+
"SearchEquals",
26+
"SearchExists",
27+
"SearchGeoShape",
28+
"SearchGeoWithin",
29+
"SearchIn",
30+
"SearchMoreLikeThis",
31+
"SearchPhrase",
32+
"SearchQueryString",
33+
"SearchRange",
34+
"SearchRegex",
35+
"SearchScoreOption",
36+
"SearchText",
37+
"SearchVector",
38+
"SearchWildcard",
39+
]

django_mongodb_backend/expressions/builtins.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ def col(self, compiler, connection, as_path=False): # noqa: ARG001
7171
# Add the column's collection's alias for columns in joined collections.
7272
has_alias = self.alias and self.alias != compiler.collection_name
7373
prefix = f"{self.alias}." if has_alias else ""
74-
return f"{prefix}{self.target.column}" if as_path else f"${prefix}{self.target.column}"
74+
if not as_path:
75+
prefix = f"${prefix}"
76+
return f"{prefix}{self.target.column}"
7577

7678

7779
def col_pairs(self, compiler, connection):

tests/queries_/test_search.py

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from django.test import TransactionTestCase, skipUnlessDBFeature
1111
from pymongo.operations import SearchIndexModel
1212

13-
from django_mongodb_backend.expressions.search import (
13+
from django_mongodb_backend.expressions import (
1414
CompoundExpression,
1515
SearchAutocomplete,
1616
SearchEquals,
@@ -84,7 +84,7 @@ def drop_index():
8484

8585

8686
@skipUnlessDBFeature("supports_atlas_search")
87-
class SearchEqualsTest(SearchUtilsMixin):
87+
class SearchEqualsTests(SearchUtilsMixin):
8888
@classmethod
8989
def setUpClass(cls):
9090
super().setUpClass()
@@ -100,7 +100,6 @@ def setUpClass(cls):
100100
)
101101

102102
def setUp(self):
103-
super().setUp()
104103
self.article = Article.objects.create(headline="cross", number=1, body="body")
105104
Article.objects.create(headline="other thing", number=2, body="body")
106105

@@ -110,7 +109,6 @@ def test_search_equals(self):
110109

111110
def test_boost_score(self):
112111
boost_score = SearchScoreOption({"boost": {"value": 3}})
113-
114112
qs = Article.objects.annotate(
115113
score=SearchEquals(path="headline", value="cross", score=boost_score)
116114
)
@@ -148,7 +146,7 @@ def test_function_score(self):
148146

149147

150148
@skipUnlessDBFeature("supports_atlas_search")
151-
class SearchAutocompleteTest(SearchUtilsMixin):
149+
class SearchAutocompleteTests(SearchUtilsMixin):
152150
@classmethod
153151
def setUpClass(cls):
154152
super().setUpClass()
@@ -186,7 +184,6 @@ def setUpClass(cls):
186184
)
187185

188186
def setUp(self):
189-
super().setUp()
190187
self.article = Article.objects.create(
191188
headline="crossing and something",
192189
number=2,
@@ -229,7 +226,7 @@ def test_constant_score(self):
229226

230227

231228
@skipUnlessDBFeature("supports_atlas_search")
232-
class SearchExistsTest(SearchUtilsMixin):
229+
class SearchExistsTests(SearchUtilsMixin):
233230
@classmethod
234231
def setUpClass(cls):
235232
super().setUpClass()
@@ -240,7 +237,6 @@ def setUpClass(cls):
240237
)
241238

242239
def setUp(self):
243-
super().setUp()
244240
self.article = Article.objects.create(headline="ignored", number=3, body="something")
245241

246242
def test_search_exists(self):
@@ -256,7 +252,7 @@ def test_constant_score(self):
256252

257253

258254
@skipUnlessDBFeature("supports_atlas_search")
259-
class SearchInTest(SearchUtilsMixin):
255+
class SearchInTests(SearchUtilsMixin):
260256
@classmethod
261257
def setUpClass(cls):
262258
super().setUpClass()
@@ -267,7 +263,6 @@ def setUpClass(cls):
267263
)
268264

269265
def setUp(self):
270-
super().setUp()
271266
self.article = Article.objects.create(headline="cross", number=1, body="a")
272267
Article.objects.create(headline="road", number=2, body="b")
273268

@@ -286,7 +281,7 @@ def test_constant_score(self):
286281

287282

288283
@skipUnlessDBFeature("supports_atlas_search")
289-
class SearchPhraseTest(SearchUtilsMixin):
284+
class SearchPhraseTests(SearchUtilsMixin):
290285
@classmethod
291286
def setUpClass(cls):
292287
super().setUpClass()
@@ -297,7 +292,6 @@ def setUpClass(cls):
297292
)
298293

299294
def setUp(self):
300-
super().setUp()
301295
self.article = Article.objects.create(
302296
headline="irrelevant", number=1, body="the quick brown fox"
303297
)
@@ -318,7 +312,7 @@ def test_constant_score(self):
318312

319313

320314
@skipUnlessDBFeature("supports_atlas_search")
321-
class SearchRangeTest(SearchUtilsMixin):
315+
class SearchRangeTests(SearchUtilsMixin):
322316
@classmethod
323317
def setUpClass(cls):
324318
super().setUpClass()
@@ -330,7 +324,6 @@ def setUpClass(cls):
330324
Article.objects.create(headline="x", number=5, body="z")
331325

332326
def setUp(self):
333-
super().setUp()
334327
self.number20 = Article.objects.create(headline="y", number=20, body="z")
335328

336329
def test_search_range(self):
@@ -348,7 +341,7 @@ def test_constant_score(self):
348341

349342

350343
@skipUnlessDBFeature("supports_atlas_search")
351-
class SearchRegexTest(SearchUtilsMixin):
344+
class SearchRegexTests(SearchUtilsMixin):
352345
@classmethod
353346
def setUpClass(cls):
354347
super().setUpClass()
@@ -364,7 +357,6 @@ def setUpClass(cls):
364357
)
365358

366359
def setUp(self):
367-
super().setUp()
368360
self.article = Article.objects.create(headline="hello world", number=1, body="abc")
369361
Article.objects.create(headline="hola mundo", number=2, body="abc")
370362

@@ -387,7 +379,7 @@ def test_constant_score(self):
387379

388380

389381
@skipUnlessDBFeature("supports_atlas_search")
390-
class SearchTextTest(SearchUtilsMixin):
382+
class SearchTextTests(SearchUtilsMixin):
391383
@classmethod
392384
def setUpClass(cls):
393385
super().setUpClass()
@@ -398,7 +390,6 @@ def setUpClass(cls):
398390
)
399391

400392
def setUp(self):
401-
super().setUp()
402393
self.article = Article.objects.create(
403394
headline="ignored", number=1, body="The lazy dog sleeps"
404395
)
@@ -437,7 +428,7 @@ def test_constant_score(self):
437428

438429

439430
@skipUnlessDBFeature("supports_atlas_search")
440-
class SearchWildcardTest(SearchUtilsMixin):
431+
class SearchWildcardTests(SearchUtilsMixin):
441432
@classmethod
442433
def setUpClass(cls):
443434
super().setUpClass()
@@ -453,7 +444,6 @@ def setUpClass(cls):
453444
)
454445

455446
def setUp(self):
456-
super().setUp()
457447
self.article = Article.objects.create(headline="dark-knight", number=1, body="")
458448
Article.objects.create(headline="batman", number=2, body="")
459449

@@ -472,7 +462,7 @@ def test_constant_score(self):
472462

473463

474464
@skipUnlessDBFeature("supports_atlas_search")
475-
class SearchGeoShapeTest(SearchUtilsMixin):
465+
class SearchGeoShapeTests(SearchUtilsMixin):
476466
@classmethod
477467
def setUpClass(cls):
478468
super().setUpClass()
@@ -488,7 +478,6 @@ def setUpClass(cls):
488478
)
489479

490480
def setUp(self):
491-
super().setUp()
492481
self.article = Article.objects.create(
493482
headline="any", number=1, body="", location={"type": "Point", "coordinates": [40, 5]}
494483
)
@@ -523,7 +512,7 @@ def test_constant_score(self):
523512

524513

525514
@skipUnlessDBFeature("supports_atlas_search")
526-
class SearchGeoWithinTest(SearchUtilsMixin):
515+
class SearchGeoWithinTests(SearchUtilsMixin):
527516
@classmethod
528517
def setUpClass(cls):
529518
super().setUpClass()
@@ -534,7 +523,6 @@ def setUpClass(cls):
534523
)
535524

536525
def setUp(self):
537-
super().setUp()
538526
self.article = Article.objects.create(
539527
headline="geo", number=2, body="", location={"type": "Point", "coordinates": [40, 5]}
540528
)
@@ -577,7 +565,7 @@ def test_constant_score(self):
577565

578566
@skipUnlessDBFeature("supports_atlas_search")
579567
@unittest.expectedFailure
580-
class SearchMoreLikeThisTest(SearchUtilsMixin):
568+
class SearchMoreLikeThisTests(SearchUtilsMixin):
581569
@classmethod
582570
def setUpClass(cls):
583571
super().setUpClass()
@@ -620,7 +608,7 @@ def test_search_more_like_this(self):
620608

621609

622610
@skipUnlessDBFeature("supports_atlas_search")
623-
class CompoundSearchTest(SearchUtilsMixin):
611+
class CompoundSearchTests(SearchUtilsMixin):
624612
@classmethod
625613
def setUpClass(cls):
626614
super().setUpClass()
@@ -640,7 +628,6 @@ def setUpClass(cls):
640628
)
641629

642630
def setUp(self):
643-
super().setUp()
644631
self.mars_mission = Article.objects.create(
645632
number=1,
646633
headline="space exploration",
@@ -785,7 +772,7 @@ def test_search_and_filter(self):
785772

786773

787774
@skipUnlessDBFeature("supports_atlas_search")
788-
class SearchVectorTest(SearchUtilsMixin):
775+
class SearchVectorTests(SearchUtilsMixin):
789776
@classmethod
790777
def setUpClass(cls):
791778
super().setUpClass()
@@ -807,7 +794,6 @@ def setUpClass(cls):
807794
)
808795

809796
def setUp(self):
810-
super().setUp()
811797
self.mars = Article.objects.create(
812798
headline="Mars landing",
813799
number=1,

0 commit comments

Comments
 (0)