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,18 @@ 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
+ return self ._make_result (next (cursor ), columns )
41
+ # result_type is MULTI
42
+ cursor .batch_size (chunk_size )
43
+ result = self .cursor_iter (cursor , chunk_size , columns )
44
+ if not chunked_fetch :
45
+ # If using non-chunked reads, return the same data structure as
46
+ # normally, but ensure it is all read into memory before going
47
+ # any further.
48
+ return list (result )
49
+ return result
41
50
42
51
def results_iter (
43
52
self ,
@@ -49,14 +58,23 @@ def results_iter(
49
58
"""
50
59
Return an iterator over the results from executing query given
51
60
to this compiler. Called by QuerySet methods.
61
+
62
+ This method is copied from the superclass with one modification: the
63
+ `if tuple_expected` block is deindented so that the result of
64
+ _make_result() (a list) is cast to tuple as needed. For SQL database
65
+ drivers, results come from cursor.fetchmany() and are tuples, so the
66
+ cast is only needed there when apply_converters() casts the tuple to a
67
+ list. This customized method could be removed if _make_result() cast
68
+ its return value to a tuple, but that would be more expensive since
69
+ it's not always needed.
52
70
"""
53
71
if results is None :
54
72
# QuerySet.values() or values_list()
55
73
results = self .execute_sql (MULTI , chunked_fetch = chunked_fetch , chunk_size = chunk_size )
56
74
57
75
fields = [s [0 ] for s in self .select [0 : self .col_count ]]
58
76
converters = self .get_converters (fields )
59
- rows = results
77
+ rows = chain . from_iterable ( results )
60
78
if converters :
61
79
rows = self .apply_converters (rows , converters )
62
80
if tuple_expected :
@@ -86,6 +104,15 @@ def _make_result(self, entity, columns):
86
104
result .append (obj .get (name ))
87
105
return result
88
106
107
+ def cursor_iter (self , cursor , chunk_size , columns ):
108
+ """Generator to yield chunks from cursor."""
109
+ chunk = []
110
+ for i , row in enumerate (cursor ):
111
+ if i % chunk_size == 0 and i > 0 :
112
+ yield chunk
113
+ chunk .append (self ._make_result (row , columns ))
114
+ yield chunk
115
+
89
116
def check_query (self ):
90
117
"""Check if the current query is supported by the database."""
91
118
if self .query .is_empty ():
@@ -293,8 +320,14 @@ def check_query(self):
293
320
)
294
321
295
322
296
- class SQLUpdateCompiler (SQLCompiler ):
323
+ class SQLUpdateCompiler (compiler . SQLUpdateCompiler , SQLCompiler ):
297
324
def execute_sql (self , result_type ):
325
+ """
326
+ Execute the specified update. Return the number of rows affected by
327
+ the primary update query. The "primary update query" is the first
328
+ non-empty query that is executed. Row counts for any subsequent,
329
+ related queries are not available.
330
+ """
298
331
self .pre_sql_setup ()
299
332
values = []
300
333
for field , _ , value in self .query .values :
@@ -309,7 +342,13 @@ def execute_sql(self, result_type):
309
342
)
310
343
prepared = field .get_db_prep_save (value , connection = self .connection )
311
344
values .append ((field , prepared ))
312
- return self .update (values )
345
+ rows = 0 if (is_empty := not bool (values )) else self .update (values )
346
+ for query in self .query .get_related_updates ():
347
+ aux_rows = query .get_compiler (self .using ).execute_sql (result_type )
348
+ if is_empty and aux_rows :
349
+ rows = aux_rows
350
+ is_empty = False
351
+ return rows
313
352
314
353
def update (self , values ):
315
354
spec = {}
0 commit comments