Opened 4 years ago

Closed 4 years ago

Last modified 4 years ago

#32455 closed Bug (fixed)

Q(...) & Exists(...) raises a TypeError

Reported by: john-parton Owned by: Hasan Ramezani
Component: Database layer (models, ORM) Version: 3.1
Severity: Normal Keywords:
Cc: Matthew Schinckel Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Exists(...) & Q(...) works, but Q(...) & Exists(...) raise a TypeError

Here's a minimal example:

In [3]: Exists(Product.objects.all()) & Q()
Out[3]: <Q: (AND: <django.db.models.expressions.Exists object at 0x7fc18dd0ed90>, (AND: ))>

In [4]: Q() & Exists(Product.objects.all())
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-21d3dea0fcb9> in <module>
----> 1 Q() & Exists(Product.objects.all())

~/Code/venv/ecom/lib/python3.8/site-packages/django/db/models/query_utils.py in __and__(self, other)
     90 
     91     def __and__(self, other):
---> 92         return self._combine(other, self.AND)
     93 
     94     def __invert__(self):

~/Code/venv/ecom/lib/python3.8/site-packages/django/db/models/query_utils.py in _combine(self, other, conn)
     71     def _combine(self, other, conn):
     72         if not isinstance(other, Q):
---> 73             raise TypeError(other)
     74 
     75         # If the other Q() is empty, ignore it and just use `self`.

TypeError: <django.db.models.expressions.Exists object at 0x7fc18dd21400>

The & (and |) operators should be commutative on Q-Exists pairs, but it's not

I think there's a missing definition of __rand__ somewhere.

Change History (9)

comment:1 by Alexandr Tatarinov, 4 years ago

Triage Stage: UnreviewedAccepted

Reproduced on 3.1.6. The exception is raised by this two lines in the Q._combine, which are not present in the Combinable._combine from which Exists inherit.

if not isinstance(other, Q):
    raise TypeError(other)

comment:2 by Mariusz Felisiak, 4 years ago

Summary: Exists(...) & Q(...) works, but Q(...) & Exists(...) raise a TypeErrorQ(...) & Exists(...) raises a TypeError

Tests:

diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index 08ea0a51d3..20d0404f44 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -815,6 +815,15 @@ class BasicExpressionsTests(TestCase):
             Employee.objects.filter(Exists(is_poc) | Q(salary__lt=15)),
             [self.example_inc.ceo, self.max],
         )
+        self.assertCountEqual(
+           Employee.objects.filter(Q(salary__gte=30) & Exists(is_ceo)),
+           [self.max],
+        )
+        self.assertCountEqual(
+            Employee.objects.filter(Q(salary__lt=15) | Exists(is_poc)),
+            [self.example_inc.ceo, self.max],
+        )
+
 
 
 class IterableLookupInnerExpressionsTests(TestCase):

comment:3 by Mariusz Felisiak, 4 years ago

Cc: Matthew Schinckel added

comment:4 by Hasan Ramezani, 4 years ago

Has patch: set
Owner: changed from nobody to Hasan Ramezani
Status: newassigned

comment:5 by Mariusz Felisiak, 4 years ago

Triage Stage: AcceptedReady for checkin

comment:6 by Mariusz Felisiak <felisiak.mariusz@…>, 4 years ago

In efce2149:

Refs #32455 -- Added tests for left combining an empty Q() with boolean expressions.

comment:7 by Mariusz Felisiak <felisiak.mariusz@…>, 4 years ago

Resolution: fixed
Status: assignedclosed

In f2bef2b7:

Fixed #32455 -- Allowed right combining Q() with boolean expressions.

comment:8 by Mariusz Felisiak <felisiak.mariusz@…>, 4 years ago

In 732cf4c5:

[3.2.x] Refs #32455 -- Added tests for left combining an empty Q() with boolean expressions.

Backport of efce21497cc21140c5fe2b133064cd815c97b3f5 from master

comment:9 by Mariusz Felisiak <felisiak.mariusz@…>, 4 years ago

In 0debc6b:

[3.2.x] Fixed #32455 -- Allowed right combining Q() with boolean expressions.

Backport of f2bef2b7bc6c817af0f5fa77e1052a1f5ce12f71 from master

Note: See TracTickets for help on using tickets.
Back to Top