@@ -26,40 +26,30 @@ def data_contains(self, compiler, connection): # noqa: ARG001
26
26
27
27
28
28
def _has_key_predicate (path , root_column , negated = False ):
29
- result = {"$and" : [{"$ne" : [{"$type" : path }, "missing" ]}, {"$ne" : [root_column , None ]}]}
29
+ """Return MQL to check for the existence of `path`."""
30
+ result = {
31
+ "$and" : [
32
+ # The path must exist (i.e. not be "missing").
33
+ {"$ne" : [{"$type" : path }, "missing" ]},
34
+ # If the JSONField value is None, an additional check for not null
35
+ # is needed since $type returns null instead of "missing".
36
+ {"$ne" : [root_column , None ]},
37
+ ]
38
+ }
30
39
if negated :
31
40
result = {"$not" : result }
32
41
return result
33
42
34
43
35
44
def has_key_lookup (self , compiler , connection ):
36
- """
37
- Performs a key lookup in a JSONField to check for the existence of a key.
38
-
39
- The key lookup in a JSONField is defined by the following conditions:
40
- 1. The path type must be different from "missing".
41
- 2. In cases where the JSONField is None (as the $type returns Null instead of "missing"),
42
- an additional check for not null is added.
43
-
44
- Returns:
45
- A dictionary representing the MongoDB query for key lookup.
46
-
47
- Raises:
48
- AssertionError: If `self.mongo_operator` is None and there are multiple keys.
49
-
50
- Note:
51
- The function handles key lookup in two ways:
52
- - Via `KeyTransform`
53
- - Directly via key
54
-
55
- Both are transformed into a `KeyTransform` for consistent handling.
56
- """
45
+ """Return MQL to check for the existence of a key."""
57
46
rhs = self .rhs
58
47
lhs = process_lhs (self , compiler , connection )
59
48
if not isinstance (rhs , list | tuple ):
60
49
rhs = [rhs ]
61
50
paths = []
62
- # Transform all keys into KeyTransform instances for consistent handling
51
+ # Transform any "raw" keys into KeyTransforms to allow consistent handling
52
+ # in the code that follows.
63
53
for key in rhs :
64
54
rhs_json_path = key if isinstance (key , KeyTransform ) else KeyTransform (key , self .lhs )
65
55
paths .append (rhs_json_path .as_mql (compiler , connection ))
@@ -85,19 +75,12 @@ def json_exact_process_rhs(self, compiler, connection):
85
75
86
76
def key_transform (self , compiler , connection ):
87
77
"""
88
- Transforms a Django KeyTransform (JSON path) into a MongoDB query path.
89
-
90
- In MongoDB, JSON paths cannot always be represented simply as $var.key1.key2.key3
91
- due to potential array types. Therefore, indexing arrays requires the use of
92
- `arrayElemAt`. Additionally, a conditional check (if statement) is necessary
93
- to verify the type before performing the operation.
94
-
95
- Returns:
96
- A dictionary representing the MongoDB query for the JSON path.
78
+ Return MQL for this KeyTransform (JSON path).
97
79
98
- Note:
99
- The function constructs the MongoDB path by iterating through the key transforms
100
- and handling both field access and array indexing appropriately.
80
+ JSON paths cannot always be represented simply as $var.key1.key2.key3 due
81
+ to possible array types. Therefore, indexing arrays requires the use of
82
+ `arrayElemAt`. Additionally, $cond is necessary to verify the type before
83
+ performing the operation.
101
84
"""
102
85
key_transforms = [self .key_name ]
103
86
previous = self .lhs
@@ -107,11 +90,11 @@ def key_transform(self, compiler, connection):
107
90
previous = previous .lhs
108
91
lhs_mql = previous .as_mql (compiler , connection )
109
92
result = lhs_mql
110
- # Build the MongoDB path using the collected key transforms
93
+ # Build the MQL path using the collected key transforms.
111
94
for key in key_transforms :
112
95
get_field = {"$getField" : {"input" : result , "field" : key }}
113
- # Handle array indexing if the key is a digit
114
- # if we have an index like '001' is not an array index.
96
+ # Handle array indexing if the key is a digit. If key is something
97
+ # like '001', it's not an array index despite isdigit() returning True .
115
98
if key .isdigit () and str (int (key )) == key :
116
99
result = {
117
100
"$cond" : {
@@ -127,54 +110,33 @@ def key_transform(self, compiler, connection):
127
110
128
111
def key_transform_in (self , compiler , connection ):
129
112
"""
130
- Checks if a JSON path's values are within a set of specified values and ensures the key exists.
131
-
132
- This function performs two main checks:
133
- 1. Verifies that the resulting path values are within the right-hand side (rhs) values.
134
- 2. Ensures that the key exists.
135
-
136
- Returns:
137
- A dictionary representing the MongoDB query that checks both conditions.
138
-
139
- Note:
140
- The function processes the left-hand side (lhs) and right-hand side (rhs)
141
- of the query, constructs the MongoDB expression, and checks both the value
142
- existence in rhs and the key existence in the JSON document.
113
+ Return MQL to check if a JSON path exists and that its values are in the
114
+ set of specified values (rhs).
143
115
"""
144
116
lhs_mql = process_lhs (self , compiler , connection )
145
- # Traverse to the root column
117
+ # Traverse to the root column.
146
118
previous = self .lhs
147
119
while isinstance (previous , KeyTransform ):
148
120
previous = previous .lhs
149
121
root_column = previous .as_mql (compiler , connection )
150
122
value = process_rhs (self , compiler , connection )
151
- # Construct the expression to check if lhs_mql values are in rhs values
123
+ # Construct the expression to check if lhs_mql values are in rhs values.
152
124
expr = connection .mongo_operators [self .lookup_name ](lhs_mql , value )
153
125
return {"$and" : [_has_key_predicate (lhs_mql , root_column ), expr ]}
154
126
155
127
156
128
def key_transform_is_null (self , compiler , connection ):
157
129
"""
158
- Handles the KeyTransformIsNull lookup.
159
-
160
- This function borrows logic from HasKey for `isnull=False`. If `isnull=True`,
161
- the query should match only objects that do not have the key.
162
-
163
- Returns:
164
- A dictionary representing the MongoDB query for checking the nullability of the key.
130
+ Return MQL to check the nullability of a key.
165
131
166
- Note:
167
- If `isnull=True`, the query matches objects where the key is missing
168
- or the root column is null.
169
- If `isnull=False`, the query negates the result to match objects where the key exists
170
- and the root column isn't null.
132
+ If `isnull=True`, the query matches objects where the key is missing or the
133
+ root column is null. If `isnull=False`, the query negates the result to
134
+ match objects where the key exists.
171
135
172
- Reference:
173
- https://code.djangoproject.com/ticket/32252
136
+ Reference: https://code.djangoproject.com/ticket/32252
174
137
"""
175
138
lhs_mql = process_lhs (self , compiler , connection )
176
139
rhs_mql = process_rhs (self , compiler , connection )
177
-
178
140
# Get the root column.
179
141
previous = self .lhs
180
142
while isinstance (previous , KeyTransform ):
@@ -185,23 +147,12 @@ def key_transform_is_null(self, compiler, connection):
185
147
186
148
def key_transform_numeric_lookup_mixin (self , compiler , connection ):
187
149
"""
188
- Checks if the field exists and fulfills the given numeric lookup expression.
189
-
190
- This function ensures that the field in the JSON document:
191
- 1. Exists (i.e., is not "missing" or "null").
192
- 2. Satisfies the specified numeric lookup expression.
193
-
194
- Returns:
195
- A dictionary representing the MongoDB query that checks both conditions.
196
-
197
- Note:
198
- The function constructs the MongoDB expression to check if the field exists
199
- and fulfills the numeric lookup expression, and combines these conditions
200
- using $and.
150
+ Return MQL to check if the field exists (i.e., is not "missing" or "null")
151
+ and that the field matches the given numeric lookup expression.
201
152
"""
202
153
expr = builtin_lookup (self , compiler , connection )
203
154
lhs = process_lhs (self , compiler , connection )
204
- # Check if the type of lhs is not "missing" or "null"
155
+ # Check if the type of lhs is not "missing" or "null".
205
156
not_missing_or_null = {"$not" : {"$in" : [{"$type" : lhs }, ["missing" , "null" ]]}}
206
157
return {"$and" : [expr , not_missing_or_null ]}
207
158
0 commit comments