From 1c12df4aa6c599959d9eb6de2076bf8aa6e301d6 Mon Sep 17 00:00:00 2001 From: anabelensc Date: Mon, 14 Dec 2015 19:13:21 +0000 Subject: [PATCH] Fixed #25912 -- Added binary left/right shift operators to F expressions. Thanks Mariusz Felisiak for review and MySQL advice. --- AUTHORS | 1 + django/db/backends/mysql/operations.py | 5 ++++- django/db/backends/oracle/operations.py | 7 +++++-- django/db/models/expressions.py | 8 ++++++++ docs/releases/1.11.txt | 3 +++ docs/topics/db/queries.txt | 8 ++++++-- tests/expressions/tests.py | 10 ++++++++++ 7 files changed, 37 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9faa62271a..e81431be7d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -42,6 +42,7 @@ answer newbie questions, and generally made Django that much better: Amit Ramon Amit Upadhyay A. Murat Eren + Ana Belen Sarabia Ana Krivokapic Andi Albrecht André Ericson diff --git a/django/db/backends/mysql/operations.py b/django/db/backends/mysql/operations.py index a9c39b5363..43c39441c2 100644 --- a/django/db/backends/mysql/operations.py +++ b/django/db/backends/mysql/operations.py @@ -203,8 +203,11 @@ class DatabaseOperations(BaseDatabaseOperations): return 'POW(%s)' % ','.join(sub_expressions) # Convert the result to a signed integer since MySQL's binary operators # return an unsigned integer. - elif connector in ('&', '|'): + elif connector in ('&', '|', '<<'): return 'CONVERT(%s, SIGNED)' % connector.join(sub_expressions) + elif connector == '>>': + lhs, rhs = sub_expressions + return 'FLOOR(%(lhs)s / POW(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs} return super(DatabaseOperations, self).combine_expression(connector, sub_expressions) def get_db_converters(self, expression): diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index e283c574f2..74f05d5abe 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -436,14 +436,17 @@ WHEN (new.%(col_name)s IS NULL) value.second, value.microsecond) def combine_expression(self, connector, sub_expressions): - "Oracle requires special cases for %% and & operators in query expressions" + lhs, rhs = sub_expressions if connector == '%%': return 'MOD(%s)' % ','.join(sub_expressions) elif connector == '&': return 'BITAND(%s)' % ','.join(sub_expressions) elif connector == '|': - lhs, rhs = sub_expressions return 'BITAND(-%(lhs)s-1,%(rhs)s)+%(lhs)s' % {'lhs': lhs, 'rhs': rhs} + elif connector == '<<': + return '(%(lhs)s * POWER(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs} + elif connector == '>>': + return 'FLOOR(%(lhs)s / POWER(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs} elif connector == '^': return 'POWER(%s)' % ','.join(sub_expressions) return super(DatabaseOperations, self).combine_expression(connector, sub_expressions) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 3add089624..1ff4cd7735 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -30,6 +30,8 @@ class Combinable(object): # usage. BITAND = '&' BITOR = '|' + BITLEFTSHIFT = '<<' + BITRIGHTSHIFT = '>>' def _combine(self, other, connector, reversed, node=None): if not hasattr(other, 'resolve_expression'): @@ -76,6 +78,12 @@ class Combinable(object): def bitand(self, other): return self._combine(other, self.BITAND, False) + def bitleftshift(self, other): + return self._combine(other, self.BITLEFTSHIFT, False) + + def bitrightshift(self, other): + return self._combine(other, self.BITRIGHTSHIFT, False) + def __or__(self, other): raise NotImplementedError( "Use .bitand() and .bitor() for bitwise logical operations." diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 9593a5c658..abdfb08a27 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -367,6 +367,9 @@ Models :meth:`~django.db.models.Expression.desc` to control the ordering of null values. +* The new ``F`` expression ``bitleftshift()`` and ``bitrightshift()`` methods + allow :ref:`bitwise shift operations `. + Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 0d69099fa7..f1540cbb96 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -652,11 +652,15 @@ that were modified more than 3 days after they were published:: >>> from datetime import timedelta >>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3)) -The ``F()`` objects support bitwise operations by ``.bitand()`` and -``.bitor()``, for example:: +The ``F()`` objects support bitwise operations by ``.bitand()``, ``.bitor()``, +``.bitrightshift()``, and ``.bitleftshift()``. For example:: >>> F('somefield').bitand(16) +.. versionchanged:: 1.11 + + Support for ``.bitrightshift()`` and ``.bitleftshift()`` was added. + The ``pk`` lookup shortcut -------------------------- diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index ff9f7278c5..18d003d57d 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -745,6 +745,16 @@ class ExpressionOperatorTests(TestCase): self.assertEqual(Number.objects.get(pk=self.n1.pk).integer, -64) self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3)) + def test_lefthand_bitwise_left_shift_operator(self): + Number.objects.update(integer=F('integer').bitleftshift(2)) + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 168) + self.assertEqual(Number.objects.get(pk=self.n1.pk).integer, -168) + + def test_lefthand_bitwise_right_shift_operator(self): + Number.objects.update(integer=F('integer').bitrightshift(2)) + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 10) + self.assertEqual(Number.objects.get(pk=self.n1.pk).integer, -11) + def test_lefthand_bitwise_or(self): # LH Bitwise or on integers Number.objects.update(integer=F('integer').bitor(48))