#36342: Slicing a QuerySet with a result cache results in a list?
-------------------------------------+-------------------------------------
     Reporter:  Willem Van Onsem     |                     Type:
                                     |  Cleanup/optimization
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  5.2                  |                 Severity:  Normal
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 As
 
[https://github.com/django/django/blob/adf2991d32c24f8b2e549a25a7eda52f317a91a6/django/db/models/query.py#L401-L436
 per code] if you slice a QuerySet with a result cache, we return the
 sliced result cache, this thus means that for a `QuerySet`:

 {{{
 from django.contrib.auth.model import User

 qs = User.objects.all()
 bool(qs)  # enable/disable

 qs[:3].values('id')
 }}}

 will raise an error because `qs[:3]` returns a list, whereas if we comment
 out the `bool`, it will still work.

 This is done probably because of performance reasons: if we have a
 `QuerySet`, and we already know the results, we can just work with these
 results.

 But I'm wondering if we "can have the cake and eat it too". We could for
 example create a sliced copy of the queryset, and populate the result
 cache of the queryset. Something along the lines of:

 {{{
     def __getitem__(self, k):
         """Retrieve an item or slice from the set of results."""
         if not isinstance(k, (int, slice)):
             raise TypeError(
                 "QuerySet indices must be integers or slices, not %s."
                 % type(k).__name__
             )
         if (isinstance(k, int) and k < 0) or (
             isinstance(k, slice)
             and (
                 (k.start is not None and k.start < 0)
                 or (k.stop is not None and k.stop < 0)
             )
         ):
             raise ValueError("Negative indexing is not supported.")

         # remove below
         # if self._result_cache is not None:
         #    return self._result_cache[k]

         if isinstance(k, slice):
             qs = self._chain()
             if k.start is not None:
                 start = int(k.start)
             else:
                 start = None
             if k.stop is not None:
                 stop = int(k.stop)
             else:
                 stop = None
             qs.query.set_limits(start, stop)
             if self._result_cache is not None:
                 # populate the QuerySet
                 qs._result_cache = self._result_cache[k]
             return list(qs)[:: k.step] if k.step else qs
 }}}

 this thus means that, (a) unless we use a step, we always get a `QuerySet`
 for slicing (since it is not always known in advance *if* the `QuerySet`
 has a result cache, that can be the result of complicated code flows); and
 (b) if the result cache was present, the queryset we generate will not
 have to fetch the data, if we don't make more `QuerySet` calls.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36342>
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 visit 
https://groups.google.com/d/msgid/django-updates/01070196542ee19b-42e5f5f1-d7b8-486b-9d8a-f237bf7a86e7-000000%40eu-central-1.amazonses.com.

Reply via email to