1
0
mirror of https://github.com/django/django.git synced 2025-03-25 16:50:45 +00:00

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

This commit is contained in:
Adam Chainz 2015-09-10 17:07:09 +01:00 committed by Tim Graham
parent 4d933ad418
commit 3fe3887a2e
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__ = [
@ -98,7 +98,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

@ -593,6 +593,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

@ -405,6 +405,12 @@ class AggregateTestCase(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_non_grouped_annotation_not_in_group_by(self): def test_non_grouped_annotation_not_in_group_by(self):
""" """
An annotation not included in values() before an aggregate should be An annotation not included in values() before an aggregate should be

View File

@ -878,6 +878,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',);'''),
] ]