Skip to content

Commit ca8a7cf

Browse files
committed
Refactor.
1 parent 89d3ef9 commit ca8a7cf

File tree

3 files changed

+290
-158
lines changed

3 files changed

+290
-158
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,4 @@ repos:
8181
rev: "v2.2.6"
8282
hooks:
8383
- id: codespell
84-
args: ["-L", "nin", "SearchIn", "searchin"]
84+
args: ["-L", "nin", "-L", "searchin"]

django_mongodb_backend/expressions/builtins.py

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from bson import Decimal128
66
from django.core.exceptions import EmptyResultSet, FullResultSet
77
from django.db import NotSupportedError
8+
from django.db.models import Expression, FloatField
89
from django.db.models.expressions import (
910
Case,
1011
Col,
@@ -212,6 +213,293 @@ def value(self, compiler, connection): # noqa: ARG001
212213
return value
213214

214215

216+
class SearchExpression(Expression):
217+
def __init__(self):
218+
super().__init__(output_field=FloatField())
219+
220+
def get_source_expressions(self):
221+
return []
222+
223+
def __str__(self):
224+
args = ", ".join(map(str, self.get_source_expressions()))
225+
return f"{self.search_type}({args})"
226+
227+
def __repr__(self):
228+
return str(self)
229+
230+
def as_sql(self, compiler, connection):
231+
return "", []
232+
233+
def _get_query_index(self, fields, compiler):
234+
fields = set(fields)
235+
for search_indexes in compiler.collection.list_search_indexes():
236+
mappings = search_indexes["latestDefinition"]["mappings"]
237+
if mappings["dynamic"] or fields.issubset(set(mappings["fields"])):
238+
return search_indexes["name"]
239+
return "default"
240+
241+
242+
class SearchAutocomplete(SearchExpression):
243+
def __init__(self, path, query, score=None):
244+
self.path = F(path)
245+
self.query = Value(query)
246+
self.score = score
247+
super().__init__()
248+
249+
def as_mql(self, compiler, connection):
250+
params = {
251+
"path": self.path.as_mql(compiler, connection)[1:],
252+
"query": self.query.as_mql(compiler, connection),
253+
}
254+
if self.score is not None:
255+
params["score"] = self.score
256+
index = self._get_query_index([self.path], compiler)
257+
return {"$search": {"autocomplete": params, "index": index}}
258+
259+
260+
class SearchEquals(SearchExpression):
261+
def __init__(self, path, value, score=None):
262+
self.path = F(path)
263+
self.value = Value(query)
264+
self.score = score
265+
super().__init__()
266+
267+
def as_mql(self, compiler, connection):
268+
params = {
269+
"path": self.path.as_mql(compiler, connection)[1:],
270+
"value": self.value.as_mql(compiler, connection),
271+
}
272+
if self.score is not None:
273+
params["score"] = self.score
274+
index = self._get_query_index([self.path], compiler)
275+
return {"$search": {"equals": params, "index": index}}
276+
277+
278+
class SearchExists(SearchExpression):
279+
def __init__(self, path, score=None):
280+
self.path = F(path)
281+
self.score = score
282+
super().__init__()
283+
284+
def as_mql(self, compiler, connection):
285+
params = {
286+
"path": self.path.as_mql(compiler, connection)[1:],
287+
}
288+
if self.score is not None:
289+
params["score"] = self.score
290+
index = self._get_query_index([self.path], compiler)
291+
return {"$search": {"exists": params, "index": index}}
292+
293+
294+
class SearchIn(SearchExpression):
295+
def __init__(self, path, value, score=None):
296+
self.path = F(path)
297+
self.value = Value(value)
298+
self.score = score
299+
super().__init__()
300+
301+
def as_mql(self, compiler, connection):
302+
params = {
303+
"path": self.path.as_mql(compiler, connection)[1:],
304+
"value": self.value.as_mql(compiler, connection),
305+
}
306+
if self.score is not None:
307+
params["score"] = self.score
308+
index = self._get_query_index([self.path], compiler)
309+
return {"$search": {"in": params, "index": index}}
310+
311+
312+
class SearchPhrase(SearchExpression):
313+
def __init__(self, path, value, slop=None, synonyms=None, score=None):
314+
self.path = F(path)
315+
self.value = Value(value)
316+
self.score = score
317+
self.slop = slop
318+
self.synonyms = synonyms
319+
super().__init__()
320+
321+
def as_mql(self, compiler, connection):
322+
params = {
323+
"path": self.path.as_mql(compiler, connection)[1:],
324+
"value": self.value.as_mql(compiler, connection),
325+
}
326+
if self.score is not None:
327+
params["score"] = self.score
328+
if self.slop is not None:
329+
params["slop"] = self.slop
330+
if self.synonyms is not None:
331+
params["synonyms"] = self.synonyms
332+
index = self._get_query_index([self.path], compiler)
333+
return {"$search": {"phrase": params, "index": index}}
334+
335+
336+
class SearchQueryString(SearchExpression):
337+
def __init__(self, path, query, score=None):
338+
self.path = F(path)
339+
self.query = Value(query)
340+
self.score = score
341+
super().__init__()
342+
343+
def as_mql(self, compiler, connection):
344+
params = {
345+
"defaultPath": self.path.as_mql(compiler, connection)[1:],
346+
"query": self.query.as_mql(compiler, connection),
347+
}
348+
if self.score is not None:
349+
params["score"] = self.score
350+
index = self._get_query_index([self.path], compiler)
351+
return {"$search": {"queryString": params, "index": index}}
352+
353+
354+
class SearchRange(SearchExpression):
355+
def __init__(self, path, lt=None, lte=None, gt=None, gte=None, score=None):
356+
self.path = F(path)
357+
self.lt = Value(lt)
358+
self.lte = Value(lte)
359+
self.gt = Value(gt)
360+
self.gte = Value(gte)
361+
self.score = score
362+
super().__init__()
363+
364+
def as_mql(self, compiler, connection):
365+
params = {
366+
"path": self.path.as_mql(compiler, connection)[1:],
367+
}
368+
if self.score is not None:
369+
params["score"] = self.score
370+
if self.lt is not None:
371+
params["lt"] = self.lt.as_mql(compiler, connection)
372+
if self.lte is not None:
373+
params["lte"] = self.lte.as_mql(compiler, connection)
374+
if self.gt is not None:
375+
params["gt"] = self.gt.as_mql(compiler, connection)
376+
if self.gte is not None:
377+
params["gte"] = self.gte.as_mql(compiler, connection)
378+
index = self._get_query_index([self.path], compiler)
379+
return {"$search": {"range": params, "index": index}}
380+
381+
382+
class SearchRegex(SearchExpression):
383+
def __init__(self, path, query, allow_analyzed_field=None, score=None):
384+
self.path = F(path)
385+
self.allow_analyzed_field = Value(allow_analyzed_field)
386+
self.score = score
387+
super().__init__()
388+
389+
def as_mql(self, compiler, connection):
390+
params = {
391+
"path": self.path.as_mql(compiler, connection)[1:],
392+
}
393+
if self.score:
394+
params["score"] = self.score
395+
if self.allow_analyzed_field is not None:
396+
params["allowAnalyzedField"] = self.allow_analyzed_field.as_mql(compiler, connection)
397+
index = self._get_query_index([self.path], compiler)
398+
return {"$search": {"regex": params, "index": index}}
399+
400+
401+
class SearchText(SearchExpression):
402+
def __init__(self, path, query, fuzzy=None, match_criteria=None, synonyms=None, score=None):
403+
self.path = F(path)
404+
self.fuzzy = Value(fuzzy)
405+
self.match_criteria = Value(match_criteria)
406+
self.synonyms = Value(synonyms)
407+
self.score = score
408+
super().__init__()
409+
410+
def as_mql(self, compiler, connection):
411+
params = {
412+
"path": self.path.as_mql(compiler, connection)[1:],
413+
}
414+
if self.score:
415+
params["score"] = self.score
416+
if self.fuzzy is not None:
417+
params["fuzzy"] = self.fuzzy.as_mql(compiler, connection)
418+
if self.match_criteria is not None:
419+
params["matchCriteria"] = self.match_criteria.as_mql(compiler, connection)
420+
if self.synonyms is not None:
421+
params["synonyms"] = self.synonyms.as_mql(compiler, connection)
422+
index = self._get_query_index([self.path], compiler)
423+
return {"$search": {"text": params, "index": index}}
424+
425+
426+
class SearchWildcard(SearchExpression):
427+
def __init__(self, path, query, allow_analyzed_field=None, score=None):
428+
self.path = F(path)
429+
self.allow_analyzed_field = Value(allow_analyzed_field)
430+
self.score = score
431+
super().__init__()
432+
433+
def as_mql(self, compiler, connection):
434+
params = {
435+
"path": self.path.as_mql(compiler, connection)[1:],
436+
}
437+
if self.score:
438+
params["score"] = self.score
439+
if self.allow_analyzed_field is not None:
440+
params["allowAnalyzedField"] = self.allow_analyzed_field.as_mql(compiler, connection)
441+
index = self._get_query_index([self.path], compiler)
442+
return {"$search": {"wildcard": params, "index": index}}
443+
444+
445+
class SearchGeoShape(SearchExpression):
446+
def __init__(self, path, relation, geometry, score=None):
447+
self.path = F(path)
448+
self.relation = relation
449+
self.geometry = geometry
450+
self.score = score
451+
super().__init__()
452+
453+
def as_mql(self, compiler, connection):
454+
params = {
455+
"path": self.path.as_mql(compiler, connection)[1:],
456+
"relation": self.relation,
457+
"geometry": self.geometry,
458+
}
459+
if self.score:
460+
params["score"] = self.score
461+
index = self._get_query_index([self.path], compiler)
462+
return {"$search": {"wildcard": params, "index": index}}
463+
464+
465+
class SearchGeoWithin(SearchExpression):
466+
def __init__(self, path, kind, geo_object, geometry, score=None):
467+
self.path = F(path)
468+
self.kind = kind
469+
self.geo_object = geo_object
470+
self.score = score
471+
super().__init__()
472+
473+
def as_mql(self, compiler, connection):
474+
params = {
475+
"path": self.path.as_mql(compiler, connection)[1:],
476+
self.kind: self.geo_object,
477+
}
478+
if self.score:
479+
params["score"] = self.score
480+
index = self._get_query_index([self.path], compiler)
481+
return {"$search": {"wildcard": params, "index": index}}
482+
483+
484+
class SearchMoreLikeThis(SearchExpression):
485+
def __init__(self, documents, score=None):
486+
self.documents = documents
487+
self.score = score
488+
super().__init__()
489+
490+
def as_mql(self, compiler, connection):
491+
params = {
492+
"like": self.documents,
493+
}
494+
if self.score:
495+
params["score"] = self.score
496+
needed_fields = []
497+
for doc in self.documents:
498+
needed_fields += list(doc.keys())
499+
index = self._get_query_index(needed_fields, compiler)
500+
return {"$search": {"wildcard": params, "index": index}}
501+
502+
215503
def register_expressions():
216504
Case.as_mql = case
217505
Col.as_mql = col

0 commit comments

Comments
 (0)