Opened 12 months ago

Last modified 11 months ago

#36682 assigned Bug

RecursionError when traversing several reverse relations with FilteredRelation

Reported by: REGNIER Guillaume Owned by: Augusto Pontes
Component: Database layer (models, ORM) Version: 5.2
Severity: Normal Keywords: FilteredRelation
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description (last modified by REGNIER Guillaume)

When declaring a filtered relation with a condition on the 2-distant reverse relation, query compilation fails with a RecursionError

Steps to Reproduce:

class Company(models.Model):
    pass

class Employee(models.Model):
    company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="employees")

class Mission(models.Model):
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="missions")
    name = models.TextField()
    deadline = models.DateTimeField()
qs = Company.objects.alias(
    specific_mission=FilteredRelation(
        "employees__missions",
        condition=Q(employees__missions__name="specific"),
    )
)
qs = qs.filter(specific_mission__deadline__lt=localtime())
list(qs)

Expected Behavior:

.filter(...) does not throw RecursionError and produce an SQL query equivalent to

SELECT 
  "company"."id" 
FROM 
  "company" 
  INNER JOIN "employee" ON (
    "company"."id" = "employee"."company_id"
  ) 
  INNER JOIN "mission" specific_mission ON (
    "employee"."id" = specific_mission."employee_id" 
    AND specific_mission."name" = 'specific'
  ) 
WHERE 
  specific_mission."deadline" < '2025-10-23 11:07:06.224821+02:00'

Actual Behavior:

.filter(...) throws RecursionError

Analysis:

My understanding is that Query.join() fails to reuse the join for the first reverse relation, leading to the FilteredRelation being resolved again and again.
Loop is:

  • FilteredRelation.resolve_expression()
  • Query.build_filter()
  • Query._add_q()
  • Query.build_filter()
  • Query.setup_joins()
  • Query.join()
  • FilteredRelation.resolve_expression()

Workaround:

        qs = Company.objects.alias(
            _employees=FilteredRelation("employees"),
            specific_mission=FilteredRelation(
                "_employees__missions",
                condition=Q(_employees__missions__name="specific"),
            ),
        )
        qs = qs.filter(specific_mission__deadline__lt=localtime())
        list(qs)

Related tickets:

#36109: Related to FilteredRelation and RecursionError (uses mentioned workaround)
#34957: Probably the same underlying issue, ticket was closed due to lacks reproduction details

Change History (9)

comment:1 by Simon Charette, 12 months ago

Triage Stage: Unreviewed → Accepted

Thank you for your report, reproduced against 5e2bbebed9f36bb9d15f168444e7982287761877.

Last edited 12 months ago by Simon Charette (previous) (diff)

comment:2 by REGNIER Guillaume, 12 months ago

Description: modified (diff)

comment:3 by Augusto Pontes, 11 months ago

Has patch: set
Needs documentation: set
Needs tests: set
Owner: set to Augusto Pontes
Status: new → assigned

comment:4 by Augusto Pontes, 11 months ago

Hi, so i have a suggestion of how fixing it, i sent a PR, have a look:

​https://github.com/django/django/pull/20010

comment:5 by Augusto Pontes, 11 months ago

Triage Stage: Accepted → Ready for checkin

comment:6 by Simon Charette, 11 months ago

Needs documentation: unset
Patch needs improvement: set
Triage Stage: Ready for checkin → Accepted

You ​can't mark your own patch RFC.

More over the proposed patch has several problems that should be addressed before it can be considered for final review.

in reply to:  6 comment:7 by Augusto Pontes, 11 months ago

Replying to Simon Charette:

You ​can't mark your own patch RFC.

More over the proposed patch has several problems that should be addressed before it can be considered for final review.

Ok

in reply to:  6 comment:8 by Augusto Pontes, 11 months ago

Replying to Simon Charette:

You ​can't mark your own patch RFC.

More over the proposed patch has several problems that should be addressed before it can be considered for final review.

I accept suggestions on the PR of how can i improve to solve this problem

in reply to:  6 comment:9 by Augusto Pontes, 11 months ago

Replying to Simon Charette:

You ​can't mark your own patch RFC.

More over the proposed patch has several problems that should be addressed before it can be considered for final review.

I saw your comments, i will read them

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