1
1
from django.db import NotSupportedError
2
- from django.db.models import Expression, FloatField
2
+ from django.db.models import Expression, FloatField, JSONField
3
+ from django.db.models.expressions import F, Value
3
4
4
5
5
6
class Operator:
@@ -75,42 +76,57 @@ def __repr__(self):
75
76
def as_sql(self, compiler, connection):
76
77
return "", []
77
78
79
+ def _get_indexed_fields(self, mappings):
80
+ for field, definition in mappings.get("fields", {}).items():
81
+ yield field
82
+ for path in self._get_indexed_fields(definition):
83
+ yield f"{field}.{path}"
84
+
78
85
def _get_query_index(self, fields, compiler):
79
86
fields = set(fields)
80
87
for search_indexes in compiler.collection.list_search_indexes():
81
88
mappings = search_indexes["latestDefinition"]["mappings"]
82
- if mappings["dynamic"] or fields.issubset(set(mappings["fields"])):
89
+ indexed_fields = set(self._get_indexed_fields(mappings))
90
+ if mappings["dynamic"] or fields.issubset(indexed_fields):
83
91
return search_indexes["name"]
84
92
return "default"
85
93
86
- def search_operator(self):
94
+ def search_operator(self, compiler, connection ):
87
95
raise NotImplementedError
88
96
89
97
def as_mql(self, compiler, connection):
90
- index = self._get_query_index(self.get_search_fields(), compiler)
91
- return {"$search": {**self.search_operator(), "index": index}}
98
+ index = self._get_query_index(self.get_search_fields(compiler, connection ), compiler)
99
+ return {"$search": {**self.search_operator(compiler, connection ), "index": index}}
92
100
93
101
94
102
class SearchAutocomplete(SearchExpression):
95
- def __init__(self, path, query, fuzzy=None, score=None):
96
- self.path = path
97
- self.query = query
103
+ def __init__(self, path, query, fuzzy=None, token_order=None, score=None):
104
+ self.path = F(path) if isinstance(path, str) else path
105
+ self.query = Value(query) if not hasattr(query, "resolve_expression") else query
106
+ if fuzzy is not None and not hasattr(fuzzy, "resolve_expression"):
107
+ fuzzy = Value(fuzzy, output_field=JSONField())
98
108
self.fuzzy = fuzzy
109
+ if token_order is not None and not hasattr(token_order, "resolve_expression"):
110
+ token_order = Value(token_order)
111
+ self.token_order = token_order
99
112
self.score = score
100
113
super().__init__()
101
114
102
- def get_search_fields(self):
103
- return {self.path}
115
+ def get_search_fields(self, compiler, connection):
116
+ # Shall i implement resolve_something? I think I have to do
117
+ return {self.path.as_mql(compiler, connection, as_path=True)}
104
118
105
- def search_operator(self):
119
+ def search_operator(self, compiler, connection ):
106
120
params = {
107
- "path": self.path,
108
- "query": self.query,
121
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
122
+ "query": self.query.as_mql(compiler, connection) ,
109
123
}
110
124
if self.score is not None:
111
- params["score"] = self.score
125
+ params["score"] = self.score.as_mql(compiler, connection)
112
126
if self.fuzzy is not None:
113
- params["fuzzy"] = self.fuzzy
127
+ params["fuzzy"] = self.fuzzy.as_mql(compiler, connection)
128
+ if self.token_order is not None:
129
+ params["tokenOrder"] = self.token_order.as_mql(compiler, connection)
114
130
return {"autocomplete": params}
115
131
116
132
@@ -121,16 +137,16 @@ def __init__(self, path, value, score=None):
121
137
self.score = score
122
138
super().__init__()
123
139
124
- def get_search_fields(self):
125
- return {self.path}
140
+ def get_search_fields(self, compiler, connection ):
141
+ return {self.path.as_mql(compiler, connection, as_path=True) }
126
142
127
- def search_operator(self):
143
+ def search_operator(self, compiler, connection ):
128
144
params = {
129
- "path": self.path,
130
- "value": self.value,
145
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
146
+ "value": self.value.as_mql(compiler, connection, as_path=True) ,
131
147
}
132
148
if self.score is not None:
133
- params["score"] = self.score
149
+ params["score"] = self.score.as_mql(compiler, connection, as_path=True)
134
150
return {"equals": params}
135
151
136
152
@@ -140,15 +156,15 @@ def __init__(self, path, score=None):
140
156
self.score = score
141
157
super().__init__()
142
158
143
- def get_search_fields(self):
144
- return {self.path}
159
+ def get_search_fields(self, compiler, connection ):
160
+ return {self.path.as_mql(compiler, connection, as_path=True) }
145
161
146
- def search_operator(self):
162
+ def search_operator(self, compiler, connection ):
147
163
params = {
148
- "path": self.path,
164
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
149
165
}
150
166
if self.score is not None:
151
- params["score"] = self.score
167
+ params["score"] = self.score.definitions
152
168
return {"exists": params}
153
169
154
170
@@ -159,16 +175,16 @@ def __init__(self, path, value, score=None):
159
175
self.score = score
160
176
super().__init__()
161
177
162
- def get_search_fields(self):
163
- return {self.path}
178
+ def get_search_fields(self, compiler, connection ):
179
+ return {self.path.as_mql(compiler, connection, as_path=True) }
164
180
165
- def search_operator(self):
181
+ def search_operator(self, compiler, connection ):
166
182
params = {
167
- "path": self.path,
168
- "value": self.value,
183
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
184
+ "value": self.value.as_mql(compiler, connection, as_path=True) ,
169
185
}
170
186
if self.score is not None:
171
- params["score"] = self.score
187
+ params["score"] = self.score.definitions
172
188
return {"in": params}
173
189
174
190
@@ -181,20 +197,20 @@ def __init__(self, path, query, slop=None, synonyms=None, score=None):
181
197
self.synonyms = synonyms
182
198
super().__init__()
183
199
184
- def get_search_fields(self):
185
- return {self.path}
200
+ def get_search_fields(self, compiler, connection ):
201
+ return {self.path.as_mql(compiler, connection, as_path=True) }
186
202
187
- def search_operator(self):
203
+ def search_operator(self, compiler, connection ):
188
204
params = {
189
- "path": self.path,
190
- "query": self.query,
205
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
206
+ "query": self.query.as_mql(compiler, connection, as_path=True) ,
191
207
}
192
208
if self.score is not None:
193
- params["score"] = self.score
209
+ params["score"] = self.score.as_mql(compiler, connection, as_path=True)
194
210
if self.slop is not None:
195
- params["slop"] = self.slop
211
+ params["slop"] = self.slop.as_mql(compiler, connection, as_path=True)
196
212
if self.synonyms is not None:
197
- params["synonyms"] = self.synonyms
213
+ params["synonyms"] = self.synonyms.as_mql(compiler, connection, as_path=True)
198
214
return {"phrase": params}
199
215
200
216
@@ -205,16 +221,16 @@ def __init__(self, path, query, score=None):
205
221
self.score = score
206
222
super().__init__()
207
223
208
- def get_search_fields(self):
209
- return {self.path}
224
+ def get_search_fields(self, compiler, connection ):
225
+ return {self.path.as_mql(compiler, connection, as_path=True) }
210
226
211
- def search_operator(self):
227
+ def search_operator(self, compiler, connection ):
212
228
params = {
213
229
"defaultPath": self.path,
214
- "query": self.query,
230
+ "query": self.query.as_mql(compiler, connection, as_path=True) ,
215
231
}
216
232
if self.score is not None:
217
- params["score"] = self.score
233
+ params["score"] = self.score.definitions
218
234
return {"queryString": params}
219
235
220
236
@@ -228,15 +244,15 @@ def __init__(self, path, lt=None, lte=None, gt=None, gte=None, score=None):
228
244
self.score = score
229
245
super().__init__()
230
246
231
- def get_search_fields(self):
232
- return {self.path}
247
+ def get_search_fields(self, compiler, connection ):
248
+ return {self.path.as_mql(compiler, connection, as_path=True) }
233
249
234
- def search_operator(self):
250
+ def search_operator(self, compiler, connection ):
235
251
params = {
236
- "path": self.path,
252
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
237
253
}
238
254
if self.score is not None:
239
- params["score"] = self.score
255
+ params["score"] = self.score.definitions
240
256
if self.lt is not None:
241
257
params["lt"] = self.lt
242
258
if self.lte is not None:
@@ -256,16 +272,16 @@ def __init__(self, path, query, allow_analyzed_field=None, score=None):
256
272
self.score = score
257
273
super().__init__()
258
274
259
- def get_search_fields(self):
260
- return {self.path}
275
+ def get_search_fields(self, compiler, connection ):
276
+ return {self.path.as_mql(compiler, connection, as_path=True) }
261
277
262
- def search_operator(self):
278
+ def search_operator(self, compiler, connection ):
263
279
params = {
264
- "path": self.path,
265
- "query": self.query,
280
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
281
+ "query": self.query.as_mql(compiler, connection, as_path=True) ,
266
282
}
267
283
if self.score:
268
- params["score"] = self.score
284
+ params["score"] = self.score.definitions
269
285
if self.allow_analyzed_field is not None:
270
286
params["allowAnalyzedField"] = self.allow_analyzed_field
271
287
return {"regex": params}
@@ -281,16 +297,16 @@ def __init__(self, path, query, fuzzy=None, match_criteria=None, synonyms=None,
281
297
self.score = score
282
298
super().__init__()
283
299
284
- def get_search_fields(self):
285
- return {self.path}
300
+ def get_search_fields(self, compiler, connection ):
301
+ return {self.path.as_mql(compiler, connection, as_path=True) }
286
302
287
- def search_operator(self):
303
+ def search_operator(self, compiler, connection ):
288
304
params = {
289
- "path": self.path,
290
- "query": self.query,
305
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
306
+ "query": self.query.as_mql(compiler, connection, as_path=True) ,
291
307
}
292
308
if self.score:
293
- params["score"] = self.score
309
+ params["score"] = self.score.definitions
294
310
if self.fuzzy is not None:
295
311
params["fuzzy"] = self.fuzzy
296
312
if self.match_criteria is not None:
@@ -308,16 +324,16 @@ def __init__(self, path, query, allow_analyzed_field=None, score=None):
308
324
self.score = score
309
325
super().__init__()
310
326
311
- def get_search_fields(self):
312
- return {self.path}
327
+ def get_search_fields(self, compiler, connection ):
328
+ return {self.path.as_mql(compiler, connection, as_path=True) }
313
329
314
- def search_operator(self):
330
+ def search_operator(self, compiler, connection ):
315
331
params = {
316
- "path": self.path,
317
- "query": self.query,
332
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
333
+ "query": self.query.as_mql(compiler, connection, as_path=True) ,
318
334
}
319
335
if self.score:
320
- params["score"] = self.score
336
+ params["score"] = self.score.definitions
321
337
if self.allow_analyzed_field is not None:
322
338
params["allowAnalyzedField"] = self.allow_analyzed_field
323
339
return {"wildcard": params}
@@ -331,17 +347,17 @@ def __init__(self, path, relation, geometry, score=None):
331
347
self.score = score
332
348
super().__init__()
333
349
334
- def get_search_fields(self):
335
- return {self.path}
350
+ def get_search_fields(self, compiler, connection ):
351
+ return {self.path.as_mql(compiler, connection, as_path=True) }
336
352
337
- def search_operator(self):
353
+ def search_operator(self, compiler, connection ):
338
354
params = {
339
- "path": self.path,
355
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
340
356
"relation": self.relation,
341
357
"geometry": self.geometry,
342
358
}
343
359
if self.score:
344
- params["score"] = self.score
360
+ params["score"] = self.score.definitions
345
361
return {"geoShape": params}
346
362
347
363
@@ -353,17 +369,17 @@ def __init__(self, path, kind, geo_object, score=None):
353
369
self.score = score
354
370
super().__init__()
355
371
356
- def search_operator(self):
372
+ def search_operator(self, compiler, connection ):
357
373
params = {
358
- "path": self.path,
374
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
359
375
self.kind: self.geo_object,
360
376
}
361
377
if self.score:
362
- params["score"] = self.score
378
+ params["score"] = self.score.definitions
363
379
return {"geoWithin": params}
364
380
365
- def get_search_fields(self):
366
- return {self.path}
381
+ def get_search_fields(self, compiler, connection ):
382
+ return {self.path.as_mql(compiler, connection, as_path=True) }
367
383
368
384
369
385
class SearchMoreLikeThis(SearchExpression):
@@ -372,15 +388,15 @@ def __init__(self, documents, score=None):
372
388
self.score = score
373
389
super().__init__()
374
390
375
- def search_operator(self):
391
+ def search_operator(self, compiler, connection ):
376
392
params = {
377
393
"like": self.documents,
378
394
}
379
395
if self.score:
380
- params["score"] = self.score
396
+ params["score"] = self.score.definitions
381
397
return {"moreLikeThis": params}
382
398
383
- def get_search_fields(self):
399
+ def get_search_fields(self, compiler, connection ):
384
400
needed_fields = set()
385
401
for doc in self.documents:
386
402
needed_fields.update(set(doc.keys()))
@@ -404,13 +420,13 @@ def __init__(
404
420
self.score = score
405
421
self.minimum_should_match = minimum_should_match
406
422
407
- def get_search_fields(self):
423
+ def get_search_fields(self, compiler, connection ):
408
424
fields = set()
409
425
for clause in self.must + self.should + self.filter + self.must_not:
410
426
fields.update(clause.get_search_fields())
411
427
return fields
412
428
413
- def search_operator(self):
429
+ def search_operator(self, compiler, connection ):
414
430
params = {}
415
431
if self.must:
416
432
params["must"] = [clause.search_operator() for clause in self.must]
@@ -491,8 +507,8 @@ def __or__(self, other):
491
507
def __ror__(self, other):
492
508
raise NotSupportedError("SearchVector cannot be combined")
493
509
494
- def get_search_fields(self):
495
- return {self.path}
510
+ def get_search_fields(self, compiler, connection ):
511
+ return {self.path.as_mql(compiler, connection, as_path=True) }
496
512
497
513
def _get_query_index(self, fields, compiler):
498
514
for search_indexes in compiler.collection.list_search_indexes():
@@ -507,7 +523,7 @@ def _get_query_index(self, fields, compiler):
507
523
def as_mql(self, compiler, connection):
508
524
params = {
509
525
"index": self._get_query_index(self.get_search_fields(), compiler),
510
- "path": self.path,
526
+ "path": self.path.as_mql(compiler, connection, as_path=True) ,
511
527
"queryVector": self.query_vector,
512
528
"limit": self.limit,
513
529
}
0 commit comments