Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #19360 -- Exception for aggregates on SQLite #637

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions django/db/backends/sqlite3/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
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
Expand Down Expand Up @@ -127,6 +129,18 @@ def bulk_batch_size(self, fields, objs):
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):
# fix for ticket 19360
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
Expand Down
8 changes: 8 additions & 0 deletions docs/ref/models/querysets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~

Expand Down
11 changes: 11 additions & 0 deletions tests/regressiontests/backends/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 17 additions & 1 deletion tests/regressiontests/backends/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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)
Expand Down Expand Up @@ -362,6 +363,22 @@ def test_parameter_escaping(self):
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):
Expand Down Expand Up @@ -400,7 +417,6 @@ def test_cursor_executemany_with_iterator(self):
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
Expand Down