-
Notifications
You must be signed in to change notification settings - Fork 28
INTPYTHON-749: Fix bug in null matching on queryoptimizer #401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
6f2052a
139d0b4
fafe479
50177bc
e8011b9
0ca2a5e
4af823a
0161dfb
0b29263
7802896
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
from bson import ObjectId | ||
from django.db import connection | ||
from django.test import TestCase | ||
|
||
from django_mongodb_backend.test import MongoTestCaseMixin | ||
|
||
from .models import Book, Number | ||
from .models import Book, NullableJSONModel, Number | ||
|
||
|
||
class NumericLookupTests(TestCase): | ||
|
@@ -66,3 +68,72 @@ def test_eq_and_in(self): | |
"lookup__book", | ||
[{"$match": {"$and": [{"isbn": {"$in": ("12345", "56789")}}, {"title": "Moby Dick"}]}}], | ||
) | ||
|
||
|
||
class NullValueLookupTests(MongoTestCaseMixin, TestCase): | ||
_OPERATOR_PREDICATE_MAP = { | ||
"exact": lambda field: {field: None}, | ||
"in": lambda field: {field: {"$in": [None]}}, | ||
} | ||
|
||
@classmethod | ||
def setUpTestData(cls): | ||
cls.book_objs = Book.objects.bulk_create( | ||
Book(title=f"Book {i}", isbn=str(i)) for i in range(5) | ||
) | ||
|
||
cls.null_objs = NullableJSONModel.objects.bulk_create(NullableJSONModel() for _ in range(5)) | ||
cls.null_objs.append(NullableJSONModel.objects.create(value={"name": None})) | ||
cls.unique_id = ObjectId() | ||
|
||
def _test_none_filter_nullable_json(self, op, predicate, field): | ||
with self.assertNumQueries(1) as ctx: | ||
self.assertQuerySetEqual( | ||
NullableJSONModel.objects.filter( | ||
**{f"{field}__{op}": [None] if op == "in" else None} | ||
), | ||
[], | ||
) | ||
self.assertAggregateQuery( | ||
ctx.captured_queries[0]["sql"], | ||
"lookup__nullablejsonmodel", | ||
[{"$match": {"$and": [{"$exists": False}, predicate(field)]}}], | ||
) | ||
|
||
def _test_none_filter_binary_operator(self, op, predicate, field): | ||
with self.assertNumQueries(1) as ctx: | ||
self.assertQuerySetEqual( | ||
Book.objects.filter(**{f"{field}__{op}": [None] if op == "in" else None}), [] | ||
) | ||
self.assertAggregateQuery( | ||
ctx.captured_queries[0]["sql"], | ||
"lookup__book", | ||
[ | ||
{ | ||
"$match": { | ||
"$or": [ | ||
{"$and": [{field: {"$exists": True}}, predicate(field)]}, | ||
{"$expr": {"$eq": [{"$type": f"${field}"}, "missing"]}}, | ||
] | ||
} | ||
} | ||
], | ||
) | ||
|
||
def _test_with_raw_data(self, model, test_function, field): | ||
collection = connection.database.get_collection(model._meta.db_table) | ||
try: | ||
collection.insert_one({"_id": self.unique_id}) | ||
|
||
|
||
for op, predicate in self._OPERATOR_PREDICATE_MAP.items(): | ||
with self.subTest(op=op): | ||
test_function(op, predicate, field) | ||
|
||
finally: | ||
collection.delete_one({"_id": self.unique_id}) | ||
|
||
def test_none_filter_nullable_json(self): | ||
self._test_with_raw_data(NullableJSONModel, self._test_none_filter_nullable_json, "value") | ||
|
||
|
||
def test_none_filter_binary_operator(self): | ||
self._test_with_raw_data(Book, self._test_none_filter_binary_operator, "title") |
Uh oh!
There was an error while loading. Please reload this page.