Fixed #33282 -- Fixed a crash when OR'ing subquery and aggregation lookups.
As a QuerySet resolves to Query the outer column references grouping logic should be defined on the latter and proxied from Subquery for the cases where get_group_by_cols is called on unresolved expressions. Thanks Antonio Terceiro for the report and initial patch.
This commit is contained in:
parent
e3bde71676
commit
e5a92d400a
|
@ -1181,12 +1181,13 @@ class Subquery(BaseExpression, Combinable):
|
||||||
return sql, sql_params
|
return sql, sql_params
|
||||||
|
|
||||||
def get_group_by_cols(self, alias=None):
|
def get_group_by_cols(self, alias=None):
|
||||||
|
# If this expression is referenced by an alias for an explicit GROUP BY
|
||||||
|
# through values() a reference to this expression and not the
|
||||||
|
# underlying .query must be returned to ensure external column
|
||||||
|
# references are not grouped against as well.
|
||||||
if alias:
|
if alias:
|
||||||
return [Ref(alias, self)]
|
return [Ref(alias, self)]
|
||||||
external_cols = self.get_external_cols()
|
return self.query.get_group_by_cols()
|
||||||
if any(col.possibly_multivalued for col in external_cols):
|
|
||||||
return [self]
|
|
||||||
return external_cols
|
|
||||||
|
|
||||||
|
|
||||||
class Exists(Subquery):
|
class Exists(Subquery):
|
||||||
|
|
|
@ -1053,6 +1053,14 @@ class Query(BaseExpression):
|
||||||
if col.alias in self.external_aliases
|
if col.alias in self.external_aliases
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_group_by_cols(self, alias=None):
|
||||||
|
if alias:
|
||||||
|
return [Ref(alias, self)]
|
||||||
|
external_cols = self.get_external_cols()
|
||||||
|
if any(col.possibly_multivalued for col in external_cols):
|
||||||
|
return [self]
|
||||||
|
return external_cols
|
||||||
|
|
||||||
def as_sql(self, compiler, connection):
|
def as_sql(self, compiler, connection):
|
||||||
# Some backends (e.g. Oracle) raise an error when a subquery contains
|
# Some backends (e.g. Oracle) raise an error when a subquery contains
|
||||||
# unnecessary ORDER BY clause.
|
# unnecessary ORDER BY clause.
|
||||||
|
|
|
@ -1280,10 +1280,17 @@ class AggregateTestCase(TestCase):
|
||||||
).values(
|
).values(
|
||||||
'publisher'
|
'publisher'
|
||||||
).annotate(count=Count('pk')).values('count')
|
).annotate(count=Count('pk')).values('count')
|
||||||
long_books_count_breakdown = Publisher.objects.values_list(
|
groups = [
|
||||||
Subquery(long_books_count_qs, IntegerField()),
|
Subquery(long_books_count_qs),
|
||||||
).annotate(total=Count('*'))
|
long_books_count_qs,
|
||||||
self.assertEqual(dict(long_books_count_breakdown), {None: 1, 1: 4})
|
long_books_count_qs.query,
|
||||||
|
]
|
||||||
|
for group in groups:
|
||||||
|
with self.subTest(group=group.__class__.__name__):
|
||||||
|
long_books_count_breakdown = Publisher.objects.values_list(
|
||||||
|
group,
|
||||||
|
).annotate(total=Count('*'))
|
||||||
|
self.assertEqual(dict(long_books_count_breakdown), {None: 1, 1: 4})
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_subqueries_in_group_by')
|
@skipUnlessDBFeature('supports_subqueries_in_group_by')
|
||||||
def test_group_by_exists_annotation(self):
|
def test_group_by_exists_annotation(self):
|
||||||
|
@ -1341,6 +1348,25 @@ class AggregateTestCase(TestCase):
|
||||||
).values_list('publisher_count', flat=True)
|
).values_list('publisher_count', flat=True)
|
||||||
self.assertSequenceEqual(books_breakdown, [1] * 6)
|
self.assertSequenceEqual(books_breakdown, [1] * 6)
|
||||||
|
|
||||||
|
def test_filter_in_subquery_or_aggregation(self):
|
||||||
|
"""
|
||||||
|
Filtering against an aggregate requires the usage of the HAVING clause.
|
||||||
|
|
||||||
|
If such a filter is unionized to a non-aggregate one the latter will
|
||||||
|
also need to be moved to the HAVING clause and have its grouping
|
||||||
|
columns used in the GROUP BY.
|
||||||
|
|
||||||
|
When this is done with a subquery the specialized logic in charge of
|
||||||
|
using outer reference columns to group should be used instead of the
|
||||||
|
subquery itself as the latter might return multiple rows.
|
||||||
|
"""
|
||||||
|
authors = Author.objects.annotate(
|
||||||
|
Count('book'),
|
||||||
|
).filter(
|
||||||
|
Q(book__count__gt=0) | Q(pk__in=Book.objects.values('authors'))
|
||||||
|
)
|
||||||
|
self.assertQuerysetEqual(authors, Author.objects.all(), ordered=False)
|
||||||
|
|
||||||
def test_aggregation_random_ordering(self):
|
def test_aggregation_random_ordering(self):
|
||||||
"""Random() is not included in the GROUP BY when used for ordering."""
|
"""Random() is not included in the GROUP BY when used for ordering."""
|
||||||
authors = Author.objects.annotate(contact_count=Count('book')).order_by('?')
|
authors = Author.objects.annotate(contact_count=Count('book')).order_by('?')
|
||||||
|
|
Loading…
Reference in New Issue