From 83677faf860f1a25acd667131275307abaa23fc4 Mon Sep 17 00:00:00 2001
From: Nick Pope <nick.pope@flightdataservices.com>
Date: Wed, 19 Dec 2018 23:01:44 +0000
Subject: [PATCH] Fixed #30056 -- Added SQLite support for StdDev and Variance
 functions.

---
 django/db/backends/base/features.py    | 12 +-----------
 django/db/backends/sqlite3/base.py     | 13 +++++++++++++
 django/db/backends/sqlite3/features.py | 21 ---------------------
 docs/ref/models/querysets.txt          | 16 ++++------------
 docs/releases/2.2.txt                  |  3 +++
 tests/aggregation_regress/tests.py     |  1 -
 tests/backends/tests.py                |  1 -
 7 files changed, 21 insertions(+), 46 deletions(-)

diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index 9787901b07..7a71c76bd3 100644
--- a/django/db/backends/base/features.py
+++ b/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
 
 
@@ -298,12 +297,3 @@ class BaseDatabaseFeatures:
             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
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index 1c39fb4add..5b1c80d27c 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -7,6 +7,7 @@ import functools
 import math
 import operator
 import re
+import statistics
 import warnings
 from itertools import chain
 from sqlite3 import dbapi2 as Database
@@ -49,6 +50,14 @@ def none_guard(func):
     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))
@@ -210,6 +219,10 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         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
 
diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
index d76a1275f5..c57b8e1934 100644
--- a/django/db/backends/sqlite3/features.py
+++ b/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
 
@@ -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
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 81a82fe7b6..9dd661e6a7 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -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``
 ~~~~~~~
@@ -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
 ===================
diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt
index f2e822f5a5..f731562640 100644
--- a/docs/releases/2.2.txt
+++ b/docs/releases/2.2.txt
@@ -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 the :class:`~django.db.models.StdDev` and
+  :class:`~django.db.models.Variance` functions.
+
 Requests and Responses
 ~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py
index 893b22ae69..29b32c4987 100644
--- a/tests/aggregation_regress/tests.py
+++ b/tests/aggregation_regress/tests.py
@@ -1116,7 +1116,6 @@ class AggregationTests(TestCase):
             lambda b: (b.name, b.authorCount)
         )
 
-    @skipUnlessDBFeature('supports_stddev')
     def test_stddev(self):
         self.assertEqual(
             Book.objects.aggregate(StdDev('pages')),
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index ee6bee3a02..d1b89950c0 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -340,7 +340,6 @@ class BackendTestCase(TransactionTestCase):
 
     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):