Skip to content

Commit cf06a24

Browse files
committed
Handle empty set or full set in range queries.
1 parent 1693ea9 commit cf06a24

File tree

5 files changed

+46
-22
lines changed

5 files changed

+46
-22
lines changed

django_mongodb_backend/base.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import os
44

5-
from django.core.exceptions import ImproperlyConfigured
5+
from django.core.exceptions import EmptyResultSet, FullResultSet, ImproperlyConfigured
66
from django.db import DEFAULT_DB_ALIAS
77
from django.db.backends.base.base import BaseDatabaseWrapper
88
from django.db.backends.utils import debug_transaction
@@ -143,14 +143,16 @@ def _isnull_operator_match(a, b):
143143
}
144144

145145
def range_match(a, b):
146-
## TODO: MAKE A TEST TO TEST WHEN BOTH ENDS ARE NONE. WHAT SHALL I RETURN?
147146
conditions = []
148-
if b[0] is not None:
147+
start, end = b
148+
if start is not None:
149149
conditions.append({a: {"$gte": b[0]}})
150-
if b[1] is not None:
150+
if end is not None:
151151
conditions.append({a: {"$lte": b[1]}})
152+
if start is not None and end is not None and start > end:
153+
raise EmptyResultSet
152154
if not conditions:
153-
return {"$literal": True}
155+
raise FullResultSet
154156
return {"$and": conditions}
155157

156158
# match, path, find? don't know which name use.

django_mongodb_backend/fields/json.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,15 @@ def key_transform_in_path(self, compiler, connection):
183183

184184

185185
def key_transform_is_null_expr(self, compiler, connection):
186+
"""
187+
Return MQL to check the nullability of a key.
188+
189+
If `isnull=True`, the query matches objects where the key is missing or the
190+
root column is null. If `isnull=False`, the query negates the result to
191+
match objects where the key exists.
192+
193+
Reference: https://code.djangoproject.com/ticket/32252
194+
"""
186195
previous = self.lhs
187196
while isinstance(previous, KeyTransform):
188197
previous = previous.lhs
@@ -194,13 +203,7 @@ def key_transform_is_null_expr(self, compiler, connection):
194203

195204
def key_transform_is_null_path(self, compiler, connection):
196205
"""
197-
Return MQL to check the nullability of a key.
198-
199-
If `isnull=True`, the query matches objects where the key is missing or the
200-
root column is null. If `isnull=False`, the query negates the result to
201-
match objects where the key exists.
202-
203-
Reference: https://code.djangoproject.com/ticket/32252
206+
Return MQL to check the nullability of a key using the operator $exists.
204207
"""
205208
lhs_mql = process_lhs(self, compiler, connection, as_path=True)
206209
rhs_mql = process_rhs(self, compiler, connection, as_path=True)

tests/lookup_/tests.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
1+
from bson import SON
12
from django.test import TestCase
23

34
from django_mongodb_backend.test import MongoTestCaseMixin
45

56
from .models import Book, Number
67

78

8-
class NumericLookupTests(TestCase):
9+
class NumericLookupTests(MongoTestCaseMixin, TestCase):
910
@classmethod
1011
def setUpTestData(cls):
1112
cls.objs = Number.objects.bulk_create(Number(num=x) for x in range(5))
1213
# Null values should be excluded in less than queries.
13-
Number.objects.create()
14+
cls.null_number = Number.objects.create()
1415

1516
def test_lt(self):
1617
self.assertQuerySetEqual(Number.objects.filter(num__lt=3), self.objs[:3])
1718

1819
def test_lte(self):
1920
self.assertQuerySetEqual(Number.objects.filter(num__lte=3), self.objs[:4])
2021

22+
def test_empty_range(self):
23+
with self.assertNumQueries(0):
24+
self.assertQuerySetEqual(Number.objects.filter(num__range=[3, 1]), [])
25+
26+
def test_full_range(self):
27+
with self.assertNumQueries(1) as ctx:
28+
self.assertQuerySetEqual(
29+
Number.objects.filter(num__range=[None, None]), [self.null_number, *self.objs]
30+
)
31+
query = ctx.captured_queries[0]["sql"]
32+
self.assertAggregateQuery(
33+
query, "lookup__number", [{"$addFields": {"num": "$num"}}, {"$sort": SON([("num", 1)])}]
34+
)
35+
2136

2237
class RegexTests(MongoTestCaseMixin, TestCase):
2338
def test_mql(self):

tests/model_fields_/test_embedded_model.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,16 @@ def test_nested(self):
244244
)
245245
self.assertCountEqual(Book.objects.filter(author__address__city="NYC"), [obj])
246246

247-
def test_annotate(self):
247+
def test_filter_by_simple_annotate(self):
248248
obj = Book.objects.create(
249249
author=Author(name="Shakespeare", age=55, address=Address(city="NYC", state="NY"))
250250
)
251-
book_from_ny = (
252-
Book.objects.annotate(city=F("author__address__city")).filter(city="NYC").first()
253-
)
254-
self.assertCountEqual(book_from_ny.city, obj.author.address.city)
251+
with self.assertNumQueries(1) as ctx:
252+
book_from_ny = (
253+
Book.objects.annotate(city=F("author__address__city")).filter(city="NYC").first()
254+
)
255+
self.assertCountEqual(book_from_ny.city, obj.author.address.city)
256+
self.assertIn("{'$match': {'author.address.city': 'NYC'}}", ctx.captured_queries[0]["sql"])
255257

256258

257259
class ArrayFieldTests(TestCase):

tests/model_fields_/test_embedded_model_array.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,12 @@ def test_nested_lookup(self):
312312
with self.assertRaisesMessage(ValueError, msg):
313313
Exhibit.objects.filter(sections__artifacts__name="")
314314

315-
def test_foreign_field_exact(self):
315+
def test_foreign_field_exact_path(self):
316316
"""Querying from a foreign key to an EmbeddedModelArrayField."""
317-
qs = Tour.objects.filter(exhibit__sections__number=1)
318-
self.assertCountEqual(qs, [self.egypt_tour, self.wonders_tour])
317+
with self.assertNumQueries(1) as ctx:
318+
qs = Tour.objects.filter(exhibit__sections__number=1)
319+
self.assertCountEqual(qs, [self.egypt_tour, self.wonders_tour])
320+
self.assertNotIn("anyElementTrue", ctx.captured_queries[0]["sql"])
319321

320322
def test_foreign_field_exact_expr(self):
321323
"""Querying from a foreign key to an EmbeddedModelArrayField."""

0 commit comments

Comments
 (0)