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:
parent
2e55cf580e
commit
eb6c107624
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
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,
|
||||||
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
|
||||||
|
|
Loading…
Reference in New Issue