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:
Florian Hahn 2013-02-21 23:02:18 +01:00 committed by Tim Graham
parent 1597503a01
commit 5240b83462
9 changed files with 56 additions and 2 deletions

View File

@ -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'

View File

@ -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):

View File

@ -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

View File

@ -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."

View File

@ -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
=============== ===============

View File

@ -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
^^^^^^^ ^^^^^^^

View File

@ -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::

View File

@ -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):

View File

@ -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):