Skip to content

Commit fdfc411

Browse files
committed
Refactor.
1 parent 847a384 commit fdfc411

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,
@@ -207,6 +208,293 @@ def value(self, compiler, connection): # noqa: ARG001
207208
return value
208209

209210

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

0 commit comments

Comments
 (0)