From ec0319ff821492783f555917b61bceacddb77571 Mon Sep 17 00:00:00 2001 From: Adnan Umer Date: Wed, 18 Apr 2018 22:25:59 +0500 Subject: [PATCH] Fixed #29339 -- Added result caching to RawQuerySet. --- django/db/models/query.py | 9 +++++++++ docs/releases/2.1.txt | 3 +++ docs/topics/db/sql.txt | 5 +---- tests/raw_query/tests.py | 12 ++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 1c6c3aae353..e1f3ce8adf2 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1277,6 +1277,7 @@ class RawQuerySet: self.query = query or sql.RawQuery(sql=raw_query, using=self.db, params=params) self.params = params or () self.translations = translations or {} + self._result_cache = None def resolve_model_init_order(self): """Resolve the init field names and value positions.""" @@ -1288,7 +1289,15 @@ class RawQuerySet: model_init_names = [f.attname for f in model_init_fields] return model_init_names, model_init_order, annotation_fields + def _fetch_all(self): + if self._result_cache is None: + self._result_cache = list(self.iterator()) + def __iter__(self): + self._fetch_all() + return iter(self._result_cache) + + def iterator(self): # Cache some things for performance reasons outside the loop. db = self.db compiler = connections[db].ops.compiler('SQLCompiler')( diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index 7b65b90b9c9..f3a99e5fc00 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -401,6 +401,9 @@ Miscellaneous * The admin CSS class ``field-box`` is renamed to ``fieldBox`` to prevent conflicts with the class given to model fields named "box". +* ``QuerySet.raw()`` now caches its results like regular querysets. Use + ``iterator()`` if you don't want caching. + .. _deprecated-features-2.1: Features deprecated in 2.1 diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index 96d79a999ce..cff628011ec 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -91,10 +91,7 @@ options that make it very powerful. :class:`~django.db.models.query.QuerySet`, ``RawQuerySet`` doesn't implement all methods you can use with ``QuerySet``. For example, ``__bool__()`` and ``__len__()`` are not defined in ``RawQuerySet``, and - thus all ``RawQuerySet`` instances are considered ``True``. The reason - these methods are not implemented in ``RawQuerySet`` is that implementing - them without internal caching would be a performance drawback and adding - such caching would be backward incompatible. + thus all ``RawQuerySet`` instances are considered ``True``. Mapping query fields to model fields ------------------------------------ diff --git a/tests/raw_query/tests.py b/tests/raw_query/tests.py index bcc075ea355..1f0d0c363fe 100644 --- a/tests/raw_query/tests.py +++ b/tests/raw_query/tests.py @@ -318,3 +318,15 @@ class RawQueryTests(TestCase): c = Coffee.objects.create(brand='starbucks', price=20.5) qs = Coffee.objects.raw("SELECT * FROM raw_query_coffee WHERE price >= %s", params=[Decimal(20)]) self.assertEqual(list(qs), [c]) + + def test_result_caching(self): + with self.assertNumQueries(1): + books = Book.objects.raw('SELECT * FROM raw_query_book') + list(books) + list(books) + + def test_iterator(self): + with self.assertNumQueries(2): + books = Book.objects.raw('SELECT * FROM raw_query_book') + list(books.iterator()) + list(books.iterator())