#35376: Prefetched data not used when combining prefetch_related() and only()
-------------------------------------+-------------------------------------
               Reporter:  Michael    |          Owner:  nobody
  Schwarz                            |
                   Type:             |         Status:  new
  Uncategorized                      |
              Component:  Database   |        Version:  4.2
  layer (models, ORM)                |
               Severity:  Normal     |       Keywords:
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 I think I found a simple case combining `prefetch_related()` and `only()`,
 where  prefetched data isn't used when it should.

 See the following model with a `Restaurant` being a `Building`, and a
 `Building` being part of a `City`. (replace `Building` with `Business` if
 it bugs you that a building can only contain a single restaurant 🙃, I
 noticed too late):

 {{{#!python
 from django.db.models import DO_NOTHING
 from django.db.models import ForeignKey
 from django.db.models import Model
 from django.db.models import OneToOneField
 from django.db.models import TextField

 class City(Model):
     name = TextField()

 class Building(Model):
     city = ForeignKey(City, on_delete=DO_NOTHING)

     street_name = TextField()
     street_no = TextField()

 class Restaurant(Model):
     building = OneToOneField(Building, on_delete=DO_NOTHING)

     name = TextField()
 }}}

 I'm trying to build a query set of buildings with some of its attributes
 deferred using `only()`, and the associated restaurants and cities being
 prefetched. In the following parametrized `pytest` test case, only the
 last instance is exhibiting the issue, the other 3 seem to work as I would
 expect.

 The test case creates an instance of each model, runs the query
 (`list(...)`), and then accesses the `restaurant` attribute, which should
 be prefetched in every case. The test case check that the access does not
 generate an additional query using `django_assert_num_queries()` from
 `pytest-django`.

 {{{#!python
 import pytest

 from myproject.models import Building
 from myproject.models import Restaurant
 from myproject.models import City


 @pytest.mark.parametrize('qs', [
     Building.objects.prefetch_related("restaurant", "city"),
     Building.objects.only("street_name").prefetch_related("restaurant"),
     Building.objects.only("street_name").prefetch_related("city",
 "restaurant"),
     Building.objects.only("street_name").prefetch_related("restaurant",
 "city")
 ])
 def test_repro(db, django_assert_num_queries, qs):
     Restaurant.objects.create(
         name="",
         building=Building.objects.create(
             city=City.objects.create(name=""), street_name="",
 street_no=""
         ),
     )

     result = list(qs)

     with django_assert_num_queries(0):
         result[0].restaurant
 }}}

 The first 3 instances of the test above succeed, the last one fails:

 {{{
 $ venv/bin/pytest test_repro.py
 [...]

     @pytest.mark.parametrize('qs', [
         Building.objects.prefetch_related("restaurant", "city"),
 Building.objects.only("street_name").prefetch_related("restaurant"),
         Building.objects.only("street_name").prefetch_related("city",
 "restaurant"),
 Building.objects.only("street_name").prefetch_related("restaurant",
 "city")
     ])
     def test_repro(db, django_assert_num_queries, qs):
         Restaurant.objects.create(
             name="",
             building=Building.objects.create(
                 city=City.objects.create(name=""), street_name="",
 street_no=""
             ),
         )

         result = list(qs)

 >       with django_assert_num_queries(0):

 [...]

 E               Failed: Expected to perform 0 queries but 1 was done (add
 -v option to show queries)
 }}}

 I've created a [https://github.com/Feuermurmel/only_prefetch_related_repro
 minimal project on GitHub] documenting the exact setup, including all
 package versions.

 AFAICT, in each case above, `restaurant` should be prefetched, and the
 attribute access should not generate an additional access. Only when
 `only()` is used, another related model is also prefetched, and that other
 model is mentioned _after_ `restaurant` in the call to
 `prefetch_related()`, the prefetched data for `restaurant` isn't used.

 Running macOS 12.7.4, Python 3.12.2 and Django 4.2.11.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35376>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/0107018ee21b3e85-1d87db03-6000-42ba-8487-fd687c8a7b9a-000000%40eu-central-1.amazonses.com.

Reply via email to