Kaydet (Commit) eb6c1076 authored tarafından Nick Sandford's avatar Nick Sandford Kaydeden (comit) Claude Paroz

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.
üst 2e55cf58
...@@ -18,6 +18,8 @@ from django.db.backends.signals import connection_created ...@@ -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): ...@@ -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
......
...@@ -2188,6 +2188,14 @@ Django provides the following aggregation functions in the ...@@ -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
~~~ ~~~
......
...@@ -75,3 +75,14 @@ class Article(models.Model): ...@@ -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
...@@ -12,6 +12,7 @@ from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS, ...@@ -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): ...@@ -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): ...@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment