[1.8.x] Fixed #24748 -- Fixed incorrect GROUP BY on MySQL in some queries

When the query's model had a self-referential foreign key, the
compiler.get_group_by() code incorrectly used the self-referential
foreign key's column (for example parent_id) as GROUP BY clause
when it should have used the model's primary key column (id).

Backport of adc57632bc from master
This commit is contained in:
Anssi Kääriäinen 2015-05-05 14:44:33 +03:00 committed by Tim Graham
parent 056a91dbfa
commit d5ce2dd7bc
4 changed files with 23 additions and 27 deletions

View File

@ -145,9 +145,12 @@ class SQLCompiler(object):
# then also add having expressions to group by. # then also add having expressions to group by.
pk = None pk = None
for expr in expressions: for expr in expressions:
if (expr.output_field.primary_key and # Is this a reference to query's base table primary key? If the
getattr(expr.output_field, 'model') == self.query.model): # expression isn't a Col-like, then skip the expression.
if (getattr(expr, 'target', None) == self.query.model._meta.pk and
getattr(expr, 'alias', None) == self.query.tables[0]):
pk = expr pk = expr
break
if pk: if pk:
expressions = [pk] + [expr for expr in expressions if expr in having] expressions = [pk] + [expr for expr in expressions if expr in having]
return expressions return expressions

View File

@ -17,3 +17,6 @@ Bugfixes
* Corrected join promotion for ``Case`` expressions. For example, annotating a * Corrected join promotion for ``Case`` expressions. For example, annotating a
query with a ``Case`` expression could unexpectedly filter out results query with a ``Case`` expression could unexpectedly filter out results
(:ticket:`24766`). (:ticket:`24766`).
* Fixed incorrect GROUP BY clause generation on MySQL when the query's model
has a self-referential foreign key (:ticket:`24748`).

View File

@ -104,3 +104,8 @@ class Bravo(models.Model):
class Charlie(models.Model): class Charlie(models.Model):
alfa = models.ForeignKey(Alfa, null=True) alfa = models.ForeignKey(Alfa, null=True)
bravo = models.ForeignKey(Bravo, null=True) bravo = models.ForeignKey(Bravo, null=True)
class SelfRefFK(models.Model):
name = models.CharField(max_length=50)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

View File

@ -16,7 +16,7 @@ from django.utils import six
from .models import ( from .models import (
Alfa, Author, Book, Bravo, Charlie, Clues, Entries, HardbackBook, ItemTag, Alfa, Author, Book, Bravo, Charlie, Clues, Entries, HardbackBook, ItemTag,
Publisher, WithManualPK, Publisher, SelfRefFK, WithManualPK,
) )
@ -1174,28 +1174,13 @@ class JoinPromotionTests(TestCase):
self.assertIn(' INNER JOIN ', str(qs.query)) self.assertIn(' INNER JOIN ', str(qs.query))
class AggregationOnRelationTest(TestCase): class SelfReferentialFKTests(TestCase):
def setUp(self): def test_ticket_24748(self):
self.a = Author.objects.create(name='Anssi', age=33) t1 = SelfRefFK.objects.create(name='t1')
self.p = Publisher.objects.create(name='Manning', num_awards=3) SelfRefFK.objects.create(name='t2', parent=t1)
Book.objects.create(isbn='asdf', name='Foo', pages=10, rating=0.1, price="0.0", SelfRefFK.objects.create(name='t3', parent=t1)
contact=self.a, publisher=self.p, pubdate=datetime.date.today()) self.assertQuerysetEqual(
SelfRefFK.objects.annotate(num_children=Count('children')).order_by('name'),
def test_annotate_on_relation(self): [('t1', 2), ('t2', 0), ('t3', 0)],
qs = Book.objects.annotate(avg_price=Avg('price'), publisher_name=F('publisher__name')) lambda x: (x.name, x.num_children)
self.assertEqual(qs[0].avg_price, 0.0)
self.assertEqual(qs[0].publisher_name, "Manning")
def test_aggregate_on_relation(self):
# A query with an existing annotation aggregation on a relation should
# succeed.
qs = Book.objects.annotate(avg_price=Avg('price')).aggregate(
publisher_awards=Sum('publisher__num_awards')
) )
self.assertEqual(qs['publisher_awards'], 3)
Book.objects.create(isbn='asdf', name='Foo', pages=10, rating=0.1, price="0.0",
contact=self.a, publisher=self.p, pubdate=datetime.date.today())
qs = Book.objects.annotate(avg_price=Avg('price')).aggregate(
publisher_awards=Sum('publisher__num_awards')
)
self.assertEqual(qs['publisher_awards'], 6)