Fixed #17027 -- Added support for the power operator in F expressions.
Thanks dan at dlo.me for the initial patch. - Added __pow__ and __rpow__ to ExpressionNode - Added oracle and mysql specific power expressions - Added used-defined power function for sqlite
This commit is contained in:
parent
1597503a01
commit
5240b83462
|
@ -386,6 +386,14 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
|
items_sql = "(%s)" % ", ".join(["%s"] * len(fields))
|
||||||
return "VALUES " + ", ".join([items_sql] * num_values)
|
return "VALUES " + ", ".join([items_sql] * num_values)
|
||||||
|
|
||||||
|
def combine_expression(self, connector, sub_expressions):
|
||||||
|
"""
|
||||||
|
MySQL requires special cases for ^ operators in query expressions
|
||||||
|
"""
|
||||||
|
if connector == '^':
|
||||||
|
return 'POW(%s)' % ','.join(sub_expressions)
|
||||||
|
return super(DatabaseOperations, self).combine_expression(connector, sub_expressions)
|
||||||
|
|
||||||
|
|
||||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
vendor = 'mysql'
|
vendor = 'mysql'
|
||||||
|
|
|
@ -482,6 +482,8 @@ WHEN (new.%(col_name)s IS NULL)
|
||||||
return 'BITAND(%s)' % ','.join(sub_expressions)
|
return 'BITAND(%s)' % ','.join(sub_expressions)
|
||||||
elif connector == '|':
|
elif connector == '|':
|
||||||
raise NotImplementedError("Bit-wise or is not supported in Oracle.")
|
raise NotImplementedError("Bit-wise or is not supported in Oracle.")
|
||||||
|
elif connector == '^':
|
||||||
|
return 'POWER(%s)' % ','.join(sub_expressions)
|
||||||
return super(DatabaseOperations, self).combine_expression(connector, sub_expressions)
|
return super(DatabaseOperations, self).combine_expression(connector, sub_expressions)
|
||||||
|
|
||||||
def _get_sequence_name(self, table):
|
def _get_sequence_name(self, table):
|
||||||
|
|
|
@ -304,6 +304,13 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
res.extend(["UNION ALL SELECT %s" % ", ".join(["%s"] * len(fields))] * (num_values - 1))
|
res.extend(["UNION ALL SELECT %s" % ", ".join(["%s"] * len(fields))] * (num_values - 1))
|
||||||
return " ".join(res)
|
return " ".join(res)
|
||||||
|
|
||||||
|
def combine_expression(self, connector, sub_expressions):
|
||||||
|
# SQLite doesn't have a power function, so we fake it with a
|
||||||
|
# user-defined function django_power that's registered in connect().
|
||||||
|
if connector == '^':
|
||||||
|
return 'django_power(%s)' % ','.join(sub_expressions)
|
||||||
|
return super(DatabaseOperations, self).combine_expression(connector, sub_expressions)
|
||||||
|
|
||||||
|
|
||||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
vendor = 'sqlite'
|
vendor = 'sqlite'
|
||||||
|
@ -376,6 +383,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
conn.create_function("django_datetime_trunc", 3, _sqlite_datetime_trunc)
|
conn.create_function("django_datetime_trunc", 3, _sqlite_datetime_trunc)
|
||||||
conn.create_function("regexp", 2, _sqlite_regexp)
|
conn.create_function("regexp", 2, _sqlite_regexp)
|
||||||
conn.create_function("django_format_dtdelta", 5, _sqlite_format_dtdelta)
|
conn.create_function("django_format_dtdelta", 5, _sqlite_format_dtdelta)
|
||||||
|
conn.create_function("django_power", 2, _sqlite_power)
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def init_connection_state(self):
|
def init_connection_state(self):
|
||||||
|
@ -567,3 +575,7 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
|
||||||
|
|
||||||
def _sqlite_regexp(re_pattern, re_string):
|
def _sqlite_regexp(re_pattern, re_string):
|
||||||
return bool(re.search(re_pattern, force_text(re_string))) if re_string is not None else False
|
return bool(re.search(re_pattern, force_text(re_string))) if re_string is not None else False
|
||||||
|
|
||||||
|
|
||||||
|
def _sqlite_power(x, y):
|
||||||
|
return x ** y
|
||||||
|
|
|
@ -14,6 +14,7 @@ class ExpressionNode(tree.Node):
|
||||||
SUB = '-'
|
SUB = '-'
|
||||||
MUL = '*'
|
MUL = '*'
|
||||||
DIV = '/'
|
DIV = '/'
|
||||||
|
POW = '^'
|
||||||
MOD = '%%' # This is a quoted % operator - it is quoted
|
MOD = '%%' # This is a quoted % operator - it is quoted
|
||||||
# because it can be used in strings that also
|
# because it can be used in strings that also
|
||||||
# have parameter substitution.
|
# have parameter substitution.
|
||||||
|
@ -85,6 +86,9 @@ class ExpressionNode(tree.Node):
|
||||||
def __mod__(self, other):
|
def __mod__(self, other):
|
||||||
return self._combine(other, self.MOD, False)
|
return self._combine(other, self.MOD, False)
|
||||||
|
|
||||||
|
def __pow__(self, other):
|
||||||
|
return self._combine(other, self.POW, False)
|
||||||
|
|
||||||
def __and__(self, other):
|
def __and__(self, other):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Use .bitand() and .bitor() for bitwise logical operations."
|
"Use .bitand() and .bitor() for bitwise logical operations."
|
||||||
|
@ -119,6 +123,9 @@ class ExpressionNode(tree.Node):
|
||||||
def __rmod__(self, other):
|
def __rmod__(self, other):
|
||||||
return self._combine(other, self.MOD, True)
|
return self._combine(other, self.MOD, True)
|
||||||
|
|
||||||
|
def __rpow__(self, other):
|
||||||
|
return self._combine(other, self.POW, True)
|
||||||
|
|
||||||
def __rand__(self, other):
|
def __rand__(self, other):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Use .bitand() and .bitor() for bitwise logical operations."
|
"Use .bitand() and .bitor() for bitwise logical operations."
|
||||||
|
|
|
@ -112,6 +112,10 @@ As well as addition, Django supports subtraction, multiplication, division,
|
||||||
and modulo arithmetic with ``F()`` objects, using Python constants,
|
and modulo arithmetic with ``F()`` objects, using Python constants,
|
||||||
variables, and even other ``F()`` objects.
|
variables, and even other ``F()`` objects.
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
The power operator ``**`` is also supported.
|
||||||
|
|
||||||
``Q()`` objects
|
``Q()`` objects
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
|
|
@ -346,6 +346,9 @@ Models
|
||||||
:attr:`~django.db.models.ForeignKey.related_name` to
|
:attr:`~django.db.models.ForeignKey.related_name` to
|
||||||
`'+'` or ending it with `'+'`.
|
`'+'` or ending it with `'+'`.
|
||||||
|
|
||||||
|
* :class:`F expressions <django.db.models.F>` support the power operator
|
||||||
|
(``**``).
|
||||||
|
|
||||||
Signals
|
Signals
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -610,12 +610,16 @@ and use that ``F()`` object in the query::
|
||||||
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
|
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
|
||||||
|
|
||||||
Django supports the use of addition, subtraction, multiplication,
|
Django supports the use of addition, subtraction, multiplication,
|
||||||
division and modulo arithmetic with ``F()`` objects, both with constants
|
division, modulo, and power arithmetic with ``F()`` objects, both with constants
|
||||||
and with other ``F()`` objects. To find all the blog entries with more than
|
and with other ``F()`` objects. To find all the blog entries with more than
|
||||||
*twice* as many comments as pingbacks, we modify the query::
|
*twice* as many comments as pingbacks, we modify the query::
|
||||||
|
|
||||||
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
|
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
The power operator ``**`` was added.
|
||||||
|
|
||||||
To find all the entries where the rating of the entry is less than the
|
To find all the entries where the rating of the entry is less than the
|
||||||
sum of the pingback count and comment count, we would issue the
|
sum of the pingback count and comment count, we would issue the
|
||||||
query::
|
query::
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.db import models
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Number(models.Model):
|
class Number(models.Model):
|
||||||
integer = models.IntegerField(db_column='the_integer')
|
integer = models.BigIntegerField(db_column='the_integer')
|
||||||
float = models.FloatField(null=True, db_column='the_float')
|
float = models.FloatField(null=True, db_column='the_float')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -145,6 +145,13 @@ class ExpressionOperatorTests(TestCase):
|
||||||
self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 58)
|
self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 58)
|
||||||
self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
|
self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
|
||||||
|
|
||||||
|
def test_lefthand_power(self):
|
||||||
|
# LH Powert arithmetic operation on floats and integers
|
||||||
|
Number.objects.filter(pk=self.n.pk).update(integer=F('integer') ** 2,
|
||||||
|
float=F('float') ** 1.5)
|
||||||
|
self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 1764)
|
||||||
|
self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(61.02, places=2))
|
||||||
|
|
||||||
def test_right_hand_addition(self):
|
def test_right_hand_addition(self):
|
||||||
# Right hand operators
|
# Right hand operators
|
||||||
Number.objects.filter(pk=self.n.pk).update(integer=15 + F('integer'),
|
Number.objects.filter(pk=self.n.pk).update(integer=15 + F('integer'),
|
||||||
|
@ -185,6 +192,13 @@ class ExpressionOperatorTests(TestCase):
|
||||||
self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 27)
|
self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 27)
|
||||||
self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
|
self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3))
|
||||||
|
|
||||||
|
def test_righthand_power(self):
|
||||||
|
# RH Powert arithmetic operation on floats and integers
|
||||||
|
Number.objects.filter(pk=self.n.pk).update(integer=2 ** F('integer'),
|
||||||
|
float=1.5 ** F('float'))
|
||||||
|
self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 4398046511104)
|
||||||
|
self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(536.308, places=3))
|
||||||
|
|
||||||
|
|
||||||
class FTimeDeltaTests(TestCase):
|
class FTimeDeltaTests(TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue