Opened 2 years ago

Closed 2 years ago

#34024 closed Bug (fixed)

'WhereNode' object has no attribute 'is_summary' when counting a queryset with annotation from a subquery

Reported by: Valentin Rigal Owned by: David Sanders
Component: Database layer (models, ORM) Version: 4.1
Severity: Normal Keywords:
Cc: David Sanders, David Wobrock Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I reproduced on a blank project (4.0 and 4.1 with a Postgres database) using this use case:

models.py

class School(models.Model):
    """A school"""

class Director(models.Model):
    school = models.ForeignKey(School, on_delete=models.CASCADE)                        
    speciality = models.CharField(max_length=200)

It is not possible to run this code:

>>> School.objects.annotate(
>>>     speciality=Subquery(Director.objects.values('speciality')[:1]),
>>>     has_speciality=Q(speciality__isnull=False)
>>> ).count()

AttributeError: 'WhereNode' object has no attribute 'is_summary'

Reverting this precise commit seems to fix the issue : https://github.com/django/django/commit/b64db05b9cedd96905d637a2d824cbbf428e40e7

The request is a bit exotic, I don't know if it should be supported.
This is the first ticket I open, I can clarify/submit a merge request if needed.

Change History (6)

comment:1 by David Sanders, 2 years ago

Confirmed on latest main.

Full traceback from ipython:

[ins] In [1]: School.objects.annotate(
         ...:     speciality=Subquery(Director.objects.values('speciality')[:1]),
         ...:     has_speciality=Q(speciality__isnull=False)
         ...: ).count()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In [1], line 4
      1 School.objects.annotate(
      2     speciality=Subquery(Director.objects.values('speciality')[:1]),
      3     has_speciality=Q(speciality__isnull=False)
----> 4 ).count()

File ~/projects/django/django/db/models/query.py:621, in QuerySet.count(self)
    618 if self._result_cache is not None:
    619     return len(self._result_cache)
--> 621 return self.query.get_count(using=self.db)

File ~/projects/django/django/db/models/sql/query.py:554, in Query.get_count(self, using)
    552 obj = self.clone()
    553 obj.add_annotation(Count("*"), alias="__count", is_summary=True)
--> 554 return obj.get_aggregation(using, ["__count"])["__count"]

File ~/projects/django/django/db/models/sql/query.py:503, in Query.get_aggregation(self, using, added_aggregate_names)
    501 for alias, expression in list(inner_query.annotation_select.items()):
    502     annotation_select_mask = inner_query.annotation_select_mask
--> 503     if expression.is_summary:
    504         expression, col_cnt = inner_query.rewrite_cols(expression, col_cnt)
    505         outer_query.annotations[alias] = expression.relabeled_clone(
    506             relabels
    507         )

AttributeError: 'WhereNode' object has no attribute 'is_summary'

comment:2 by Simon Charette, 2 years ago

Triage Stage: UnreviewedAccepted

comment:3 by David Sanders, 2 years ago

Cc: David Sanders added

comment:4 by David Sanders, 2 years ago

Here's a simple boiled down failing test case:

diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 1bd72dd8b8..c6d9b05761 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -1312,6 +1312,10 @@ class Queries1Tests(TestCase):
         )
         self.assertSequenceEqual(Note.objects.exclude(negate=True), [self.n3])

+    def test_count_on_annotation(self):
+        # Ticket: #34024
+        Tag.objects.annotate(has_pk=~Q(pk=None)).count()
+

 class Queries2Tests(TestCase):
     @classmethod

results

E
======================================================================
ERROR: test_count_on_annotation (queries.tests.Queries1Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/davids/projects/django/tests/queries/tests.py", line 1317, in test_count_on_annotation
    Tag.objects.annotate(has_pk=~Q(pk=None)).count()
  File "/Users/davids/projects/django/django/db/models/query.py", line 621, in count
    return self.query.get_count(using=self.db)
  File "/Users/davids/projects/django/django/db/models/sql/query.py", line 554, in get_count
    return obj.get_aggregation(using, ["__count"])["__count"]
  File "/Users/davids/projects/django/django/db/models/sql/query.py", line 503, in get_aggregation
    if expression.is_summary:
AttributeError: 'WhereNode' object has no attribute 'is_summary'

----------------------------------------------------------------------
Ran 1 test in 0.014s

FAILED (errors=1)
Destroying test database for alias 'default'...
Version 0, edited 2 years ago by David Sanders (next)

comment:5 by David Wobrock, 2 years ago

Cc: David Wobrock added
Has patch: set
Owner: changed from nobody to David Sanders
Status: newassigned

comment:6 by GitHub <noreply@…>, 2 years ago

Resolution: fixed
Status: assignedclosed

In 1674c705:

Fixed #34024 -- Fixed crash when aggregating querysets with Q objects annotations.

This reverts b64db05b9cedd96905d637a2d824cbbf428e40e7.

It was reasonable to assume it was unnecessary code as there were
no failing tests upon its removal. This commit adds the necessary
regression tests for the failing condition identified in #34024
alongside the original tests added in the PR for which
WhereNode.is_summary was introduced.

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