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 ():
@@ -293,8 +324,14 @@ def check_query(self):
293
324
)
294
325
295
326
296
- class SQLUpdateCompiler (SQLCompiler ):
327
+ class SQLUpdateCompiler (compiler . SQLUpdateCompiler , SQLCompiler ):
297
328
def execute_sql (self , result_type ):
329
+ """
330
+ Execute the specified update. Return the number of rows affected by
331
+ the primary update query. The "primary update query" is the first
332
+ non-empty query that is executed. Row counts for any subsequent,
333
+ related queries are not available.
334
+ """
298
335
self .pre_sql_setup ()
299
336
values = []
300
337
for field , _ , value in self .query .values :
@@ -309,7 +346,14 @@ def execute_sql(self, result_type):
309
346
)
310
347
prepared = field .get_db_prep_save (value , connection = self .connection )
311
348
values .append ((field , prepared ))
312
- return self .update (values )
349
+ is_empty = not bool (values )
350
+ rows = 0 if is_empty else self .update (values )
351
+ for query in self .query .get_related_updates ():
352
+ aux_rows = query .get_compiler (self .using ).execute_sql (result_type )
353
+ if is_empty and aux_rows :
354
+ rows = aux_rows
355
+ is_empty = False
356
+ return rows
313
357
314
358
def update (self , values ):
315
359
spec = {}
0 commit comments