Fixed #32961 -- Added BitXor() aggregate to django.contrib.postgres.
This commit is contained in:
parent
000d430234
commit
bd47b9bc81
|
@ -9,7 +9,8 @@ from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
from .mixins import OrderableAggMixin
|
from .mixins import OrderableAggMixin
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ArrayAgg', 'BitAnd', 'BitOr', 'BoolAnd', 'BoolOr', 'JSONBAgg', 'StringAgg',
|
'ArrayAgg', 'BitAnd', 'BitOr', 'BitXor', 'BoolAnd', 'BoolOr', 'JSONBAgg',
|
||||||
|
'StringAgg',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,6 +61,10 @@ class BitOr(Aggregate):
|
||||||
function = 'BIT_OR'
|
function = 'BIT_OR'
|
||||||
|
|
||||||
|
|
||||||
|
class BitXor(Aggregate):
|
||||||
|
function = 'BIT_XOR'
|
||||||
|
|
||||||
|
|
||||||
class BoolAnd(Aggregate):
|
class BoolAnd(Aggregate):
|
||||||
function = 'BOOL_AND'
|
function = 'BOOL_AND'
|
||||||
output_field = BooleanField()
|
output_field = BooleanField()
|
||||||
|
|
|
@ -91,6 +91,11 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
||||||
def is_postgresql_13(self):
|
def is_postgresql_13(self):
|
||||||
return self.connection.pg_version >= 130000
|
return self.connection.pg_version >= 130000
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def is_postgresql_14(self):
|
||||||
|
return self.connection.pg_version >= 140000
|
||||||
|
|
||||||
|
has_bit_xor = property(operator.attrgetter('is_postgresql_14'))
|
||||||
has_websearch_to_tsquery = property(operator.attrgetter('is_postgresql_11'))
|
has_websearch_to_tsquery = property(operator.attrgetter('is_postgresql_11'))
|
||||||
supports_covering_indexes = property(operator.attrgetter('is_postgresql_11'))
|
supports_covering_indexes = property(operator.attrgetter('is_postgresql_11'))
|
||||||
supports_covering_gist_indexes = property(operator.attrgetter('is_postgresql_12'))
|
supports_covering_gist_indexes = property(operator.attrgetter('is_postgresql_12'))
|
||||||
|
|
|
@ -75,6 +75,16 @@ General-purpose aggregation functions
|
||||||
Returns an ``int`` of the bitwise ``OR`` of all non-null input values, or
|
Returns an ``int`` of the bitwise ``OR`` of all non-null input values, or
|
||||||
``default`` if all values are null.
|
``default`` if all values are null.
|
||||||
|
|
||||||
|
``BitXor``
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
.. class:: BitXor(expression, filter=None, default=None, **extra)
|
||||||
|
|
||||||
|
Returns an ``int`` of the bitwise ``XOR`` of all non-null input values, or
|
||||||
|
``default`` if all values are null. It requires PostgreSQL 14+.
|
||||||
|
|
||||||
``BoolAnd``
|
``BoolAnd``
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,9 @@ Minor features
|
||||||
:mod:`django.contrib.postgres`
|
:mod:`django.contrib.postgres`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* The new :class:`BitXor() <django.contrib.postgres.aggregates.BitXor>`
|
||||||
|
aggregate function returns an ``int`` of the bitwise ``XOR`` of all non-null
|
||||||
|
input values.
|
||||||
|
|
||||||
:mod:`django.contrib.redirects`
|
:mod:`django.contrib.redirects`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
from django.db import connection
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
CharField, F, Func, IntegerField, OuterRef, Q, Subquery, Value,
|
CharField, F, Func, IntegerField, OuterRef, Q, Subquery, Value,
|
||||||
)
|
)
|
||||||
from django.db.models.fields.json import KeyTextTransform, KeyTransform
|
from django.db.models.fields.json import KeyTextTransform, KeyTransform
|
||||||
from django.db.models.functions import Cast, Concat, Substr
|
from django.db.models.functions import Cast, Concat, Substr
|
||||||
|
from django.test import skipUnlessDBFeature
|
||||||
from django.test.utils import Approximate, ignore_warnings
|
from django.test.utils import Approximate, ignore_warnings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.deprecation import RemovedInDjango50Warning
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
|
@ -12,9 +14,9 @@ from .models import AggregateTestModel, HotelReservation, Room, StatTestModel
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.contrib.postgres.aggregates import (
|
from django.contrib.postgres.aggregates import (
|
||||||
ArrayAgg, BitAnd, BitOr, BoolAnd, BoolOr, Corr, CovarPop, JSONBAgg,
|
ArrayAgg, BitAnd, BitOr, BitXor, BoolAnd, BoolOr, Corr, CovarPop,
|
||||||
RegrAvgX, RegrAvgY, RegrCount, RegrIntercept, RegrR2, RegrSlope,
|
JSONBAgg, RegrAvgX, RegrAvgY, RegrCount, RegrIntercept, RegrR2,
|
||||||
RegrSXX, RegrSXY, RegrSYY, StatAggregate, StringAgg,
|
RegrSlope, RegrSXX, RegrSXY, RegrSYY, StatAggregate, StringAgg,
|
||||||
)
|
)
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -68,6 +70,8 @@ class TestGeneralAggregate(PostgreSQLTestCase):
|
||||||
(JSONBAgg('integer_field'), []),
|
(JSONBAgg('integer_field'), []),
|
||||||
(StringAgg('char_field', delimiter=';'), ''),
|
(StringAgg('char_field', delimiter=';'), ''),
|
||||||
]
|
]
|
||||||
|
if connection.features.has_bit_xor:
|
||||||
|
tests.append((BitXor('integer_field'), None))
|
||||||
for aggregation, expected_result in tests:
|
for aggregation, expected_result in tests:
|
||||||
with self.subTest(aggregation=aggregation):
|
with self.subTest(aggregation=aggregation):
|
||||||
# Empty result with non-execution optimization.
|
# Empty result with non-execution optimization.
|
||||||
|
@ -96,6 +100,8 @@ class TestGeneralAggregate(PostgreSQLTestCase):
|
||||||
(JSONBAgg('integer_field', default=Value('["<empty>"]')), ['<empty>']),
|
(JSONBAgg('integer_field', default=Value('["<empty>"]')), ['<empty>']),
|
||||||
(StringAgg('char_field', delimiter=';', default=Value('<empty>')), '<empty>'),
|
(StringAgg('char_field', delimiter=';', default=Value('<empty>')), '<empty>'),
|
||||||
]
|
]
|
||||||
|
if connection.features.has_bit_xor:
|
||||||
|
tests.append((BitXor('integer_field', default=0), 0))
|
||||||
for aggregation, expected_result in tests:
|
for aggregation, expected_result in tests:
|
||||||
with self.subTest(aggregation=aggregation):
|
with self.subTest(aggregation=aggregation):
|
||||||
# Empty result with non-execution optimization.
|
# Empty result with non-execution optimization.
|
||||||
|
@ -275,6 +281,28 @@ class TestGeneralAggregate(PostgreSQLTestCase):
|
||||||
integer_field=0).aggregate(bitor=BitOr('integer_field'))
|
integer_field=0).aggregate(bitor=BitOr('integer_field'))
|
||||||
self.assertEqual(values, {'bitor': 0})
|
self.assertEqual(values, {'bitor': 0})
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('has_bit_xor')
|
||||||
|
def test_bit_xor_general(self):
|
||||||
|
AggregateTestModel.objects.create(integer_field=3)
|
||||||
|
values = AggregateTestModel.objects.filter(
|
||||||
|
integer_field__in=[1, 3],
|
||||||
|
).aggregate(bitxor=BitXor('integer_field'))
|
||||||
|
self.assertEqual(values, {'bitxor': 2})
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('has_bit_xor')
|
||||||
|
def test_bit_xor_on_only_true_values(self):
|
||||||
|
values = AggregateTestModel.objects.filter(
|
||||||
|
integer_field=1,
|
||||||
|
).aggregate(bitxor=BitXor('integer_field'))
|
||||||
|
self.assertEqual(values, {'bitxor': 1})
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('has_bit_xor')
|
||||||
|
def test_bit_xor_on_only_false_values(self):
|
||||||
|
values = AggregateTestModel.objects.filter(
|
||||||
|
integer_field=0,
|
||||||
|
).aggregate(bitxor=BitXor('integer_field'))
|
||||||
|
self.assertEqual(values, {'bitxor': 0})
|
||||||
|
|
||||||
def test_bool_and_general(self):
|
def test_bool_and_general(self):
|
||||||
values = AggregateTestModel.objects.aggregate(booland=BoolAnd('boolean_field'))
|
values = AggregateTestModel.objects.aggregate(booland=BoolAnd('boolean_field'))
|
||||||
self.assertEqual(values, {'booland': False})
|
self.assertEqual(values, {'booland': False})
|
||||||
|
|
Loading…
Reference in New Issue