Skip to content

Commit 6f2052a

Browse files
committed
Fix bug in null matching on queryoptimizer
1 parent 43ca475 commit 6f2052a

File tree

2 files changed

+41
-10
lines changed

2 files changed

+41
-10
lines changed

django_mongodb_backend/query_conversion/expression_converters.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ def convert(cls, args):
4444
):
4545
field_name = field_expr[1:] # Remove the $ prefix.
4646
if cls.operator == "$eq":
47-
return {field_name: value}
48-
return {field_name: {cls.operator: value}}
47+
query = {field_name: value}
48+
else:
49+
query = {field_name: {cls.operator: value}}
50+
if value is None:
51+
query = {"$and": [{field_name: {"$exists": True}}, query]}
52+
return query
4953
return None
5054

5155

@@ -102,7 +106,12 @@ def convert(cls, in_args):
102106
if isinstance(values, (list, tuple, set)) and all(
103107
cls.is_simple_value(v) for v in values
104108
):
105-
return {field_name: {"$in": values}}
109+
core_check = {field_name: {"$in": values}}
110+
return (
111+
{"$and": [{field_name: {"$exists": True}}, core_check]}
112+
if None in values
113+
else core_check
114+
)
106115
return None
107116

108117

tests/expression_converter_/test_op_expressions.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
from django_mongodb_backend.query_conversion.expression_converters import convert_expression
88

99

10+
def _wrap_condition_if_null(_type, condition, path):
11+
if _type is None:
12+
return {"$and": [{path: {"$exists": True}}, condition]}
13+
return condition
14+
15+
1016
class ConversionTestCase(SimpleTestCase):
1117
CONVERTIBLE_TYPES = {
1218
"int": 42,
@@ -53,10 +59,14 @@ def test_no_conversion_dict_value(self):
5359
self.assertNotOptimizable({"$eq": ["$status", {"$gt": 5}]})
5460

5561
def _test_conversion_valid_type(self, _type):
56-
self.assertConversionEqual({"$eq": ["$age", _type]}, {"age": _type})
62+
self.assertConversionEqual(
63+
{"$eq": ["$age", _type]}, _wrap_condition_if_null(_type, {"age": _type}, "age")
64+
)
5765

5866
def _test_conversion_valid_array_type(self, _type):
59-
self.assertConversionEqual({"$eq": ["$age", _type]}, {"age": _type})
67+
self.assertConversionEqual(
68+
{"$eq": ["$age", _type]}, _wrap_condition_if_null(_type, {"age": _type}, "age")
69+
)
6070

6171
def test_conversion_various_types(self):
6272
self._test_conversion_various_types(self._test_conversion_valid_type)
@@ -78,7 +88,10 @@ def test_no_conversion_dict_value(self):
7888
self.assertNotOptimizable({"$in": ["$status", [{"bad": "val"}]]})
7989

8090
def _test_conversion_valid_type(self, _type):
81-
self.assertConversionEqual({"$in": ["$age", [_type]]}, {"age": {"$in": [_type]}})
91+
self.assertConversionEqual(
92+
{"$in": ["$age", [_type]]},
93+
_wrap_condition_if_null(_type, {"age": {"$in": [_type]}}, "age"),
94+
)
8295

8396
def test_conversion_various_types(self):
8497
for _type, val in self.CONVERTIBLE_TYPES.items():
@@ -170,7 +183,10 @@ def test_no_conversion_dict_value(self):
170183
self.assertNotOptimizable({"$gt": ["$price", {}]})
171184

172185
def _test_conversion_valid_type(self, _type):
173-
self.assertConversionEqual({"$gt": ["$price", _type]}, {"price": {"$gt": _type}})
186+
self.assertConversionEqual(
187+
{"$gt": ["$price", _type]},
188+
_wrap_condition_if_null(_type, {"price": {"$gt": _type}}, "price"),
189+
)
174190

175191
def test_conversion_various_types(self):
176192
self._test_conversion_various_types(self._test_conversion_valid_type)
@@ -193,7 +209,7 @@ def test_no_conversion_dict_value(self):
193209
def _test_conversion_valid_type(self, _type):
194210
expr = {"$gte": ["$price", _type]}
195211
expected = {"price": {"$gte": _type}}
196-
self.assertConversionEqual(expr, expected)
212+
self.assertConversionEqual(expr, _wrap_condition_if_null(_type, expected, "price"))
197213

198214
def test_conversion_various_types(self):
199215
self._test_conversion_various_types(self._test_conversion_valid_type)
@@ -210,7 +226,10 @@ def test_no_conversion_dict_value(self):
210226
self.assertNotOptimizable({"$lt": ["$price", {}]})
211227

212228
def _test_conversion_valid_type(self, _type):
213-
self.assertConversionEqual({"$lt": ["$price", _type]}, {"price": {"$lt": _type}})
229+
self.assertConversionEqual(
230+
{"$lt": ["$price", _type]},
231+
_wrap_condition_if_null(_type, {"price": {"$lt": _type}}, "price"),
232+
)
214233

215234
def test_conversion_various_types(self):
216235
self._test_conversion_various_types(self._test_conversion_valid_type)
@@ -227,7 +246,10 @@ def test_no_conversion_dict_value(self):
227246
self.assertNotOptimizable({"$lte": ["$price", {}]})
228247

229248
def _test_conversion_valid_type(self, _type):
230-
self.assertConversionEqual({"$lte": ["$price", _type]}, {"price": {"$lte": _type}})
249+
self.assertConversionEqual(
250+
{"$lte": ["$price", _type]},
251+
_wrap_condition_if_null(_type, {"price": {"$lte": _type}}, "price"),
252+
)
231253

232254
def test_conversion_various_types(self):
233255
self._test_conversion_various_types(self._test_conversion_valid_type)

0 commit comments

Comments
 (0)