Kaydet (Commit) 5240b834 authored tarafından Florian Hahn's avatar Florian Hahn Kaydeden (comit) Tim Graham

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
üst 1597503a
...@@ -386,6 +386,14 @@ class DatabaseOperations(BaseDatabaseOperations): ...@@ -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) ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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, ...@@ -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 ...@@ -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:: ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment