Opened 5 hours ago
#36109 new Bug
Three or more stacked FilteredRelations can create a RecursionError
Reported by: | Peter DeVita | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 5.1 |
Severity: | Normal | Keywords: | FilteredRelation |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Example test case, this belongs to FilteredRelationTests
def test_three_level_nested_chained_relations(self): borrower1 = Borrower.objects.create(name="Jenny") # borrower 1 reserves, rents, and returns book1. Reservation.objects.create( borrower=borrower1, book=self.book1, state=Reservation.STOPPED, ) qs = ( Author.objects.annotate( my_books=FilteredRelation( "book" ), my_reserved_books=FilteredRelation( "my_books__reservation", condition=Q(my_books__reservation__state=Reservation.STOPPED) ), my_readers=FilteredRelation( "my_reserved_books__borrower", condition=Q(my_reserved_books__borrower=borrower1) ) ) ) qs = qs.filter( my_readers=borrower1 ) self.assertQuerySetEqual( qs, [ ("Alice",) ], lambda x: (x.name,), )
This will result in a RecursionError
when the filter is applied. From what I can understand, this is because on Query
when it gets to setup_joins
and attempts to setup the join for the my_readers
FilteredRelation, it is not allowed to reuse the aliases from the first two FilteredRelation
s. It then constantly recurses, trying to setup the joins and then not being able to reuse the joins.
I'm no ORM expert but the fix might be as simple as just adding the FilteredRelation's join alias to the can_reuse
set. Any joins that were going to that table but not through the FilteredRelation should be unaffected since they weren't going to reference its alias anyways. Any joins that do reference that FilteredRelation should always want to reuse this too. The fix is in my diff/PR and tests all pass with it.
This bug might also be only because we're filtering on the last join in the set as the first filter condition, it might be possible to force the aliases to be reusable if you filtered one at a time on each FilteredRelation leading up to it.