[1.8.x] Fixed #25377 -- Changed Count queries to execute COUNT(*) instead of COUNT('*').

Backport of 3fe3887a2e from master
This commit is contained in:
Adam Chainz 2015-09-10 17:07:09 +01:00 committed by Tim Graham
parent 69017bade0
commit 3c2c74f58f
6 changed files with 33 additions and 14 deletions

View File

@ -2,7 +2,7 @@
Classes to represent the definitions of aggregate functions. Classes to represent the definitions of aggregate functions.
""" """
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db.models.expressions import Func, Value from django.db.models.expressions import Func, Star
from django.db.models.fields import FloatField, IntegerField from django.db.models.fields import FloatField, IntegerField
__all__ = [ __all__ = [
@ -90,7 +90,7 @@ class Count(Aggregate):
def __init__(self, expression, distinct=False, **extra): def __init__(self, expression, distinct=False, **extra):
if expression == '*': if expression == '*':
expression = Value(expression) expression = Star()
super(Count, self).__init__( super(Count, self).__init__(
expression, distinct='DISTINCT ' if distinct else '', output_field=IntegerField(), **extra) expression, distinct='DISTINCT ' if distinct else '', output_field=IntegerField(), **extra)

View File

@ -584,6 +584,14 @@ class RawSQL(Expression):
return [self] return [self]
class Star(Expression):
def __repr__(self):
return "'*'"
def as_sql(self, compiler, connection):
return '*', []
class Random(Expression): class Random(Expression):
def __init__(self): def __init__(self):
super(Random, self).__init__(output_field=fields.FloatField()) super(Random, self).__init__(output_field=fields.FloatField())

View File

@ -32,3 +32,7 @@ Bugfixes
* Fixed migrations crash on MySQL when adding a text or a blob field with an * Fixed migrations crash on MySQL when adding a text or a blob field with an
unhashable default (:ticket:`25393`). unhashable default (:ticket:`25393`).
* Changed ``Count`` queries to execute ``COUNT(*)`` instead of ``COUNT('*')``
as versions of Django before 1.8 did (:ticket:`25377`). This may fix a
performance regression on some databases.

View File

@ -306,6 +306,12 @@ class BaseAggregateTestCase(TestCase):
vals = Book.objects.aggregate(Count("rating", distinct=True)) vals = Book.objects.aggregate(Count("rating", distinct=True))
self.assertEqual(vals, {"rating__count": 4}) self.assertEqual(vals, {"rating__count": 4})
def test_count_star(self):
with self.assertNumQueries(1) as ctx:
Book.objects.aggregate(n=Count("*"))
sql = ctx.captured_queries[0]['sql']
self.assertIn('SELECT COUNT(*) ', sql)
def test_fkey_aggregate(self): def test_fkey_aggregate(self):
explicit = list(Author.objects.annotate(Count('book__id'))) explicit = list(Author.objects.annotate(Count('book__id')))
implicit = list(Author.objects.annotate(Count('book'))) implicit = list(Author.objects.annotate(Count('book')))

View File

@ -877,6 +877,7 @@ class ReprTests(TestCase):
def test_aggregates(self): def test_aggregates(self):
self.assertEqual(repr(Avg('a')), "Avg(F(a))") self.assertEqual(repr(Avg('a')), "Avg(F(a))")
self.assertEqual(repr(Count('a')), "Count(F(a), distinct=False)") self.assertEqual(repr(Count('a')), "Count(F(a), distinct=False)")
self.assertEqual(repr(Count('*')), "Count('*', distinct=False)")
self.assertEqual(repr(Max('a')), "Max(F(a))") self.assertEqual(repr(Max('a')), "Max(F(a))")
self.assertEqual(repr(Min('a')), "Min(F(a))") self.assertEqual(repr(Min('a')), "Min(F(a))")
self.assertEqual(repr(StdDev('a')), "StdDev(F(a), sample=False)") self.assertEqual(repr(StdDev('a')), "StdDev(F(a), sample=False)")

View File

@ -63,25 +63,25 @@ class TestDebugSQL(unittest.TestCase):
if six.PY3: if six.PY3:
expected_outputs = [ expected_outputs = [
('''QUERY = 'SELECT COUNT(%s) AS "__count" ''' ('''QUERY = 'SELECT COUNT(*) AS "__count" '''
'''FROM "test_runner_person" WHERE ''' '''FROM "test_runner_person" WHERE '''
'''"test_runner_person"."first_name" = %s' ''' '''"test_runner_person"."first_name" = %s' '''
'''- PARAMS = ('*', 'error');'''), '''- PARAMS = ('error',);'''),
('''QUERY = 'SELECT COUNT(%s) AS "__count" ''' ('''QUERY = 'SELECT COUNT(*) AS "__count" '''
'''FROM "test_runner_person" WHERE ''' '''FROM "test_runner_person" WHERE '''
'''"test_runner_person"."first_name" = %s' ''' '''"test_runner_person"."first_name" = %s' '''
'''- PARAMS = ('*', 'fail');'''), '''- PARAMS = ('fail',);'''),
] ]
else: else:
expected_outputs = [ expected_outputs = [
('''QUERY = u'SELECT COUNT(%s) AS "__count" ''' ('''QUERY = u'SELECT COUNT(*) AS "__count" '''
'''FROM "test_runner_person" WHERE ''' '''FROM "test_runner_person" WHERE '''
'''"test_runner_person"."first_name" = %s' ''' '''"test_runner_person"."first_name" = %s' '''
'''- PARAMS = (u'*', u'error');'''), '''- PARAMS = (u'error',);'''),
('''QUERY = u'SELECT COUNT(%s) AS "__count" ''' ('''QUERY = u'SELECT COUNT(*) AS "__count" '''
'''FROM "test_runner_person" WHERE ''' '''FROM "test_runner_person" WHERE '''
'''"test_runner_person"."first_name" = %s' ''' '''"test_runner_person"."first_name" = %s' '''
'''- PARAMS = (u'*', u'fail');'''), '''- PARAMS = (u'fail',);'''),
] ]
verbose_expected_outputs = [ verbose_expected_outputs = [
@ -94,15 +94,15 @@ class TestDebugSQL(unittest.TestCase):
] ]
if six.PY3: if six.PY3:
verbose_expected_outputs += [ verbose_expected_outputs += [
('''QUERY = 'SELECT COUNT(%s) AS "__count" ''' ('''QUERY = 'SELECT COUNT(*) AS "__count" '''
'''FROM "test_runner_person" WHERE ''' '''FROM "test_runner_person" WHERE '''
'''"test_runner_person"."first_name" = %s' ''' '''"test_runner_person"."first_name" = %s' '''
'''- PARAMS = ('*', 'pass');'''), '''- PARAMS = ('pass',);'''),
] ]
else: else:
verbose_expected_outputs += [ verbose_expected_outputs += [
('''QUERY = u'SELECT COUNT(%s) AS "__count" ''' ('''QUERY = u'SELECT COUNT(*) AS "__count" '''
'''FROM "test_runner_person" WHERE ''' '''FROM "test_runner_person" WHERE '''
'''"test_runner_person"."first_name" = %s' ''' '''"test_runner_person"."first_name" = %s' '''
'''- PARAMS = (u'*', u'pass');'''), '''- PARAMS = (u'pass',);'''),
] ]