diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index c5a0eb71fa..899a779673 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -53,6 +53,7 @@ class BaseDatabaseFeatures(object): time_field_needs_date = False interprets_empty_strings_as_nulls = False date_field_supports_time_value = True + can_use_chunked_reads = True class BaseDatabaseOperations(object): """ diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index b8bf5c8f0b..5cb21f5e3b 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -40,6 +40,11 @@ Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) class DatabaseFeatures(BaseDatabaseFeatures): supports_constraints = False + # SQLite cannot handle us only partially reading from a cursor's result set + # and then writing the same rows to the database in another cursor. This + # setting ensures we always read result sets fully into memory all in one + # go. + can_use_chunked_reads = False class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index f682c71d07..ef69d7657f 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1616,10 +1616,16 @@ class Query(object): # The MULTI case. if self.ordering_aliases: - return order_modified_iter(cursor, len(self.ordering_aliases), + result = order_modified_iter(cursor, len(self.ordering_aliases), self.connection.features.empty_fetchmany_value) - return iter((lambda: cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)), + result = iter((lambda: cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)), self.connection.features.empty_fetchmany_value) + if not self.connection.features.can_use_chunked_reads: + # If we are using non-chunked reads, we return the same data + # structure as normally, but ensure it is all read into memory + # before going any further. + return list(result) + return result # Use the backend's custom Query class if it defines one. Otherwise, use the # default. diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index 65d0d6ec65..fb66c6214b 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -6,14 +6,14 @@ import datetime import pickle from django.db import models -from django.db.models.query import Q +from django.db.models.query import Q, ITER_CHUNK_SIZE # Python 2.3 doesn't have sorted() try: sorted except NameError: from django.utils.itercompat import sorted - + class Tag(models.Model): name = models.CharField(max_length=10) parent = models.ForeignKey('self', blank=True, null=True, @@ -820,5 +820,15 @@ Bug #7698 -- People like to slice with '0' as the high-water mark. >>> Item.objects.all()[0:0] [] +Bug #7411 - saving to db must work even with partially read result set in +another cursor. + +>>> for num in range(2 * ITER_CHUNK_SIZE + 1): +... _ = Number.objects.create(num=num) + +>>> for i, obj in enumerate(Number.objects.all()): +... obj.save() +... if i > 10: break + """}