Skip to content

Commit

Permalink
Fixed #32060 -- Added Random database function.
Browse files Browse the repository at this point in the history
  • Loading branch information
ngnpope authored and felixxm committed Oct 2, 2020
1 parent f87b0ec commit 06c5d3f
Show file tree
Hide file tree
Showing 12 changed files with 51 additions and 27 deletions.
4 changes: 0 additions & 4 deletions django/db/backends/base/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,6 @@ def quote_name(self, name):
"""
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a quote_name() method')

def random_function_sql(self):
"""Return an SQL expression that returns a random value."""
return 'RANDOM()'

def regex_lookup(self, lookup_type):
"""
Return the string to use in a query when performing regular expression
Expand Down
3 changes: 0 additions & 3 deletions django/db/backends/mysql/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,6 @@ def quote_name(self, name):
return name # Quoting once is enough.
return "`%s`" % name

def random_function_sql(self):
return 'RAND()'

def return_insert_columns(self, fields):
# MySQL and MariaDB < 10.5.0 don't support an INSERT...RETURNING
# statement.
Expand Down
3 changes: 0 additions & 3 deletions django/db/backends/oracle/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,6 @@ def quote_name(self, name):
name = name.replace('%', '%%')
return name.upper()

def random_function_sql(self):
return "DBMS_RANDOM.RANDOM"

def regex_lookup(self, lookup_type):
if lookup_type == 'regex':
match_option = "'c'"
Expand Down
4 changes: 4 additions & 0 deletions django/db/backends/sqlite3/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import hashlib
import math
import operator
import random
import re
import statistics
import warnings
Expand Down Expand Up @@ -254,6 +255,9 @@ def get_new_connection(self, conn_params):
create_deterministic_function('SIN', 1, none_guard(math.sin))
create_deterministic_function('SQRT', 1, none_guard(math.sqrt))
create_deterministic_function('TAN', 1, none_guard(math.tan))
# Don't use the built-in RANDOM() function because it returns a value
# in the range [2^63, 2^63 - 1] instead of [0, 1).
conn.create_function('RAND', 0, random.random)
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))
Expand Down
10 changes: 0 additions & 10 deletions django/db/models/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,16 +811,6 @@ def as_sql(self, compiler, connection):
return '*', []


class Random(Expression):
output_field = fields.FloatField()

def __repr__(self):
return "Random()"

def as_sql(self, compiler, connection):
return connection.ops.random_function_sql(), []


class Col(Expression):

contains_column_references = True
Expand Down
6 changes: 3 additions & 3 deletions django/db/models/functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
)
from .math import (
Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Cot, Degrees, Exp, Floor, Ln, Log,
Mod, Pi, Power, Radians, Round, Sign, Sin, Sqrt, Tan,
Mod, Pi, Power, Radians, Random, Round, Sign, Sin, Sqrt, Tan,
)
from .text import (
MD5, SHA1, SHA224, SHA256, SHA384, SHA512, Chr, Concat, ConcatPair, Left,
Expand All @@ -31,8 +31,8 @@
'TruncQuarter', 'TruncSecond', 'TruncTime', 'TruncWeek', 'TruncYear',
# math
'Abs', 'ACos', 'ASin', 'ATan', 'ATan2', 'Ceil', 'Cos', 'Cot', 'Degrees',
'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Round',
'Sign', 'Sin', 'Sqrt', 'Tan',
'Exp', 'Floor', 'Ln', 'Log', 'Mod', 'Pi', 'Power', 'Radians', 'Random',
'Round', 'Sign', 'Sin', 'Sqrt', 'Tan',
# text
'MD5', 'SHA1', 'SHA224', 'SHA256', 'SHA384', 'SHA512', 'Chr', 'Concat',
'ConcatPair', 'Left', 'Length', 'Lower', 'LPad', 'LTrim', 'Ord', 'Repeat',
Expand Down
14 changes: 14 additions & 0 deletions django/db/models/functions/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ def as_oracle(self, compiler, connection, **extra_context):
)


