Opened 6 years ago

Closed 18 months ago

#15787 closed Bug (fixed)

select_related with nested fields affects query result

Reported by: kriomant@… Owned by: nobody
Component: Database layer (models, ORM) Version: 1.3
Severity: Normal Keywords:
Cc: daniel.barreto.n@…, l0ki Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no


I belive select_related should never affect query result, but only optimize referenced objects loading. However, I've found a case where query result is changed.

from django.db import models

class Thing(models.Model):
        name = models.CharField(max_length=255)

        def __unicode__(self):

class User(models.Model):
        login = models.CharField(max_length=255)

class Lock(models.Model):
        description = models.CharField(max_length=255)

        # Only one lock per thing is allowed.
        thing = models.OneToOneField(Thing)

        user = models.ForeignKey(User)

from django.test import TestCase

from reproduce.models import *

class SimpleTest(TestCase):
        def setUp(self):
                user = User.objects.create(login='james')

                # Unlocked thing.
                self.unlocked_thing = Thing.objects.create(name='unlocked')

                # Locked thing.
                self.locked_thing = Thing.objects.create(name='locked')
                Lock.objects.create(thing=self.locked_thing, description='lock', user=user)

        def test_simple_query(self):
                things = Thing.objects.all()
                self.assertEqual(set(things), set([self.unlocked_thing, self.locked_thing]))

        def test_select_related(self):
                things = Thing.objects.select_related('lock')
                self.assertEqual(set(things), set([self.locked_thing, self.unlocked_thing]))

        def test_deep_select_related(self):
                things = Thing.objects.select_related('lock', 'lock__user')
                # This fails.
                self.assertEqual(set(things), set([self.locked_thing, self.unlocked_thing]))

./ test reproduce produces:

Creating test database for alias 'default'...
FAIL: test_deep_select_related (reproduce.tests.SimpleTest)
Traceback (most recent call last):
  File "/home/.../select_related_bug/reproduce/", line 27, in test_deep_select_related
    self.assertEqual(set(things), set([self.locked_thing, self.unlocked_thing]))
AssertionError: Items in the second set but not the first:
<Thing: unlocked>

Ran 3 tests in 0.006s

FAILED (failures=1)
Destroying test database for alias 'default'...

Change History (5)

comment:1 Changed 6 years ago by Johannes Dollinger

Easy pickings: unset
Triage Stage: UnreviewedAccepted

comment:2 Changed 5 years ago by Malcolm Tredinnick

UI/UX: unset

The initial observation is correct: select_related() is only an optimisation and should never change the results returned at the outermost object level (it will, of course, select extra lower-level objects).

comment:3 Changed 5 years ago by Daniel Barreto

Cc: daniel.barreto.n@… added

comment:4 Changed 5 years ago by l0ki

Cc: l0ki added

comment:5 Changed 18 months ago by Tim Graham

Resolution: fixed
Status: newclosed
Note: See TracTickets for help on using tickets.
Back to Top