Opened 2 years ago

Closed 2 years ago

Last modified 2 years ago

#34227 closed Bug (fixed)

Multi-level FilteredRelation with select_related() may set wrong related object.

Reported by: zhu Owned by: zhu
Component: Database layer (models, ORM) Version: 4.1
Severity: Normal Keywords:
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

test case:

# add to known_related_objects.tests.ExistingRelatedInstancesTests
    def test_wrong_select_related(self):
        with self.assertNumQueries(3):
            p = list(PoolStyle.objects.annotate(
                tournament_pool=FilteredRelation('pool__tournament__pool'),
                ).select_related('tournament_pool'))
            self.assertEqual(p[0].pool.tournament, p[0].tournament_pool.tournament)

result:

======================================================================
FAIL: test_wrong_select_related (known_related_objects.tests.ExistingRelatedInstancesTests.test_wrong_select_related)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\Work\django\tests\known_related_objects\tests.py", line 171, in test_wrong_select_related
    self.assertEqual(p[0].pool.tournament, p[0].tournament_pool.tournament)
AssertionError: <Tournament: Tournament object (1)> != <PoolStyle: PoolStyle object (1)>

----------------------------------------------------------------------

Change History (7)

comment:1 by zhu, 2 years ago

Seems this bug can be fixed by:

M django/db/models/sql/compiler.py
@@ -1270,6 +1270,9 @@ class SQLCompiler:
                 if from_obj:
                     final_field.remote_field.set_cached_value(from_obj, obj)
 
+            def no_local_setter(obj, from_obj):
+                pass
+
             def remote_setter(name, obj, from_obj):
                 setattr(from_obj, name, obj)
 
@@ -1291,7 +1294,7 @@ class SQLCompiler:
                         "model": model,
                         "field": final_field,
                         "reverse": True,
-                        "local_setter": partial(local_setter, final_field),
+                        "local_setter": partial(local_setter, final_field) if len(joins) <= 2 else no_local_setter,
                         "remote_setter": partial(remote_setter, name),
                         "from_parent": from_parent,
                     }

comment:2 by Mariusz Felisiak, 2 years ago

Component: UncategorizedDatabase layer (models, ORM)
Summary: multi level FilteredRelation with select_related may set wrong related objectCyclic multi-level FilteredRelation with select_related() may set wrong related object.
Triage Stage: UnreviewedAccepted
Type: UncategorizedBug

comment:3 by zhu, 2 years ago

"cyclic" is not the case. Try the test below:

    def test_wrong_select_related2(self):
        with self.assertNumQueries(3):
            p = list(
                Tournament.objects.filter(id=self.t2.id).annotate(
                    style=FilteredRelation('pool__another_style'),
                ).select_related('style')
            )
            self.assertEqual(self.ps3, p[0].style)
            self.assertEqual(self.p1, p[0].style.pool)
            self.assertEqual(self.p3, p[0].style.another_pool)

result:

======================================================================
FAIL: test_wrong_select_related2 (known_related_objects.tests.ExistingRelatedInstancesTests.test_wrong_select_related2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/repos/django/tests/known_related_objects/tests.py", line 186, in test_wrong_select_related2
    self.assertEqual(self.p3, p[0].style.another_pool)
AssertionError: <Pool: Pool object (3)> != <Tournament: Tournament object (2)>

----------------------------------------------------------------------

The query fetch t2 and ps3, then call remote_setter('style', ps3, t2) and local_setter(t2, ps3).
The joins is ['known_related_objects_tournament', 'known_related_objects_pool', 'style'].
The type of the first argument of the local_setter should be joins[-2], but query do not fetch that object, so no available local_setter when len(joins) > 2.

comment:4 by zhu, 2 years ago

Has patch: set
Owner: changed from nobody to zhu
Status: newassigned

comment:5 by Mariusz Felisiak, 2 years ago

Summary: Cyclic multi-level FilteredRelation with select_related() may set wrong related object.Multi-level FilteredRelation with select_related() may set wrong related object.
Triage Stage: AcceptedReady for checkin

comment:6 by Mariusz Felisiak <felisiak.mariusz@…>, 2 years ago

Resolution: fixed
Status: assignedclosed

In d3c93cdc:

Fixed #34227 -- Fixed QuerySet.select_related() with multi-level FilteredRelation.

comment:7 by Mariusz Felisiak <felisiak.mariusz@…>, 2 years ago

In f23a853:

[4.2.x] Fixed #34227 -- Fixed QuerySet.select_related() with multi-level FilteredRelation.

Backport of d3c93cdc597e0efc2815111c04dd5a427432ed37 from main

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