mirror of
https://github.com/django/django.git
synced 2025-03-12 18:30:48 +00:00
[5.2.x] Fixed #35235 -- Removed caching of BaseExpression._output_field_or_none.
Backport of cbb0812683cf3236e4a4003bf7f74b119d3cde0c from main.
This commit is contained in:
parent
d567e3a52e
commit
77f9e6bcd3
@ -167,13 +167,16 @@ class Combinable:
|
|||||||
return NegatedExpression(self)
|
return NegatedExpression(self)
|
||||||
|
|
||||||
|
|
||||||
|
class OutputFieldIsNoneError(FieldError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BaseExpression:
|
class BaseExpression:
|
||||||
"""Base class for all query expressions."""
|
"""Base class for all query expressions."""
|
||||||
|
|
||||||
empty_result_set_value = NotImplemented
|
empty_result_set_value = NotImplemented
|
||||||
# aggregate specific fields
|
# aggregate specific fields
|
||||||
is_summary = False
|
is_summary = False
|
||||||
_output_field_resolved_to_none = False
|
|
||||||
# Can the expression be used in a WHERE clause?
|
# Can the expression be used in a WHERE clause?
|
||||||
filterable = True
|
filterable = True
|
||||||
# Can the expression be used as a source expression in Window?
|
# Can the expression be used as a source expression in Window?
|
||||||
@ -323,11 +326,12 @@ class BaseExpression:
|
|||||||
"""Return the output type of this expressions."""
|
"""Return the output type of this expressions."""
|
||||||
output_field = self._resolve_output_field()
|
output_field = self._resolve_output_field()
|
||||||
if output_field is None:
|
if output_field is None:
|
||||||
self._output_field_resolved_to_none = True
|
raise OutputFieldIsNoneError(
|
||||||
raise FieldError("Cannot resolve expression type, unknown output_field")
|
"Cannot resolve expression type, unknown output_field"
|
||||||
|
)
|
||||||
return output_field
|
return output_field
|
||||||
|
|
||||||
@cached_property
|
@property
|
||||||
def _output_field_or_none(self):
|
def _output_field_or_none(self):
|
||||||
"""
|
"""
|
||||||
Return the output field of this expression, or None if
|
Return the output field of this expression, or None if
|
||||||
@ -335,9 +339,8 @@ class BaseExpression:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.output_field
|
return self.output_field
|
||||||
except FieldError:
|
except OutputFieldIsNoneError:
|
||||||
if not self._output_field_resolved_to_none:
|
return
|
||||||
raise
|
|
||||||
|
|
||||||
def _resolve_output_field(self):
|
def _resolve_output_field(self):
|
||||||
"""
|
"""
|
||||||
|
@ -51,6 +51,7 @@ from django.db.models.expressions import (
|
|||||||
Combinable,
|
Combinable,
|
||||||
CombinedExpression,
|
CombinedExpression,
|
||||||
NegatedExpression,
|
NegatedExpression,
|
||||||
|
OutputFieldIsNoneError,
|
||||||
RawSQL,
|
RawSQL,
|
||||||
Ref,
|
Ref,
|
||||||
)
|
)
|
||||||
@ -2329,6 +2330,16 @@ class ValueTests(TestCase):
|
|||||||
time = Time.objects.annotate(one=Value(1, output_field=DecimalField())).first()
|
time = Time.objects.annotate(one=Value(1, output_field=DecimalField())).first()
|
||||||
self.assertEqual(time.one, 1)
|
self.assertEqual(time.one, 1)
|
||||||
|
|
||||||
|
def test_output_field_is_none_error(self):
|
||||||
|
with self.assertRaises(OutputFieldIsNoneError):
|
||||||
|
Employee.objects.annotate(custom_expression=Value(None)).first()
|
||||||
|
|
||||||
|
def test_output_field_or_none_property_not_cached(self):
|
||||||
|
expression = Value(None, output_field=None)
|
||||||
|
self.assertIsNone(expression._output_field_or_none)
|
||||||
|
expression.output_field = BooleanField()
|
||||||
|
self.assertIsInstance(expression._output_field_or_none, BooleanField)
|
||||||
|
|
||||||
def test_resolve_output_field(self):
|
def test_resolve_output_field(self):
|
||||||
value_types = [
|
value_types = [
|
||||||
("str", CharField),
|
("str", CharField),
|
||||||
|
@ -334,6 +334,22 @@ class TestGeneralAggregate(PostgreSQLTestCase):
|
|||||||
)
|
)
|
||||||
self.assertCountEqual(qs, [[], [5]])
|
self.assertCountEqual(qs, [[], [5]])
|
||||||
|
|
||||||
|
def test_array_agg_with_empty_filter_and_default_values(self):
|
||||||
|
for filter_value in ([-1], []):
|
||||||
|
for default_value in ([], Value([])):
|
||||||
|
with self.subTest(filter=filter_value, default=default_value):
|
||||||
|
queryset = AggregateTestModel.objects.annotate(
|
||||||
|
test_array_agg=ArrayAgg(
|
||||||
|
"stattestmodel__int1",
|
||||||
|
filter=Q(pk__in=filter_value),
|
||||||
|
default=default_value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
queryset.values_list("test_array_agg", flat=True),
|
||||||
|
[[], [], [], []],
|
||||||
|
)
|
||||||
|
|
||||||
def test_bit_and_general(self):
|
def test_bit_and_general(self):
|
||||||
values = AggregateTestModel.objects.filter(integer_field__in=[0, 1]).aggregate(
|
values = AggregateTestModel.objects.filter(integer_field__in=[0, 1]).aggregate(
|
||||||
bitand=BitAnd("integer_field")
|
bitand=BitAnd("integer_field")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user