From 756e28f04f1a36d27bf5c671a6ebfa0960141721 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 10 May 2024 17:55:58 -0400 Subject: [PATCH] fix QuerySet results when Q() conditions are empty or full --- .github/workflows/test-python.yml | 1 + django_mongodb/compiler.py | 7 ++++-- django_mongodb/features.py | 2 -- django_mongodb/query.py | 38 ++++++++++++++++++++++++++++--- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 831ef9d0..600f7fb9 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -77,6 +77,7 @@ jobs: lookup.tests.LookupQueryingTests.test_isnull_lookup_in_filter model_fields or_lookups + queries.tests.Ticket12807Tests.test_ticket_12807 sessions_tests update diff --git a/django_mongodb/compiler.py b/django_mongodb/compiler.py index 457ecad4..afeefd38 100644 --- a/django_mongodb/compiler.py +++ b/django_mongodb/compiler.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.core.exceptions import EmptyResultSet +from django.core.exceptions import EmptyResultSet, FullResultSet from django.db import ( DatabaseError, IntegrityError, @@ -128,7 +128,10 @@ def build_query(self, columns=None): self.check_query() self.setup_query() query = self.query_class(self, columns) - query.add_filters(self.query.where) + try: + query.add_filters(self.query.where) + except FullResultSet: + query.mongo_query = [] query.order_by(self._get_ordering()) # This at least satisfies the most basic unit tests. diff --git a/django_mongodb/features.py b/django_mongodb/features.py index 2e70ee35..20093e33 100644 --- a/django_mongodb/features.py +++ b/django_mongodb/features.py @@ -34,8 +34,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): # 'TruncDate' object has no attribute 'alias' "model_fields.test_datetimefield.DateTimeFieldTests.test_lookup_date_with_use_tz", "model_fields.test_datetimefield.DateTimeFieldTests.test_lookup_date_without_use_tz", - # Empty queryset ORed (|) with another gives empty results. - "or_lookups.tests.OrLookupsTests.test_empty_in", } django_test_skips = { diff --git a/django_mongodb/query.py b/django_mongodb/query.py index b5d51e3d..9b40d143 100644 --- a/django_mongodb/query.py +++ b/django_mongodb/query.py @@ -1,11 +1,11 @@ import re from functools import wraps -from django.core.exceptions import FullResultSet +from django.core.exceptions import EmptyResultSet, FullResultSet from django.db import DatabaseError, IntegrityError, NotSupportedError from django.db.models.lookups import UUIDTextMixin from django.db.models.query import QuerySet -from django.db.models.sql.where import OR, SubqueryConstraint +from django.db.models.sql.where import AND, OR, SubqueryConstraint from django.utils.tree import Node from pymongo import ASCENDING, DESCENDING from pymongo.errors import DuplicateKeyError, PyMongoError @@ -142,6 +142,11 @@ def get_cursor(self): def add_filters(self, filters, query=None): children = self._get_children(filters.children) + if filters.connector == AND: + full_needed, empty_needed = len(children), 1 + else: + full_needed, empty_needed = 1, len(children) + if query is None: query = self.mongo_query @@ -162,7 +167,20 @@ def add_filters(self, filters, query=None): if filters.connector == OR and filters.negated: raise NotImplementedError("Negated ORs are not supported.") - self.add_filters(child, query=subquery) + try: + self.add_filters(child, query=subquery) + except EmptyResultSet: + empty_needed -= 1 + if empty_needed == 0: + exc = FullResultSet if filters.negated else EmptyResultSet + raise exc from None + continue + except FullResultSet: + full_needed -= 1 + if full_needed == 0: + exc = EmptyResultSet if filters.negated else FullResultSet + raise exc from None + continue if filters.connector == OR and subquery: or_conditions.extend(subquery.pop("$or", [])) @@ -173,7 +191,21 @@ def add_filters(self, filters, query=None): try: field, lookup_type, value = self._decode_child(child) + except EmptyResultSet: + empty_needed -= 1 + if empty_needed == 0: + if filters.negated: + self._negated = not self._negated + exc = FullResultSet if filters.negated else EmptyResultSet + raise exc from None + continue except FullResultSet: + full_needed -= 1 + if full_needed == 0: + if filters.negated: + self._negated = not self._negated + exc = EmptyResultSet if filters.negated else FullResultSet + raise exc from None continue column = field.column