Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions django/contrib/postgres/aggregates/general.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import warnings

from django.contrib.postgres.fields import ArrayField
from django.db.models import Aggregate, BooleanField, JSONField
from django.db.models import Aggregate
from django.db.models import BitAnd as _BitAnd
from django.db.models import BitOr as _BitOr
from django.db.models import BitXor as _BitXor
from django.db.models import BooleanField, JSONField
from django.db.models import StringAgg as _StringAgg
from django.db.models import Value
from django.utils.deprecation import RemovedInDjango70Warning

__all__ = [
"ArrayAgg",
"BitAnd",
"BitOr",
"BitXor",
"BitAnd", # RemovedInDjango70Warning
"BitOr", # RemovedInDjango70Warning
"BitXor", # RemovedInDjango70Warning
"BoolAnd",
"BoolOr",
"JSONBAgg",
Expand All @@ -28,16 +32,37 @@ def output_field(self):
return ArrayField(self.source_expressions[0].output_field)


class BitAnd(Aggregate):
function = "BIT_AND"
class BitAnd(_BitAnd):
def __init__(self, expression, **extra):
warnings.warn(
"The PostgreSQL-specific BitAnd function is deprecated. Use "
"django.db.models.aggregates.BitAnd instead.",
category=RemovedInDjango70Warning,
stacklevel=2,
)
super().__init__(expression, **extra)


class BitOr(Aggregate):
function = "BIT_OR"
class BitOr(_BitOr):
def __init__(self, expression, **extra):
warnings.warn(
"The PostgreSQL-specific BitOr function is deprecated. Use "
"django.db.models.aggregates.BitOr instead.",
category=RemovedInDjango70Warning,
stacklevel=2,
)
super().__init__(expression, **extra)


class BitXor(Aggregate):
function = "BIT_XOR"
class BitXor(_BitXor):
def __init__(self, expression, **extra):
warnings.warn(
"The PostgreSQL-specific BitXor function is deprecated. Use "
"django.db.models.aggregates.BitXor instead.",
category=RemovedInDjango70Warning,
stacklevel=2,
)
super().__init__(expression, **extra)


class BoolAnd(Aggregate):
Expand Down
6 changes: 6 additions & 0 deletions django/db/backends/base/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,12 @@ class BaseDatabaseFeatures:
# Does the database support SQL 2023 ANY_VALUE in GROUP BY?
supports_any_value = False

# Does the database support bitwise aggregations: BIT_AND, BIT_OR, and
# BIT_XOR?
supports_bit_aggregations = True
# Does the backend support the default parameter in bitwise aggregations?
supports_default_in_bit_aggregations = True

# Does the backend support indexing a TextField?
supports_index_on_text_field = True

Expand Down
1 change: 1 addition & 0 deletions django/db/backends/mysql/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_over_clause = True
supports_frame_range_fixed_distance = True
supports_update_conflicts = True
supports_default_in_bit_aggregations = False
can_rename_index = True
delete_can_self_reference_subquery = False
create_test_procedure_without_params_sql = """
Expand Down
5 changes: 5 additions & 0 deletions django/db/backends/oracle/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_collation_on_textfield = False
supports_on_delete_db_default = False
supports_no_precision_decimalfield = True
supports_default_in_bit_aggregations = False
test_now_utc_template = "CURRENT_TIMESTAMP AT TIME ZONE 'UTC'"
django_test_expected_failures = {
# A bug in Django/oracledb with respect to string handling (#23843).
Expand Down Expand Up @@ -240,3 +241,7 @@ def supports_uuid4_function(self):
@cached_property
def supports_stored_generated_columns(self):
return self.connection.oracle_version >= (23, 7)

@cached_property
def supports_bit_aggregations(self):
return self.connection.oracle_version >= (21,)
28 changes: 28 additions & 0 deletions django/db/backends/sqlite3/_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import functools
import operator
import random
import statistics
import zoneinfo
Expand Down Expand Up @@ -86,6 +87,9 @@ def register(connection):
connection.create_aggregate("VAR_POP", 1, VarPop)
connection.create_aggregate("VAR_SAMP", 1, VarSamp)
connection.create_aggregate("ANY_VALUE", 1, AnyValue)
connection.create_aggregate("BIT_AND", 1, BitAnd)
connection.create_aggregate("BIT_OR", 1, BitOr)
connection.create_aggregate("BIT_XOR", 1, BitXor)
connection.create_function("UUIDV4", 0, _sqlite_uuid4)
if PY314:
connection.create_function("UUIDV7", 0, _sqlite_uuid7)
Expand Down Expand Up @@ -535,3 +539,27 @@ class VarSamp(ListAggregate):
class AnyValue(ListAggregate):
def finalize(self):
return self[0]


class BitAggregate(ListAggregate):
bit_operator = None

def finalize(self):
items = (item for item in self if item is not None)
try:
return functools.reduce(self.bit_operator, items)
except TypeError:
# items may be an empty iterator when all elements are None.
return None


class BitAnd(BitAggregate):
bit_operator = operator.and_


class BitOr(BitAggregate):
bit_operator = operator.or_


class BitXor(BitAggregate):
bit_operator = operator.xor
51 changes: 51 additions & 0 deletions django/db/models/aggregates.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"Aggregate",
"AnyValue",
"Avg",
"BitAnd",
"BitOr",
"BitXor",
"Count",
"Max",
"Min",
Expand Down Expand Up @@ -241,6 +244,54 @@ class Avg(FixDurationInputMixin, NumericOutputFieldMixin, Aggregate):
arity = 1


class BitAggregate(Aggregate):
arity = 1

def __init__(self, expression, **extra):
super().__init__(expression, **extra)
# self.default is reset in Aggregate.resolve_expression(). Store the
# information for the later check in as_sql().
self._has_default = self.default is not None

def as_sql(self, compiler, connection, **extra_context):
if not connection.features.supports_bit_aggregations:
raise NotSupportedError(
f"{self.name} is not supported on {connection.vendor}."
)
if (
self._has_default
and not connection.features.supports_default_in_bit_aggregations
):
raise NotSupportedError(
f"{self.name} does not support the default parameter on "
f"{connection.vendor}."
)
return super().as_sql(compiler, connection, **extra_context)

def as_oracle(self, compiler, connection, **extra_context):
return self.as_sql(
compiler,
connection,
function=f"{self.function}_AGG",
**extra_context,
)


class BitAnd(BitAggregate):
function = "BIT_AND"
name = "BitAnd"


class BitOr(BitAggregate):
function = "BIT_OR"
name = "BitOr"


class BitXor(BitAggregate):
function = "BIT_XOR"
name = "BitXor"


class Count(Aggregate):
function = "COUNT"
name = "Count"
Expand Down
4 changes: 4 additions & 0 deletions docs/internals/deprecation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ details on these changes.

* The ``SQLCompiler.quote_name_unless_alias()`` method will be removed.

* The ``django.contrib.postgres.aggregates.BitAnd``,
``django.contrib.postgres.aggregates.BitOr``, and
``django.contrib.postgres.aggregates.BitXor`` classes will be removed.

.. _deprecation-removed-in-6.1:

6.1
Expand Down
15 changes: 15 additions & 0 deletions docs/ref/contrib/postgres/aggregates.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ General-purpose aggregation functions
Returns an ``int`` of the bitwise ``AND`` of all non-null input values, or
``default`` if all values are null.

.. deprecated:: 6.1

This class is deprecated in favor of the generally available
:class:`~django.db.models.BitAnd` class.

``BitOr``
---------

Expand All @@ -70,6 +75,11 @@ General-purpose aggregation functions
Returns an ``int`` of the bitwise ``OR`` of all non-null input values, or
``default`` if all values are null.

.. deprecated:: 6.1

This class is deprecated in favor of the generally available
:class:`~django.db.models.BitOr` class.

``BitXor``
----------

Expand All @@ -78,6 +88,11 @@ General-purpose aggregation functions
Returns an ``int`` of the bitwise ``XOR`` of all non-null input values, or
``default`` if all values are null.

.. deprecated:: 6.1

This class is deprecated in favor of the generally available
:class:`~django.db.models.BitXor` class.

``BoolAnd``
-----------

Expand Down
36 changes: 36 additions & 0 deletions docs/ref/models/querysets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4085,6 +4085,42 @@ by the aggregate.
unique values. This is the SQL equivalent of ``AVG(DISTINCT <field>)``.
The default value is ``False``.

``BitAnd``
~~~~~~~~~~

.. class:: BitAnd(expression, filter=None, default=None, **extra)

.. versionadded:: 6.1

Returns an ``int`` of the bitwise ``AND`` of all non-null input values, or
``default`` if all values are null.

The ``default`` parameter is not supported on MariaDB, MySQL, and Oracle.

``BitOr``
~~~~~~~~~

.. class:: BitOr(expression, filter=None, default=None, **extra)

.. versionadded:: 6.1

Returns an ``int`` of the bitwise ``OR`` of all non-null input values, or
``default`` if all values are null.

The ``default`` parameter is not supported on MariaDB, MySQL, and Oracle.

``BitXor``
~~~~~~~~~~

.. class:: BitXor(expression, filter=None, default=None, **extra)

.. versionadded:: 6.1

Returns an ``int`` of the bitwise ``XOR`` of all non-null input values, or
``default`` if all values are null.

The ``default`` parameter is not supported on MariaDB, MySQL, and Oracle.

``Count``
~~~~~~~~~

Expand Down
14 changes: 14 additions & 0 deletions docs/releases/6.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@ Models
:class:`~django.db.models.query.QuerySet` is ordered and the ordering is
deterministic.

* The new :class:`~django.db.models.BitAnd`, :class:`~django.db.models.BitOr`,
and :class:`~django.db.models.BitXor` aggregates return the bitwise ``AND``,
``OR``, ``XOR``, respectively. These aggregates were previously included only
in ``contrib.postgres``.

Pagination
~~~~~~~~~~

Expand Down Expand Up @@ -420,6 +425,9 @@ backends.
``get_geom_placeholder_sql`` and is expected to return a two-elements tuple
composed of an SQL format string and a tuple of associated parameters.

* Set the new ``DatabaseFeatures.supports_bit_aggregations`` attribute to
``False`` if the database doesn't support bitwise aggregations.

:mod:`django.contrib.admin`
---------------------------

Expand Down Expand Up @@ -539,6 +547,12 @@ Miscellaneous
:ref:`expressions <writing-your-own-query-expressions>`, is deprecated in
favor of the newly introduced ``quote_name()`` method.

* The ``BitAnd``, ``BitOr``, and ``BitXor`` classes in
``django.contrib.postgres.aggregates`` are deprecated in favor of the
generally available :class:`~django.db.models.BitAnd`,
:class:`~django.db.models.BitOr`, and :class:`~django.db.models.BitXor`
classes.

Features removed in 6.1
=======================

Expand Down
2 changes: 1 addition & 1 deletion tests/aggregation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __str__(self):

class Publisher(models.Model):
name = models.CharField(max_length=255)
num_awards = models.IntegerField()
num_awards = models.IntegerField(null=True)
duration = models.DurationField(blank=True, null=True)

def __str__(self):
Expand Down
Loading
Loading