Fixed #32060 -- Added Random database function.
This commit is contained in:
parent
f87b0ecd37
commit
06c5d3fafc
|
@ -334,10 +334,6 @@ class BaseDatabaseOperations:
|
|||
"""
|
||||
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
|
||||
|
|
|
@ -174,9 +174,6 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
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.
|
||||
|
|
|
@ -341,9 +341,6 @@ END;
|
|||
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'"
|
||||
|
|
|
@ -7,6 +7,7 @@ import functools
|
|||
import hashlib
|
||||
import math
|
||||
import operator
|
||||
import random
|
||||
import re
|
||||
import statistics
|
||||
import warnings
|
||||
|
@ -254,6 +255,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
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))
|
||||
|
|
|
@ -811,16 +811,6 @@ class Star(Expression):
|
|||
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
|
||||
|
|
|
@ -8,7 +8,7 @@ from .datetime import (
|
|||
)
|
||||
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,
|
||||
|
@ -31,8 +31,8 @@ __all__ = [
|
|||
'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',
|
||||
|
|
|
@ -141,6 +141,20 @@ class Radians(NumericOutputFieldMixin, Transform):
|
|||
)
|
||||
|
||||
|
||||
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'
|
||||
|
|
|
@ -6,8 +6,8 @@ from itertools import chain
|
|||
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,
|
||||
|
|
|
@ -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``
|
||||
---------
|
||||
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~
|
||||
|
||||
|
@ -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`
|
||||
---------------------------
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -16,7 +16,7 @@ from django.db.models import (
|
|||
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,
|
||||
|
@ -1814,7 +1814,6 @@ class ReprTests(SimpleTestCase):
|
|||
)
|
||||
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)")
|
||||
|
|
Loading…
Reference in New Issue