Forráskód Böngészése

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
Florian Hahn 12 éve
szülő
commit
5240b83462

+ 8 - 0
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'

+ 2 - 0
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):

+ 12 - 0
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

+ 7 - 0
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."

+ 4 - 0
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
 ===============
 

+ 3 - 0
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
 ^^^^^^^
 

+ 5 - 1
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::

+ 1 - 1
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):

+ 14 - 0
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):