1+ from itertools import chain
2+
13from django .core .exceptions import EmptyResultSet , FullResultSet
24from django .db import DatabaseError , IntegrityError , NotSupportedError
35from django .db .models import Count , Expression
46from django .db .models .aggregates import Aggregate
57from django .db .models .expressions import OrderBy
68from django .db .models .sql import compiler
7- from django .db .models .sql .constants import GET_ITERATOR_CHUNK_SIZE , MULTI , ORDER_DIR
9+ from django .db .models .sql .constants import GET_ITERATOR_CHUNK_SIZE , MULTI , ORDER_DIR , SINGLE
810from django .utils .functional import cached_property
911
1012from .base import Cursor
@@ -33,11 +35,21 @@ def execute_sql(
3335 except EmptyResultSet :
3436 return iter ([]) if result_type == MULTI else None
3537
36- return (
37- (self ._make_result (row , columns ) for row in query .fetch ())
38- if result_type == MULTI
39- else self ._make_result (next (query .fetch ()), columns )
40- )
38+ cursor = query .get_cursor ()
39+ if result_type == SINGLE :
40+ try :
41+ obj = cursor .next ()
42+ except StopIteration :
43+ return None # No result
44+ else :
45+ return self ._make_result (obj , columns )
46+ # result_type is MULTI
47+ cursor .batch_size (chunk_size )
48+ result = self .cursor_iter (cursor , chunk_size , columns )
49+ if not chunked_fetch :
50+ # If using non-chunked reads, read data into memory.
51+ return list (result )
52+ return result
4153
4254 def results_iter (
4355 self ,
@@ -49,14 +61,23 @@ def results_iter(
4961 """
5062 Return an iterator over the results from executing query given
5163 to this compiler. Called by QuerySet methods.
64+
65+ This method is copied from the superclass with one modification: the
66+ `if tuple_expected` block is deindented so that the result of
67+ _make_result() (a list) is cast to tuple as needed. For SQL database
68+ drivers, tuple results come from cursor.fetchmany(), so the cast is
69+ only needed there when apply_converters() casts the tuple to a list.
70+ This customized method could be removed if _make_result() cast its
71+ return value to a tuple, but that would be more expensive since that
72+ cast is not always needed.
5273 """
5374 if results is None :
5475 # QuerySet.values() or values_list()
5576 results = self .execute_sql (MULTI , chunked_fetch = chunked_fetch , chunk_size = chunk_size )
5677
5778 fields = [s [0 ] for s in self .select [0 : self .col_count ]]
5879 converters = self .get_converters (fields )
59- rows = results
80+ rows = chain . from_iterable ( results )
6081 if converters :
6182 rows = self .apply_converters (rows , converters )
6283 if tuple_expected :
@@ -86,6 +107,16 @@ def _make_result(self, entity, columns):
86107 result .append (obj .get (name ))
87108 return result
88109
110+ def cursor_iter (self , cursor , chunk_size , columns ):
111+ """Yield chunks of results from cursor."""
112+ chunk = []
113+ for row in cursor :
114+ chunk .append (self ._make_result (row , columns ))
115+ if len (chunk ) == chunk_size :
116+ yield chunk
117+ chunk = []
118+ yield chunk
119+
89120 def check_query (self ):
90121 """Check if the current query is supported by the database."""
91122 if self .query .is_empty ():
0 commit comments