8
8
from .. import forms
9
9
from ..query_utils import process_lhs , process_rhs
10
10
from . import EmbeddedModelField
11
- from .array import ArrayField
11
+ from .array import ArrayField , ArrayLenTransform
12
12
13
13
14
14
class EmbeddedModelArrayField (ArrayField ):
15
- ALLOWED_LOOKUPS = {
16
- "in" ,
17
- "exact" ,
18
- "iexact" ,
19
- "gt" ,
20
- "gte" ,
21
- "lt" ,
22
- "lte" ,
23
- "all" ,
24
- "contained_by" ,
25
- }
26
-
27
15
def __init__ (self , embedded_model , ** kwargs ):
28
16
if "size" in kwargs :
29
17
raise ValueError ("EmbeddedModelArrayField does not support size." )
@@ -69,18 +57,50 @@ def get_transform(self, name):
69
57
return transform
70
58
return KeyTransformFactory (name , self )
71
59
60
+ def _get_lookup (self , lookup_name ):
61
+ lookup = super ()._get_lookup (lookup_name )
62
+ if lookup is None or lookup is ArrayLenTransform :
63
+ return lookup
64
+
65
+ class EmbeddedModelArrayFieldLookups (Lookup ):
66
+ def as_mql (self , compiler , connection ):
67
+ raise ValueError (
68
+ "Cannot apply this lookup directly to EmbeddedModelArrayField. "
69
+ "Try querying one of its embedded fields instead."
70
+ )
71
+
72
+ return EmbeddedModelArrayFieldLookups
73
+
74
+
75
+ class _EmbeddedModelArrayOutputField (ArrayField ):
76
+ """
77
+ Represents the output of an EmbeddedModelArrayField when traversed in a query path.
78
+
79
+ This field is not meant to be used directly in model definitions. It exists solely to
80
+ support query output resolution; when an EmbeddedModelArrayField is accessed in a query,
81
+ the result should behave like an array of the embedded model's target type.
82
+
83
+ While it mimics ArrayField's lookups behavior, the way those lookups are resolved
84
+ follows the semantics of EmbeddedModelArrayField rather than native array behavior.
85
+ """
86
+
87
+ ALLOWED_LOOKUPS = {
88
+ "in" ,
89
+ "exact" ,
90
+ "iexact" ,
91
+ "gt" ,
92
+ "gte" ,
93
+ "lt" ,
94
+ "lte" ,
95
+ "all" ,
96
+ "contained_by" ,
97
+ }
98
+
72
99
def get_lookup (self , name ):
73
100
return super ().get_lookup (name ) if name in self .ALLOWED_LOOKUPS else None
74
101
75
102
76
103
class EmbeddedModelArrayFieldBuiltinLookup (Lookup ):
77
- def check_lhs (self ):
78
- if not isinstance (self .lhs , KeyTransform ):
79
- raise ValueError (
80
- "Cannot apply this lookup directly to EmbeddedModelArrayField. "
81
- "Try querying one of its embedded fields instead."
82
- )
83
-
84
104
def process_rhs (self , compiler , connection ):
85
105
value = self .rhs
86
106
if not self .get_db_prep_lookup_value_is_iterable :
@@ -95,111 +115,114 @@ def process_rhs(self, compiler, connection):
95
115
]
96
116
97
117
def as_mql (self , compiler , connection ):
98
- self .check_lhs ()
99
118
# Querying a subfield within the array elements (via nested KeyTransform).
100
119
# Replicates MongoDB's implicit ANY-match by mapping over the array and applying
101
120
# `$in` on the subfield.
102
- lhs_mql , inner_lhs_mql = process_lhs (self , compiler , connection )
121
+ lhs_mql = process_lhs (self , compiler , connection )
122
+ inner_lhs_mql = lhs_mql ["$ifNull" ][0 ]["$map" ]["in" ]
103
123
values = process_rhs (self , compiler , connection )
104
- return {
105
- "$anyElementTrue" : {
106
- "$ifNull" : [
107
- {
108
- "$map" : {
109
- "input" : lhs_mql ,
110
- "as" : "item" ,
111
- "in" : connection .mongo_operators [self .lookup_name ](
112
- inner_lhs_mql , values
113
- ),
114
- }
115
- },
116
- [],
117
- ]
118
- }
119
- }
124
+ lhs_mql ["$ifNull" ][0 ]["$map" ]["in" ] = connection .mongo_operators [self .lookup_name ](
125
+ inner_lhs_mql , values
126
+ )
127
+ return {"$anyElementTrue" : lhs_mql }
120
128
121
129
122
- @EmbeddedModelArrayField .register_lookup
130
+ @_EmbeddedModelArrayOutputField .register_lookup
123
131
class EmbeddedModelArrayFieldIn (EmbeddedModelArrayFieldBuiltinLookup , lookups .In ):
124
- pass
132
+ def get_subquery_wrapping_pipeline (self , compiler , connection , field_name , expr ):
133
+ return [
134
+ {
135
+ "$facet" : {
136
+ "group" : [
137
+ {"$project" : {"tmp_name" : expr .as_mql (compiler , connection )}},
138
+ {
139
+ "$unwind" : "$tmp_name" ,
140
+ },
141
+ {
142
+ "$group" : {
143
+ "_id" : None ,
144
+ "tmp_name" : {"$addToSet" : "$tmp_name" },
145
+ }
146
+ },
147
+ ]
148
+ }
149
+ },
150
+ {
151
+ "$project" : {
152
+ field_name : {
153
+ "$ifNull" : [
154
+ {
155
+ "$getField" : {
156
+ "input" : {"$arrayElemAt" : ["$group" , 0 ]},
157
+ "field" : "tmp_name" ,
158
+ }
159
+ },
160
+ [],
161
+ ]
162
+ }
163
+ }
164
+ },
165
+ ]
125
166
126
167
127
- @EmbeddedModelArrayField .register_lookup
168
+ @_EmbeddedModelArrayOutputField .register_lookup
128
169
class EmbeddedModelArrayFieldExact (EmbeddedModelArrayFieldBuiltinLookup , lookups .Exact ):
129
170
pass
130
171
131
172
132
- @EmbeddedModelArrayField .register_lookup
173
+ @_EmbeddedModelArrayOutputField .register_lookup
133
174
class EmbeddedModelArrayFieldIExact (EmbeddedModelArrayFieldBuiltinLookup , lookups .IExact ):
134
175
get_db_prep_lookup_value_is_iterable = False
135
176
136
177
137
- @EmbeddedModelArrayField .register_lookup
178
+ @_EmbeddedModelArrayOutputField .register_lookup
138
179
class EmbeddedModelArrayFieldGreaterThan (EmbeddedModelArrayFieldBuiltinLookup , lookups .GreaterThan ):
139
180
pass
140
181
141
182
142
- @EmbeddedModelArrayField .register_lookup
183
+ @_EmbeddedModelArrayOutputField .register_lookup
143
184
class EmbeddedModelArrayFieldGreaterThanOrEqual (
144
185
EmbeddedModelArrayFieldBuiltinLookup , lookups .GreaterThanOrEqual
145
186
):
146
187
pass
147
188
148
189
149
- @EmbeddedModelArrayField .register_lookup
190
+ @_EmbeddedModelArrayOutputField .register_lookup
150
191
class EmbeddedModelArrayFieldLessThan (EmbeddedModelArrayFieldBuiltinLookup , lookups .LessThan ):
151
192
pass
152
193
153
194
154
- @EmbeddedModelArrayField .register_lookup
195
+ @_EmbeddedModelArrayOutputField .register_lookup
155
196
class EmbeddedModelArrayFieldLessThanOrEqual (
156
197
EmbeddedModelArrayFieldBuiltinLookup , lookups .LessThanOrEqual
157
198
):
158
199
pass
159
200
160
201
161
- @EmbeddedModelArrayField .register_lookup
202
+ @_EmbeddedModelArrayOutputField .register_lookup
162
203
class EmbeddedModelArrayFieldAll (EmbeddedModelArrayFieldBuiltinLookup , Lookup ):
163
204
lookup_name = "all"
164
205
get_db_prep_lookup_value_is_iterable = False
165
206
166
207
def as_mql (self , compiler , connection ):
167
- self .check_lhs ()
168
- lhs_mql , inner_lhs_mql = process_lhs (self , compiler , connection )
208
+ lhs_mql = process_lhs (self , compiler , connection )
169
209
values = process_rhs (self , compiler , connection )
170
210
return {
171
- "$setIsSubset" : [
172
- values ,
173
- {
174
- "$ifNull" : [
175
- {
176
- "$map" : {
177
- "input" : lhs_mql ,
178
- "as" : "item" ,
179
- "in" : inner_lhs_mql ,
180
- }
181
- },
182
- [],
183
- ]
184
- },
211
+ "$and" : [
212
+ {"$ne" : [lhs_mql , None ]},
213
+ {"$ne" : [values , None ]},
214
+ {"$setIsSubset" : [values , lhs_mql ]},
185
215
]
186
216
}
187
217
188
218
189
- @EmbeddedModelArrayField .register_lookup
219
+ @_EmbeddedModelArrayOutputField .register_lookup
190
220
class ArrayContainedBy (EmbeddedModelArrayFieldBuiltinLookup , Lookup ):
191
221
lookup_name = "contained_by"
192
222
get_db_prep_lookup_value_is_iterable = False
193
223
194
224
def as_mql (self , compiler , connection ):
195
- lhs_mql , inner_lhs_mql = process_lhs (self , compiler , connection )
196
- lhs_mql = {
197
- "$map" : {
198
- "input" : lhs_mql ,
199
- "as" : "item" ,
200
- "in" : inner_lhs_mql ,
201
- }
202
- }
225
+ lhs_mql = process_lhs (self , compiler , connection )
203
226
value = process_rhs (self , compiler , connection )
204
227
return {
205
228
"$and" : [
@@ -244,7 +267,7 @@ def get_transform(self, name):
244
267
self ._sub_transform = transform
245
268
return self
246
269
output_field = self ._lhs .output_field
247
- allowed_lookups = self .array_field .ALLOWED_LOOKUPS .intersection (
270
+ allowed_lookups = self .output_field .ALLOWED_LOOKUPS .intersection (
248
271
set (output_field .get_lookups ())
249
272
)
250
273
suggested_lookups = difflib .get_close_matches (name , allowed_lookups )
@@ -262,11 +285,22 @@ def get_transform(self, name):
262
285
def as_mql (self , compiler , connection ):
263
286
inner_lhs_mql = self ._lhs .as_mql (compiler , connection )
264
287
lhs_mql = process_lhs (self , compiler , connection )
265
- return lhs_mql , inner_lhs_mql
288
+ return {
289
+ "$ifNull" : [
290
+ {
291
+ "$map" : {
292
+ "input" : lhs_mql ,
293
+ "as" : "item" ,
294
+ "in" : inner_lhs_mql ,
295
+ }
296
+ },
297
+ [],
298
+ ]
299
+ }
266
300
267
301
@property
268
302
def output_field (self ):
269
- return self .array_field
303
+ return _EmbeddedModelArrayOutputField ( self ._lhs . output_field )
270
304
271
305
272
306
class KeyTransformFactory :
0 commit comments