Fixed #31396 -- Added binary XOR operator to F expressions.
This commit is contained in:
parent
39e1c88de6
commit
f3da09df0f
|
@ -240,7 +240,8 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
return 'POW(%s)' % ','.join(sub_expressions)
|
return 'POW(%s)' % ','.join(sub_expressions)
|
||||||
# Convert the result to a signed integer since MySQL's binary operators
|
# Convert the result to a signed integer since MySQL's binary operators
|
||||||
# return an unsigned integer.
|
# return an unsigned integer.
|
||||||
elif connector in ('&', '|', '<<'):
|
elif connector in ('&', '|', '<<', '#'):
|
||||||
|
connector = '^' if connector == '#' else connector
|
||||||
return 'CONVERT(%s, SIGNED)' % connector.join(sub_expressions)
|
return 'CONVERT(%s, SIGNED)' % connector.join(sub_expressions)
|
||||||
elif connector == '>>':
|
elif connector == '>>':
|
||||||
lhs, rhs = sub_expressions
|
lhs, rhs = sub_expressions
|
||||||
|
|
|
@ -3,7 +3,7 @@ import uuid
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import DatabaseError
|
from django.db import DatabaseError, NotSupportedError
|
||||||
from django.db.backends.base.operations import BaseDatabaseOperations
|
from django.db.backends.base.operations import BaseDatabaseOperations
|
||||||
from django.db.backends.utils import strip_quotes, truncate_name
|
from django.db.backends.utils import strip_quotes, truncate_name
|
||||||
from django.db.models import AutoField, Exists, ExpressionWrapper
|
from django.db.models import AutoField, Exists, ExpressionWrapper
|
||||||
|
@ -575,6 +575,8 @@ END;
|
||||||
return 'FLOOR(%(lhs)s / POWER(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs}
|
return 'FLOOR(%(lhs)s / POWER(2, %(rhs)s))' % {'lhs': lhs, 'rhs': rhs}
|
||||||
elif connector == '^':
|
elif connector == '^':
|
||||||
return 'POWER(%s)' % ','.join(sub_expressions)
|
return 'POWER(%s)' % ','.join(sub_expressions)
|
||||||
|
elif connector == '#':
|
||||||
|
raise NotSupportedError('Bitwise XOR is not supported in Oracle.')
|
||||||
return super().combine_expression(connector, sub_expressions)
|
return super().combine_expression(connector, sub_expressions)
|
||||||
|
|
||||||
def _get_no_autofield_sequence_name(self, table):
|
def _get_no_autofield_sequence_name(self, table):
|
||||||
|
|
|
@ -218,6 +218,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
conn.create_function('ASIN', 1, none_guard(math.asin))
|
conn.create_function('ASIN', 1, none_guard(math.asin))
|
||||||
conn.create_function('ATAN', 1, none_guard(math.atan))
|
conn.create_function('ATAN', 1, none_guard(math.atan))
|
||||||
conn.create_function('ATAN2', 2, none_guard(math.atan2))
|
conn.create_function('ATAN2', 2, none_guard(math.atan2))
|
||||||
|
conn.create_function('BITXOR', 2, none_guard(operator.xor))
|
||||||
conn.create_function('CEILING', 1, none_guard(math.ceil))
|
conn.create_function('CEILING', 1, none_guard(math.ceil))
|
||||||
conn.create_function('COS', 1, none_guard(math.cos))
|
conn.create_function('COS', 1, none_guard(math.cos))
|
||||||
conn.create_function('COT', 1, none_guard(lambda x: 1 / math.tan(x)))
|
conn.create_function('COT', 1, none_guard(lambda x: 1 / math.tan(x)))
|
||||||
|
|
|
@ -312,6 +312,8 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
# function that's registered in connect().
|
# function that's registered in connect().
|
||||||
if connector == '^':
|
if connector == '^':
|
||||||
return 'POWER(%s)' % ','.join(sub_expressions)
|
return 'POWER(%s)' % ','.join(sub_expressions)
|
||||||
|
elif connector == '#':
|
||||||
|
return 'BITXOR(%s)' % ','.join(sub_expressions)
|
||||||
return super().combine_expression(connector, sub_expressions)
|
return super().combine_expression(connector, sub_expressions)
|
||||||
|
|
||||||
def combine_duration_expression(self, connector, sub_expressions):
|
def combine_duration_expression(self, connector, sub_expressions):
|
||||||
|
|
|
@ -51,6 +51,7 @@ class Combinable:
|
||||||
BITOR = '|'
|
BITOR = '|'
|
||||||
BITLEFTSHIFT = '<<'
|
BITLEFTSHIFT = '<<'
|
||||||
BITRIGHTSHIFT = '>>'
|
BITRIGHTSHIFT = '>>'
|
||||||
|
BITXOR = '#'
|
||||||
|
|
||||||
def _combine(self, other, connector, reversed):
|
def _combine(self, other, connector, reversed):
|
||||||
if not hasattr(other, 'resolve_expression'):
|
if not hasattr(other, 'resolve_expression'):
|
||||||
|
@ -105,6 +106,9 @@ class Combinable:
|
||||||
def bitrightshift(self, other):
|
def bitrightshift(self, other):
|
||||||
return self._combine(other, self.BITRIGHTSHIFT, False)
|
return self._combine(other, self.BITRIGHTSHIFT, False)
|
||||||
|
|
||||||
|
def bitxor(self, other):
|
||||||
|
return self._combine(other, self.BITXOR, False)
|
||||||
|
|
||||||
def __or__(self, other):
|
def __or__(self, other):
|
||||||
if getattr(self, 'conditional', False) and getattr(other, 'conditional', False):
|
if getattr(self, 'conditional', False) and getattr(other, 'conditional', False):
|
||||||
return Q(self) | Q(other)
|
return Q(self) | Q(other)
|
||||||
|
|
|
@ -338,6 +338,9 @@ Models
|
||||||
* The new ``is_dst`` parameter of the :meth:`.QuerySet.datetimes` determines
|
* The new ``is_dst`` parameter of the :meth:`.QuerySet.datetimes` determines
|
||||||
the treatment of nonexistent and ambiguous datetimes.
|
the treatment of nonexistent and ambiguous datetimes.
|
||||||
|
|
||||||
|
* The new :class:`~django.db.models.F` expression ``bitxor()`` method allows
|
||||||
|
:ref:`bitwise XOR operation <using-f-expressions-in-filters>`.
|
||||||
|
|
||||||
Pagination
|
Pagination
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -656,10 +656,18 @@ that were modified more than 3 days after they were published::
|
||||||
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
|
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
|
||||||
|
|
||||||
The ``F()`` objects support bitwise operations by ``.bitand()``, ``.bitor()``,
|
The ``F()`` objects support bitwise operations by ``.bitand()``, ``.bitor()``,
|
||||||
``.bitrightshift()``, and ``.bitleftshift()``. For example::
|
``.bitxor()``, ``.bitrightshift()``, and ``.bitleftshift()``. For example::
|
||||||
|
|
||||||
>>> F('somefield').bitand(16)
|
>>> F('somefield').bitand(16)
|
||||||
|
|
||||||
|
.. admonition:: Oracle
|
||||||
|
|
||||||
|
Oracle doesn't support bitwise XOR operation.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.1
|
||||||
|
|
||||||
|
Support for ``.bitxor()`` was added.
|
||||||
|
|
||||||
The ``pk`` lookup shortcut
|
The ``pk`` lookup shortcut
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from copy import deepcopy
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.db import DatabaseError, connection
|
from django.db import DatabaseError, NotSupportedError, connection
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Avg, BooleanField, Case, CharField, Count, DateField, DateTimeField,
|
Avg, BooleanField, Case, CharField, Count, DateField, DateTimeField,
|
||||||
DurationField, Exists, Expression, ExpressionList, ExpressionWrapper, F,
|
DurationField, Exists, Expression, ExpressionList, ExpressionWrapper, F,
|
||||||
|
@ -1163,6 +1163,25 @@ class ExpressionOperatorTests(TestCase):
|
||||||
self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 1764)
|
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))
|
self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(61.02, places=2))
|
||||||
|
|
||||||
|
@unittest.skipIf(connection.vendor == 'oracle', "Oracle doesn't support bitwise XOR.")
|
||||||
|
def test_lefthand_bitwise_xor(self):
|
||||||
|
Number.objects.update(integer=F('integer').bitxor(48))
|
||||||
|
self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 26)
|
||||||
|
self.assertEqual(Number.objects.get(pk=self.n1.pk).integer, -26)
|
||||||
|
|
||||||
|
@unittest.skipIf(connection.vendor == 'oracle', "Oracle doesn't support bitwise XOR.")
|
||||||
|
def test_lefthand_bitwise_xor_null(self):
|
||||||
|
employee = Employee.objects.create(firstname='John', lastname='Doe')
|
||||||
|
Employee.objects.update(salary=F('salary').bitxor(48))
|
||||||
|
employee.refresh_from_db()
|
||||||
|
self.assertIsNone(employee.salary)
|
||||||
|
|
||||||
|
@unittest.skipUnless(connection.vendor == 'oracle', "Oracle doesn't support bitwise XOR.")
|
||||||
|
def test_lefthand_bitwise_xor_not_supported(self):
|
||||||
|
msg = 'Bitwise XOR is not supported in Oracle.'
|
||||||
|
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||||
|
Number.objects.update(integer=F('integer').bitxor(48))
|
||||||
|
|
||||||
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'), float=42.7 + F('float'))
|
Number.objects.filter(pk=self.n.pk).update(integer=15 + F('integer'), float=42.7 + F('float'))
|
||||||
|
|
Loading…
Reference in New Issue