Fixed #19360 -- Raised an explicit exception for aggregates on date/time fields in sqlite3

Thanks lsaffre for the report and Chris Medrela for the initial
patch.
This commit is contained in:
Nick Sandford 2013-01-11 13:57:54 +08:00 committed by Claude Paroz
parent 2e55cf580e
commit eb6c107624
4 changed files with 49 additions and 1 deletions

View File

@ -18,6 +18,8 @@ from django.db.backends.signals import connection_created
from django.db.backends.sqlite3.client import DatabaseClient from django.db.backends.sqlite3.client import DatabaseClient
from django.db.backends.sqlite3.creation import DatabaseCreation from django.db.backends.sqlite3.creation import DatabaseCreation
from django.db.backends.sqlite3.introspection import DatabaseIntrospection from django.db.backends.sqlite3.introspection import DatabaseIntrospection
from django.db.models import fields
from django.db.models.sql import aggregates
from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import SafeBytes from django.utils.safestring import SafeBytes
@ -127,6 +129,17 @@ class DatabaseOperations(BaseDatabaseOperations):
limit = 999 if len(fields) > 1 else 500 limit = 999 if len(fields) > 1 else 500
return (limit // len(fields)) if len(fields) > 0 else len(objs) return (limit // len(fields)) if len(fields) > 0 else len(objs)
def check_aggregate_support(self, aggregate):
bad_fields = (fields.DateField, fields.DateTimeField, fields.TimeField)
bad_aggregates = (aggregates.Sum, aggregates.Avg,
aggregates.Variance, aggregates.StdDev)
if (isinstance(aggregate.source, bad_fields) and
isinstance(aggregate, bad_aggregates)):
raise NotImplementedError(
'You cannot use Sum, Avg, StdDev and Variance aggregations '
'on date/time fields in sqlite3 '
'since date/time is saved as text.')
def date_extract_sql(self, lookup_type, field_name): def date_extract_sql(self, lookup_type, field_name):
# sqlite doesn't support extract, so we fake it with the user-defined # sqlite doesn't support extract, so we fake it with the user-defined
# function django_extract that's registered in connect(). Note that # function django_extract that's registered in connect(). Note that

View File

@ -2188,6 +2188,14 @@ Django provides the following aggregation functions in the
aggregate functions, see aggregate functions, see
:doc:`the topic guide on aggregation </topics/db/aggregation>`. :doc:`the topic guide on aggregation </topics/db/aggregation>`.
.. warning::
SQLite can't handle aggregation on date/time fields out of the box.
This is because there are no native date/time fields in SQLite and Django
currently emulates these features using a text field. Attempts to use
aggregation on date/time fields in SQLite will raise
``NotImplementedError``.
Avg Avg
~~~ ~~~

View File

@ -75,3 +75,14 @@ class Article(models.Model):
def __str__(self): def __str__(self):
return self.headline return self.headline
@python_2_unicode_compatible
class Item(models.Model):
name = models.CharField(max_length=30)
date = models.DateField()
time = models.TimeField()
last_modified = models.DateTimeField()
def __str__(self):
return self.name

View File

@ -12,6 +12,7 @@ from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS,
IntegrityError, transaction) IntegrityError, transaction)
from django.db.backends.signals import connection_created from django.db.backends.signals import connection_created
from django.db.backends.postgresql_psycopg2 import version as pg_version from django.db.backends.postgresql_psycopg2 import version as pg_version
from django.db.models import fields, Sum, Avg, Variance, StdDev
from django.db.utils import ConnectionHandler, DatabaseError, load_backend from django.db.utils import ConnectionHandler, DatabaseError, load_backend
from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature, from django.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature,
TransactionTestCase) TransactionTestCase)
@ -362,6 +363,22 @@ class EscapingChecks(TestCase):
self.assertTrue(int(response)) self.assertTrue(int(response))
class SqlliteAggregationTests(TestCase):
"""
#19360: Raise NotImplementedError when aggregating on date/time fields.
"""
@unittest.skipUnless(connection.vendor == 'sqlite',
"No need to check SQLite aggregation semantics")
def test_aggregation(self):
for aggregate in (Sum, Avg, Variance, StdDev):
self.assertRaises(NotImplementedError,
models.Item.objects.all().aggregate, aggregate('time'))
self.assertRaises(NotImplementedError,
models.Item.objects.all().aggregate, aggregate('date'))
self.assertRaises(NotImplementedError,
models.Item.objects.all().aggregate, aggregate('last_modified'))
class BackendTestCase(TestCase): class BackendTestCase(TestCase):
def create_squares_with_executemany(self, args): def create_squares_with_executemany(self, args):
@ -400,7 +417,6 @@ class BackendTestCase(TestCase):
self.create_squares_with_executemany(args) self.create_squares_with_executemany(args)
self.assertEqual(models.Square.objects.count(), 9) self.assertEqual(models.Square.objects.count(), 9)
def test_unicode_fetches(self): def test_unicode_fetches(self):
#6254: fetchone, fetchmany, fetchall return strings as unicode objects #6254: fetchone, fetchmany, fetchall return strings as unicode objects
qn = connection.ops.quote_name qn = connection.ops.quote_name