1
+ from itertools import chain
2
+
1
3
from django .core .exceptions import EmptyResultSet , FullResultSet
2
4
from django .db import DatabaseError , IntegrityError , NotSupportedError
3
5
from django .db .models import Count , Expression
4
6
from django .db .models .aggregates import Aggregate
5
7
from django .db .models .expressions import OrderBy
6
8
from 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
8
10
from django .utils .functional import cached_property
9
11
10
12
from .base import Cursor
@@ -33,11 +35,21 @@ def execute_sql(
33
35
except EmptyResultSet :
34
36
return iter ([]) if result_type == MULTI else None
35
37
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
41
53
42
54
def results_iter (
43
55
self ,
@@ -49,14 +61,23 @@ def results_iter(
49
61
"""
50
62
Return an iterator over the results from executing query given
51
63
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.
52
73
"""
53
74
if results is None :
54
75
# QuerySet.values() or values_list()
55
76
results = self .execute_sql (MULTI , chunked_fetch = chunked_fetch , chunk_size = chunk_size )
56
77
57
78
fields = [s [0 ] for s in self .select [0 : self .col_count ]]
58
79
converters = self .get_converters (fields )
59
- rows = results
80
+ rows = chain . from_iterable ( results )
60
81
if converters :
61
82
rows = self .apply_converters (rows , converters )
62
83
if tuple_expected :
@@ -86,6 +107,16 @@ def _make_result(self, entity, columns):
86
107
result .append (obj .get (name ))
87
108
return result
88
109
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
+
89
120
def check_query (self ):
90
121
"""Check if the current query is supported by the database."""
91
122
if self .query .is_empty ():
0 commit comments