Fixed #12328 -- Corrected the handling of subqueries with ordering and slicing, especially when used in delete subqueries. Thanks to Walter Doekes for the report.

This fixes a feature that isn't available under MySQL and Oracle (Refs #10099).

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12912 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2010-04-04 17:05:43 +00:00
parent f92d73fbd4
commit 82efb48403
6 changed files with 58 additions and 12 deletions

View File

@ -95,6 +95,7 @@ class BaseDatabaseFeatures(object):
# If True, don't use integer foreign keys referring to, e.g., positive # If True, don't use integer foreign keys referring to, e.g., positive
# integer primary keys. # integer primary keys.
related_fields_match_type = False related_fields_match_type = False
allow_sliced_subqueries = True
class BaseDatabaseOperations(object): class BaseDatabaseOperations(object):
""" """

View File

@ -123,6 +123,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
update_can_self_select = False update_can_self_select = False
allows_group_by_pk = True allows_group_by_pk = True
related_fields_match_type = True related_fields_match_type = True
allow_sliced_subqueries = False
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "django.db.backends.mysql.compiler" compiler_module = "django.db.backends.mysql.compiler"

View File

@ -52,6 +52,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
interprets_empty_strings_as_nulls = True interprets_empty_strings_as_nulls = True
uses_savepoints = True uses_savepoints = True
can_return_id_from_insert = True can_return_id_from_insert = True
allow_sliced_subqueries = False
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):

View File

@ -3,6 +3,7 @@ The main QuerySet implementation. This provides the public API for the ORM.
""" """
from copy import deepcopy from copy import deepcopy
from itertools import izip
from django.db import connections, router, transaction, IntegrityError from django.db import connections, router, transaction, IntegrityError
from django.db.models.aggregates import Aggregate from django.db.models.aggregates import Aggregate
@ -429,11 +430,13 @@ class QuerySet(object):
# becoming too long. # becoming too long.
seen_objs = None seen_objs = None
while 1: while 1:
# Collect all the objects to be deleted in this chunk, and all the # Collect a chunk of objects to be deleted, and then all the
# objects that are related to the objects that are to be deleted. # objects that are related to the objects that are to be deleted.
# The chunking *isn't* done by slicing the del_query because we
# need to maintain the query cache on del_query (see #12328)
seen_objs = CollectedObjects(seen_objs) seen_objs = CollectedObjects(seen_objs)
for object in del_query[:CHUNK_SIZE]: for i, obj in izip(xrange(CHUNK_SIZE), del_query):
object._collect_sub_objects(seen_objs) obj._collect_sub_objects(seen_objs)
if not seen_objs: if not seen_objs:
break break

View File

@ -120,13 +120,15 @@ class SQLCompiler(object):
""" """
Perform the same functionality as the as_sql() method, returning an Perform the same functionality as the as_sql() method, returning an
SQL string and parameters. However, the alias prefixes are bumped SQL string and parameters. However, the alias prefixes are bumped
beforehand (in a copy -- the current query isn't changed) and any beforehand (in a copy -- the current query isn't changed), and any
ordering is removed. ordering is removed if the query is unsliced.
Used when nesting this query inside another. Used when nesting this query inside another.
""" """
obj = self.query.clone() obj = self.query.clone()
obj.clear_ordering(True) if obj.low_mark == 0 and obj.high_mark is None:
# If there is no slicing in use, then we can safely drop all ordering
obj.clear_ordering(True)
obj.bump_prefix() obj.bump_prefix()
return obj.get_compiler(connection=self.connection).as_sql() return obj.get_compiler(connection=self.connection).as_sql()

View File

@ -1,27 +1,65 @@
import unittest import unittest
from models import Tag, Annotation
from django.db import DatabaseError, connections, DEFAULT_DB_ALIAS
from django.db.models import Count from django.db.models import Count
from django.test import TestCase
from models import Tag, Annotation, DumbCategory
class QuerysetOrderedTests(unittest.TestCase): class QuerysetOrderedTests(unittest.TestCase):
""" """
Tests for the Queryset.ordered attribute. Tests for the Queryset.ordered attribute.
""" """
def test_no_default_or_explicit_ordering(self): def test_no_default_or_explicit_ordering(self):
self.assertEqual(Annotation.objects.all().ordered, False) self.assertEqual(Annotation.objects.all().ordered, False)
def test_cleared_default_ordering(self): def test_cleared_default_ordering(self):
self.assertEqual(Tag.objects.all().ordered, True) self.assertEqual(Tag.objects.all().ordered, True)
self.assertEqual(Tag.objects.all().order_by().ordered, False) self.assertEqual(Tag.objects.all().order_by().ordered, False)
def test_explicit_ordering(self): def test_explicit_ordering(self):
self.assertEqual(Annotation.objects.all().order_by('id').ordered, True) self.assertEqual(Annotation.objects.all().order_by('id').ordered, True)
def test_order_by_extra(self): def test_order_by_extra(self):
self.assertEqual(Annotation.objects.all().extra(order_by=['id']).ordered, True) self.assertEqual(Annotation.objects.all().extra(order_by=['id']).ordered, True)
def test_annotated_ordering(self): def test_annotated_ordering(self):
qs = Annotation.objects.annotate(num_notes=Count('notes')) qs = Annotation.objects.annotate(num_notes=Count('notes'))
self.assertEqual(qs.ordered, False) self.assertEqual(qs.ordered, False)
self.assertEqual(qs.order_by('num_notes').ordered, True) self.assertEqual(qs.order_by('num_notes').ordered, True)
class SubqueryTests(TestCase):
def setUp(self):
DumbCategory.objects.create(id=1)
DumbCategory.objects.create(id=2)
DumbCategory.objects.create(id=3)
def test_ordered_subselect(self):
"Subselects honor any manual ordering"
try:
query = DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[0:2])
self.assertEquals(set(query.values_list('id', flat=True)), set([2,3]))
query = DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[:2])
self.assertEquals(set(query.values_list('id', flat=True)), set([2,3]))
query = DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[2:])
self.assertEquals(set(query.values_list('id', flat=True)), set([1]))
except DatabaseError:
# Oracle and MySQL both have problems with sliced subselects.
# This prevents us from even evaluating this test case at all.
# Refs #10099
self.assertFalse(connections[DEFAULT_DB_ALIAS].features.allow_sliced_subqueries)
def test_sliced_delete(self):
"Delete queries can safely contain sliced subqueries"
try:
DumbCategory.objects.filter(id__in=DumbCategory.objects.order_by('-id')[0:1]).delete()
self.assertEquals(set(DumbCategory.objects.values_list('id', flat=True)), set([1,2]))
except DatabaseError:
# Oracle and MySQL both have problems with sliced subselects.
# This prevents us from even evaluating this test case at all.
# Refs #10099
self.assertFalse(connections[DEFAULT_DB_ALIAS].features.allow_sliced_subqueries)