#35309: Remove Order by on models when prefetching by id
-------------------------------------+-------------------------------------
     Reporter:  Laurent Lyaudet      |                    Owner:  nobody
         Type:                       |                   Status:  new
  Cleanup/optimization               |
    Component:  Database layer       |                  Version:  5.0
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:  prefetch order_by    |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Comment (by Laurent Lyaudet):

 I spent my night on it but I was able to make a patch, and I don't think
 there will be any regression.
 Consider the following models in some project
 TestNoOrderByForForeignKeyPrefetches and some app test_no_order_by
 models.py file:

 {{{#!python
 from django.core.management.base import BaseCommand
 from django.db import connection
 from django.db.models import Prefetch, QuerySet, RawQuerySet
 from django.db.models.fields.related_descriptors import (
     ForwardManyToOneDescriptor,
     ReverseOneToOneDescriptor,
 )

 from TestNoOrderByForForeignKeyPrefetches.test_no_order_by.models import
 A, B


 old_prefetch_init = Prefetch.__init__


 def new_prefetch_init(self, *args, **kwargs):
     result = old_prefetch_init(self, *args, **kwargs)
     if self.queryset is not None:
         self.queryset._do_not_modify_order_by = True
     return result


 Prefetch.__init__ = new_prefetch_init


 old_get_prefetch_querysets_forward_many_to_one =
 ForwardManyToOneDescriptor.get_prefetch_querysets
 old_get_prefetch_querysets_reverse_one_to_one =
 ReverseOneToOneDescriptor.get_prefetch_querysets


 def get_prefetch_querysets_forward_many_to_one(self, *args, **kwargs):
     result = old_get_prefetch_querysets_forward_many_to_one(self, *args,
 **kwargs)
     if not hasattr(result[0], '_do_not_modify_order_by'):
         result = (result[0].order_by(), *result[1:])
     return result


 def get_prefetch_querysets_reverse_one_to_one(self, *args, **kwargs):
     result = old_get_prefetch_querysets_reverse_one_to_one(self, *args,
 **kwargs)
     if not hasattr(result[0], '_do_not_modify_order_by'):
         result = (result[0].order_by(), *result[1:])
     return result


 ForwardManyToOneDescriptor.get_prefetch_querysets =
 get_prefetch_querysets_forward_many_to_one
 ReverseOneToOneDescriptor.get_prefetch_querysets =
 get_prefetch_querysets_reverse_one_to_one


 old_clone_queryset = QuerySet._clone


 def new_clone_queryset(self):
     result = old_clone_queryset(self)
     if hasattr(self, '_do_not_modify_order_by'):
         result._do_not_modify_order_by = True
     return result


 QuerySet._clone = new_clone_queryset


 old_clone_raw_queryset = RawQuerySet._clone


 def new_clone_raw_queryset(self):
     result = old_clone_raw_queryset(self)
     if hasattr(self, '_do_not_modify_order_by'):
         result._do_not_modify_order_by = True
     return result


 RawQuerySet._clone = new_clone_raw_queryset


 class Command(BaseCommand):
     help = "Test"

     def handle(self, *args, **options):
         B.objects.all().delete()
         A.objects.all().delete()

         a1 = A.objects.create(name="a1")
         a2 = A.objects.create(name="a2")
         a3 = A.objects.create(name="a3")
         a4 = A.objects.create(name="a4")
         a5 = A.objects.create(name="a5")
         a6 = A.objects.create(name="a6")
         a7 = A.objects.create(name="a7")

         b1 = B.objects.create(a=a1, name="b1")
         b2 = B.objects.create(a=a2, name="b2")
         b3 = B.objects.create(a=a3, name="b3")
         b4 = B.objects.create(a=a4, name="b4")
         b5 = B.objects.create(a=a5, name="b5")
         b6 = B.objects.create(a=a6, name="b6")
         b7 = B.objects.create(a=a7, name="b7")

         bs = list(B.objects.all().prefetch_related("a"))
         a_s = list(A.objects.all().prefetch_related("bs"))
         bs = list(B.objects.all().prefetch_related(
             Prefetch(
                 "a",
                 queryset=A.objects.order_by("-name")
             ),
         ))
         a_s = list(A.objects.all().prefetch_related(
             Prefetch(
                 "bs",
                 queryset=B.objects.order_by("-name")
             ),
         ))
         print(connection.queries)
 }}}

 If you launch the command with python3 manage.py test_no_order_by_command,
 you will see that there are 8 SELECT after the 14 INSERT and that there is
 only 7 ORDER BY on them as requested.

 I will prepare a PR.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35309#comment:5>
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 django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/0107018e45c06f83-002282d5-403f-4ad1-8f46-0a0f7226ccac-000000%40eu-central-1.amazonses.com.

Reply via email to