Browse Source

Fixed #35235 -- Removed caching of BaseExpression._output_field_or_none.

sharonwoo 6 months ago
parent
commit
cbb0812683

+ 10 - 7
django/db/models/expressions.py

@@ -167,13 +167,16 @@ class Combinable:
         return NegatedExpression(self)
 
 
+class OutputFieldIsNoneError(FieldError):
+    pass
+
+
 class BaseExpression:
     """Base class for all query expressions."""
 
     empty_result_set_value = NotImplemented
     # aggregate specific fields
     is_summary = False
-    _output_field_resolved_to_none = False
     # Can the expression be used in a WHERE clause?
     filterable = True
     # Can the expression be used as a source expression in Window?
@@ -323,11 +326,12 @@ class BaseExpression:
         """Return the output type of this expressions."""
         output_field = self._resolve_output_field()
         if output_field is None:
-            self._output_field_resolved_to_none = True
-            raise FieldError("Cannot resolve expression type, unknown output_field")
+            raise OutputFieldIsNoneError(
+                "Cannot resolve expression type, unknown output_field"
+            )
         return output_field
 
-    @cached_property
+    @property
     def _output_field_or_none(self):
         """
         Return the output field of this expression, or None if
@@ -335,9 +339,8 @@ class BaseExpression:
         """
         try:
             return self.output_field
-        except FieldError:
-            if not self._output_field_resolved_to_none:
-                raise
+        except OutputFieldIsNoneError:
+            return
 
     def _resolve_output_field(self):
         """

+ 11 - 0
tests/expressions/tests.py

@@ -51,6 +51,7 @@ from django.db.models.expressions import (
     Combinable,
     CombinedExpression,
     NegatedExpression,
+    OutputFieldIsNoneError,
     RawSQL,
     Ref,
 )
@@ -2329,6 +2330,16 @@ class ValueTests(TestCase):
         time = Time.objects.annotate(one=Value(1, output_field=DecimalField())).first()
         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):
         value_types = [
             ("str", CharField),

+ 16 - 0
tests/postgres_tests/test_aggregates.py

@@ -334,6 +334,22 @@ class TestGeneralAggregate(PostgreSQLTestCase):
         )
         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):
         values = AggregateTestModel.objects.filter(integer_field__in=[0, 1]).aggregate(
             bitand=BitAnd("integer_field")