summaryrefslogtreecommitdiff
path: root/tests/expressions
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2025-11-24 06:14:38 -0500
committerGitHub <noreply@github.com>2025-11-24 12:14:38 +0100
commit2a6e0bd72d4a69725b957d6748a4b834f21b12b5 (patch)
treec7d7b694d8a4513ab898055e884783f390a7f5ec /tests/expressions
parent57c50d8c1996733cef45204ea069a2c01b2860cc (diff)
Fixed #36751 -- Fixed empty filtered aggregation crash over annotated queryset.
Regression in b8e5a8a9a2a767f584cbe89a878a42363706f939. Refs #36404. The replace_expressions method was innapropriately dealing with falsey but not None source expressions causing them to also be potentially evaluated when __bool__ was invoked (e.g. QuerySet.__bool__ evaluates the queryset). The changes introduced in b8e5a8a9a2, which were to deal with a similar issue, surfaced the problem as aggregation over an annotated queryset requires an inlining (or pushdown) of aggregate references which is achieved through replace_expressions. In cases where an empty Q object was provided as an aggregate filter, such as when the admin facetting feature was used as reported, it would wrongly be turned into None, instead of an empty WhereNode, causing a crash at aggregate filter compilation. Note that the crash signature differed depending on whether or not the backend natively supports aggregate filtering (supports_aggregate_filter_clause) as the fallback, which makes use Case / When expressions, would result in a TypeError instead of a NoneType AttributeError. Thanks Rafael Urben for the report, Antoliny and Youngkwang Yang for the triage.
Diffstat (limited to 'tests/expressions')
-rw-r--r--tests/expressions/tests.py18
1 files changed, 18 insertions, 0 deletions
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index 6f18321aa7..981d84e9e8 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -1571,6 +1571,24 @@ class SimpleExpressionTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, msg):
expression.get_expression_for_validation()
+ def test_replace_expressions_falsey(self):
+ class AssignableExpression(Expression):
+ def __init__(self, *source_expressions):
+ super().__init__()
+ self.set_source_expressions(list(source_expressions))
+
+ def get_source_expressions(self):
+ return self.source_expressions
+
+ def set_source_expressions(self, exprs):
+ self.source_expressions = exprs
+
+ expression = AssignableExpression()
+ falsey = Q()
+ expression.set_source_expressions([falsey])
+ replaced = expression.replace_expressions({"replacement": Expression()})
+ self.assertEqual(replaced.get_source_expressions(), [falsey])
+
class ExpressionsNumericTests(TestCase):
@classmethod