Opened 15 hours ago

#36222 new Bug

ExclusionConstraint.validate() crashes when exclude includes a field in condition.

Reported by: Clifford Gama Owned by:
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords: validate_constraints ExclusionConstraint exclude
Cc: Clifford Gama Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Discovered during work on #35676.

Failing test:

  • tests/postgres_tests/test_constraints.py

    diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py
    index ab5bf2bab1..ae02fa22ff 100644
    a b class ExclusionConstraintTests(PostgreSQLTestCase):  
    797797            ),
    798798            exclude={"datespan", "start", "end", "room"},
    799799        )
     800        # Excluded condition field.
     801        constraint.validate(
     802            HotelReservation,
     803            HotelReservation(
     804                datespan=(datetimes[1].date(), datetimes[2].date()),
     805                start=datetimes[1],
     806                end=datetimes[2],
     807                room=room102,
     808            ),
     809            exclude={"cancelled"},
     810        )
    800811
    801812    def test_range_overlaps_custom(self):
    802813        class TsTzRange(Func):

Results in two test failures. Here is the failure for one of the tests:

======================================================================
ERROR: test_range_overlaps (postgres_tests.test_constraints.ExclusionConstraintTests.test_range_overlaps)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/clifford/code/django/tests/postgres_tests/test_constraints.py", line 839, in test_range_overlaps
    self._test_range_overlaps(constraint)
  File "/home/clifford/code/django/tests/postgres_tests/test_constraints.py", line 801, in _test_range_overlaps
    constraint.validate(
  File "/home/clifford/code/django/django/contrib/postgres/constraints.py", line 209, in validate
    if (self.condition & Exists(queryset.filter(self.condition))).check(
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/clifford/code/django/django/db/models/query_utils.py", line 137, in check
    query.add_q(Q(Coalesce(self, True, output_field=BooleanField())))
  File "/home/clifford/code/django/django/db/models/sql/query.py", line 1637, in add_q
    clause, _ = self._add_q(q_object, can_reuse)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/clifford/code/django/django/db/models/sql/query.py", line 1669, in _add_q
    child_clause, needed_inner = self.build_filter(
                                 ^^^^^^^^^^^^^^^^^^
  File "/home/clifford/code/django/django/db/models/sql/query.py", line 1508, in build_filter
    condition = filter_expr.resolve_expression(
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/clifford/code/django/django/db/models/expressions.py", line 300, in resolve_expression
    expr.resolve_expression(query, allow_joins, reuse, summarize)
  File "/home/clifford/code/django/django/db/models/query_utils.py", line 91, in resolve_expression
    clause, joins = query._add_q(
                    ^^^^^^^^^^^^^
  File "/home/clifford/code/django/django/db/models/sql/query.py", line 1669, in _add_q
    child_clause, needed_inner = self.build_filter(
                                 ^^^^^^^^^^^^^^^^^^
  File "/home/clifford/code/django/django/db/models/sql/query.py", line 1517, in build_filter
    lookups, parts, reffed_expression = self.solve_lookup_type(arg, summarize)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/clifford/code/django/django/db/models/sql/query.py", line 1324, in solve_lookup_type
    _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/clifford/code/django/django/db/models/sql/query.py", line 1796, in names_to_path
    raise FieldError(
django.core.exceptions.FieldError: Cannot resolve keyword 'cancelled' into field. Choices are: _check

This works correctly for UniqueConstraint (see test), but has been untested (or failure has been undocumented) for ExclusionConstraint since Constraint validation was added by #30581.

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top