Skip to content

Commit

Permalink
Fixed #30056 -- Added SQLite support for StdDev and Variance functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
ngnpope authored and timgraham committed Dec 24, 2018
1 parent e626a3f commit 9bfb0d6
Show file tree
Hide file tree
Showing 7 changed files with 21 additions and 46 deletions.
12 changes: 1 addition & 11 deletions django/db/backends/base/features.py
@@ -1,5 +1,4 @@
from django.db.models.aggregates import StdDev
from django.db.utils import NotSupportedError, ProgrammingError
from django.db.utils import ProgrammingError
from django.utils.functional import cached_property


Expand Down Expand Up @@ -298,12 +297,3 @@ def supports_transactions(self):
count, = cursor.fetchone()
cursor.execute('DROP TABLE ROLLBACK_TEST')
return count == 0

@cached_property
def supports_stddev(self):
"""Confirm support for STDDEV and related stats functions."""
try:
self.connection.ops.check_expression_support(StdDev(1))
except NotSupportedError:
return False
return True
13 changes: 13 additions & 0 deletions django/db/backends/sqlite3/base.py
Expand Up @@ -7,6 +7,7 @@
import math
import operator
import re
import statistics
import warnings
from itertools import chain
from sqlite3 import dbapi2 as Database
Expand Down Expand Up @@ -49,6 +50,14 @@ def wrapper(*args, **kwargs):
return wrapper


def list_aggregate(function):
"""
Return an aggregate class that accumulates values in a list and applies
the provided function to the data.
"""
return type('ListAggregate', (list,), {'finalize': function, 'step': list.append})


Database.register_converter("bool", b'1'.__eq__)
Database.register_converter("time", decoder(parse_time))
Database.register_converter("datetime", decoder(parse_datetime))
Expand Down Expand Up @@ -210,6 +219,10 @@ def get_new_connection(self, conn_params):
conn.create_function('SIN', 1, none_guard(math.sin))
conn.create_function('SQRT', 1, none_guard(math.sqrt))
conn.create_function('TAN', 1, none_guard(math.tan))
conn.create_aggregate('STDDEV_POP', 1, list_aggregate(statistics.pstdev))
conn.create_aggregate('STDDEV_SAMP', 1, list_aggregate(statistics.stdev))
conn.create_aggregate('VAR_POP', 1, list_aggregate(statistics.pvariance))
conn.create_aggregate('VAR_SAMP', 1, list_aggregate(statistics.variance))
conn.execute('PRAGMA foreign_keys = ON')
return conn

Expand Down
21 changes: 0 additions & 21 deletions django/db/backends/sqlite3/features.py
@@ -1,8 +1,6 @@
import sys

from django.db import utils
from django.db.backends.base.features import BaseDatabaseFeatures
from django.utils.functional import cached_property

from .base import Database

Expand Down Expand Up @@ -41,22 +39,3 @@ class DatabaseFeatures(BaseDatabaseFeatures):
# reasonably performant way.
supports_pragma_foreign_key_check = Database.sqlite_version_info >= (3, 20, 0)
can_defer_constraint_checks = supports_pragma_foreign_key_check

@cached_property
def supports_stddev(self):
"""
Confirm support for STDDEV and related stats functions.
SQLite supports STDDEV as an extension package; so
connection.ops.check_expression_support() can't unilaterally
rule out support for STDDEV. Manually check whether the call works.
"""
with self.connection.cursor() as cursor:
cursor.execute('CREATE TABLE STDDEV_TEST (X INT)')
try:
cursor.execute('SELECT STDDEV(*) FROM STDDEV_TEST')
has_support = True
except utils.DatabaseError:
has_support = False
cursor.execute('DROP TABLE STDDEV_TEST')
return has_support
16 changes: 4 additions & 12 deletions docs/ref/models/querysets.txt
Expand Up @@ -3400,12 +3400,9 @@ by the aggregate.
By default, ``StdDev`` returns the population standard deviation. However,
if ``sample=True``, the return value will be the sample standard deviation.

.. admonition:: SQLite
.. versionchanged:: 2.2

SQLite doesn't provide ``StdDev`` out of the box. An implementation
is available as an extension module for SQLite. Consult the `SQLite
documentation`_ for instructions on obtaining and installing this
extension.
SQLite support was added.

``Sum``
~~~~~~~
Expand Down Expand Up @@ -3434,14 +3431,9 @@ by the aggregate.
By default, ``Variance`` returns the population variance. However,
if ``sample=True``, the return value will be the sample variance.

.. admonition:: SQLite
.. versionchanged:: 2.2

SQLite doesn't provide ``Variance`` out of the box. An implementation
is available as an extension module for SQLite. Consult the `SQLite
documentation`_ for instructions on obtaining and installing this
extension.

.. _SQLite documentation: https://www.sqlite.org/contrib
SQLite support was added.

Query-related tools
===================
Expand Down
3 changes: 3 additions & 0 deletions docs/releases/2.2.txt
Expand Up @@ -235,6 +235,9 @@ Models
``Model.delete()``. This improves the performance of autocommit by reducing
the number of database round trips.

* Added SQLite support for :class:`~django.db.models.StdDev` and
:class:`~django.db.models.Variance`.

Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~

Expand Down
1 change: 0 additions & 1 deletion tests/aggregation_regress/tests.py
Expand Up @@ -1116,7 +1116,6 @@ def test_quoting_aggregate_order_by(self):
lambda b: (b.name, b.authorCount)
)

@skipUnlessDBFeature('supports_stddev')
def test_stddev(self):
self.assertEqual(
Book.objects.aggregate(StdDev('pages')),
Expand Down
1 change: 0 additions & 1 deletion tests/backends/tests.py
Expand Up @@ -340,7 +340,6 @@ def test_database_operations_init(self):

def test_cached_db_features(self):
self.assertIn(connection.features.supports_transactions, (True, False))
self.assertIn(connection.features.supports_stddev, (True, False))
self.assertIn(connection.features.can_introspect_foreign_keys, (True, False))

def test_duplicate_table_error(self):
Expand Down

0 comments on commit 9bfb0d6

Please sign in to comment.