1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

[1.1.X] 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).

Backport of r12912 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@12914 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2010-04-04 17:17:46 +00:00
parent 17636ef999
commit 0d6a776ccd
6 changed files with 59 additions and 13 deletions

View File

@ -102,6 +102,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

@ -113,6 +113,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):
def date_extract_sql(self, lookup_type, field_name): def date_extract_sql(self, lookup_type, field_name):

View File

@ -51,6 +51,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

@ -7,6 +7,8 @@ try:
except NameError: except NameError:
from sets import Set as set # Python 2.3 fallback from sets import Set as set # Python 2.3 fallback
from itertools import izip
from django.db import connection, transaction, IntegrityError from django.db import connection, transaction, IntegrityError
from django.db.models.aggregates import Aggregate from django.db.models.aggregates import Aggregate
from django.db.models.fields import DateField from django.db.models.fields import DateField
@ -387,11 +389,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

@ -456,13 +456,14 @@ class BaseQuery(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.clone() obj = self.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.as_sql() return obj.as_sql()

View File

@ -1,27 +1,65 @@
import unittest import unittest
from models import Tag, Annotation
from django.db import DatabaseError, connection
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(connection.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(connection.features.allow_sliced_subqueries)