summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Hahn <flo@fhahn.com>2013-02-21 23:02:18 +0100
committerTim Graham <timograham@gmail.com>2013-10-22 10:29:57 -0400
commit5240b83462ba4887b67da8d493dfdc23fec5df97 (patch)
tree7bae13c499bc84b5f4bcf79c592aa2a9b8008033
parent1597503a017a9ced422a81d314cb4097d53c3dfd (diff)
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
-rw-r--r--django/db/backends/mysql/base.py8
-rw-r--r--django/db/backends/oracle/base.py2
-rw-r--r--django/db/backends/sqlite3/base.py12
-rw-r--r--django/db/models/expressions.py7
-rw-r--r--docs/ref/models/queries.txt4
-rw-r--r--docs/releases/1.7.txt3
-rw-r--r--docs/topics/db/queries.txt6
-rw-r--r--tests/expressions_regress/models.py2
-rw-r--r--tests/expressions_regress/tests.py14
9 files changed, 56 insertions, 2 deletions
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 <django.db.models.F>` 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):