-
Couldn't load subscription status.
- Fork 29
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.