Browse Source

Fixed #29339 -- Added result caching to RawQuerySet.

Adnan Umer 7 years ago
parent
commit
ec0319ff82
4 changed files with 25 additions and 4 deletions
  1. 9 0
      django/db/models/query.py
  2. 3 0
      docs/releases/2.1.txt
  3. 1 4
      docs/topics/db/sql.txt
  4. 12 0
      tests/raw_query/tests.py

+ 9 - 0
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')(

+ 3 - 0
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

+ 1 - 4
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
 ------------------------------------

+ 12 - 0
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())