Commit eb6c1076 authored by Nick Sandford's avatar Nick Sandford Committed by Claude Paroz
Browse files

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.
parent 2e55cf58
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -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.creation import DatabaseCreation
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.functional import cached_property
from django.utils.safestring import SafeBytes
@@ -127,6 +129,17 @@ class DatabaseOperations(BaseDatabaseOperations):
        limit = 999 if len(fields) > 1 else 500
        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):
        # sqlite doesn't support extract, so we fake it with the user-defined
        # function django_extract that's registered in connect(). Note that
+8 −0
Original line number Diff line number Diff line
@@ -2188,6 +2188,14 @@ Django provides the following aggregation functions in the
aggregate functions, see
: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
~~~

+11 −0
Original line number Diff line number Diff line
@@ -75,3 +75,14 @@ class Article(models.Model):

    def __str__(self):
        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
+17 −1
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS,
    IntegrityError, transaction)
from django.db.backends.signals import connection_created
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.test import (TestCase, skipUnlessDBFeature, skipIfDBFeature,
    TransactionTestCase)
@@ -362,6 +363,22 @@ class EscapingChecks(TestCase):
        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):

    def create_squares_with_executemany(self, args):
@@ -400,7 +417,6 @@ class BackendTestCase(TestCase):
            self.create_squares_with_executemany(args)
        self.assertEqual(models.Square.objects.count(), 9)


    def test_unicode_fetches(self):
        #6254: fetchone, fetchmany, fetchall return strings as unicode objects
        qn = connection.ops.quote_name