class Random(NumericOutputFieldMixin, Func):
function = 'RANDOM'
arity = 0

def as_mysql(self, compiler, connection, **extra_context):
return super().as_sql(compiler, connection, function='RAND', **extra_context)

def as_oracle(self, compiler, connection, **extra_context):
return super().as_sql(compiler, connection, function='DBMS_RANDOM.VALUE', **extra_context)

def as_sqlite(self, compiler, connection, **extra_context):
return super().as_sql(compiler, connection, function='RAND', **extra_context)


class Round(Transform):
function = 'ROUND'
lookup_name = 'round'
Expand Down
4 changes: 2 additions & 2 deletions django/db/models/sql/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from django.core.exceptions import EmptyResultSet, FieldError
from django.db import DatabaseError, NotSupportedError
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import F, OrderBy, Random, RawSQL, Ref, Value
from django.db.models.functions import Cast
from django.db.models.expressions import F, OrderBy, RawSQL, Ref, Value
from django.db.models.functions import Cast, Random
from django.db.models.query_utils import Q, select_related_descend
from django.db.models.sql.constants import (
CURSOR, GET_ITERATOR_CHUNK_SIZE, MULTI, NO_RESULTS, ORDER_DIR, SINGLE,
Expand Down
9 changes: 9 additions & 0 deletions docs/ref/models/database-functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,15 @@ It can also be registered as a transform. For example::
>>> # Get vectors whose radians are less than 1
>>> vectors = Vector.objects.filter(x__radians__lt=1, y__radians__lt=1)

``Random``
----------

.. class:: Random(**extra)

.. versionadded:: 3.2

Returns a random value in the range ``0.0 ≤ x < 1.0``.

``Round``
---------

Expand Down
5 changes: 5 additions & 0 deletions docs/releases/3.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ Models
:attr:`TextField <django.db.models.TextField.db_collation>` allows setting a
database collation for the field.

* Added the :class:`~django.db.models.functions.Random` database function.

Pagination
~~~~~~~~~~

Expand Down Expand Up @@ -444,6 +446,9 @@ backends.
non-deterministic collations are not supported, set
``supports_non_deterministic_collations`` to ``False``.

* ``DatabaseOperations.random_function_sql()`` is removed in favor of the new
:class:`~django.db.models.functions.Random` database function.

:mod:`django.contrib.admin`
---------------------------

Expand Down
13 changes: 13 additions & 0 deletions tests/db_functions/math/test_random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.db.models.functions import Random
from django.test import TestCase

from ..models import FloatModel


class RandomTests(TestCase):
def test(self):
FloatModel.objects.create()
obj = FloatModel.objects.annotate(random=Random()).first()
self.assertIsInstance(obj.random, float)
self.assertGreaterEqual(obj.random, 0)
self.assertLess(obj.random, 1)
3 changes: 1 addition & 2 deletions tests/expressions/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
UUIDField, Value, Variance, When,
)
from django.db.models.expressions import (
Col, Combinable, CombinedExpression, Random, RawSQL, Ref,
Col, Combinable, CombinedExpression, RawSQL, Ref,
)
from django.db.models.functions import (
Coalesce, Concat, Left, Length, Lower, Substr, Upper,
Expand Down Expand Up @@ -1814,7 +1814,6 @@ def test_expressions(self):
)
self.assertEqual(repr(Func('published', function='TO_CHAR')), "Func(F(published), function=TO_CHAR)")
self.assertEqual(repr(OrderBy(Value(1))), 'OrderBy(Value(1), descending=False)')
self.assertEqual(repr(Random()), "Random()")
self.assertEqual(repr(RawSQL('table.col', [])), "RawSQL(table.col, [])")
self.assertEqual(repr(Ref('sum_cost', Sum('cost'))), "Ref(sum_cost, Sum(F(cost)))")
self.assertEqual(repr(Value(1)), "Value(1)")
Expand Down

0 comments on commit 06c5d3f

Please sign in to comment.