diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 1e3cdbad9b..84bbf1569f 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -386,6 +386,14 @@ class DatabaseOperations(BaseDatabaseOperations): items_sql = "(%s)" % ", ".join(["%s"] * len(fields)) 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): vendor = 'mysql' diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index a94d95e1ea..6a6a877b46 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -482,6 +482,8 @@ WHEN (new.%(col_name)s IS NULL) return 'BITAND(%s)' % ','.join(sub_expressions) elif connector == '|': 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) def _get_sequence_name(self, table): diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index ef214234e9..5d07f68f73 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -304,6 +304,13 @@ class DatabaseOperations(BaseDatabaseOperations): res.extend(["UNION ALL SELECT %s" % ", ".join(["%s"] * len(fields))] * (num_values - 1)) 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): vendor = 'sqlite' @@ -376,6 +383,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): conn.create_function("django_datetime_trunc", 3, _sqlite_datetime_trunc) conn.create_function("regexp", 2, _sqlite_regexp) conn.create_function("django_format_dtdelta", 5, _sqlite_format_dtdelta) + conn.create_function("django_power", 2, _sqlite_power) return conn 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): 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 diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 2fa55b2c14..38b656162d 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -14,6 +14,7 @@ class ExpressionNode(tree.Node): SUB = '-' MUL = '*' DIV = '/' + POW = '^' MOD = '%%' # This is a quoted % operator - it is quoted # because it can be used in strings that also # have parameter substitution. @@ -85,6 +86,9 @@ class ExpressionNode(tree.Node): def __mod__(self, other): return self._combine(other, self.MOD, False) + def __pow__(self, other): + return self._combine(other, self.POW, False) + def __and__(self, other): raise NotImplementedError( "Use .bitand() and .bitor() for bitwise logical operations." @@ -119,6 +123,9 @@ class ExpressionNode(tree.Node): def __rmod__(self, other): return self._combine(other, self.MOD, True) + def __rpow__(self, other): + return self._combine(other, self.POW, True) + def __rand__(self, other): raise NotImplementedError( "Use .bitand() and .bitor() for bitwise logical operations." diff --git a/docs/ref/models/queries.txt b/docs/ref/models/queries.txt index ebc5142b30..bf7480ef69 100644 --- a/docs/ref/models/queries.txt +++ b/docs/ref/models/queries.txt @@ -112,6 +112,10 @@ As well as addition, Django supports subtraction, multiplication, division, and modulo arithmetic with ``F()`` objects, using Python constants, variables, and even other ``F()`` objects. +.. versionadded:: 1.7 + + The power operator ``**`` is also supported. + ``Q()`` objects =============== diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 699566f1d3..542efc46b9 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -346,6 +346,9 @@ Models :attr:`~django.db.models.ForeignKey.related_name` to `'+'` or ending it with `'+'`. +* :class:`F expressions ` support the power operator + (``**``). + Signals ^^^^^^^ diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 1180022d6e..7b38dbaea3 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -610,12 +610,16 @@ and use that ``F()`` object in the query:: >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks')) 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 *twice* as many comments as pingbacks, we modify the query:: >>> 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 sum of the pingback count and comment count, we would issue the query:: diff --git a/tests/expressions_regress/models.py b/tests/expressions_regress/models.py index 4679dcd567..bd3e52f54d 100644 --- a/tests/expressions_regress/models.py +++ b/tests/expressions_regress/models.py @@ -8,7 +8,7 @@ from django.db import models @python_2_unicode_compatible 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') def __str__(self): diff --git a/tests/expressions_regress/tests.py b/tests/expressions_regress/tests.py index 6d96e0cd7e..470ab7c8e5 100644 --- a/tests/expressions_regress/tests.py +++ b/tests/expressions_regress/tests.py @@ -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).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): # Right hand operators 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).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):