Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[1.1.X] Fixed #12328 -- Corrected the handling of subqueries with ord…

…ering 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
  • Loading branch information...
commit 0d6a776ccd9a6035b97389ca57fee41dd7454e01 1 parent 17636ef
@freakboy3742 freakboy3742 authored
View
1  django/db/backends/__init__.py
@@ -102,6 +102,7 @@ class BaseDatabaseFeatures(object):
# If True, don't use integer foreign keys referring to, e.g., positive
# integer primary keys.
related_fields_match_type = False
+ allow_sliced_subqueries = True
class BaseDatabaseOperations(object):
"""
View
1  django/db/backends/mysql/base.py
@@ -113,6 +113,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
update_can_self_select = False
allows_group_by_pk = True
related_fields_match_type = True
+ allow_sliced_subqueries = False
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):
View
1  django/db/backends/oracle/base.py
@@ -51,6 +51,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
interprets_empty_strings_as_nulls = True
uses_savepoints = True
can_return_id_from_insert = True
+ allow_sliced_subqueries = False
class DatabaseOperations(BaseDatabaseOperations):
View
10 django/db/models/query.py
@@ -7,6 +7,8 @@
except NameError:
from sets import Set as set # Python 2.3 fallback
+from itertools import izip
+
from django.db import connection, transaction, IntegrityError
from django.db.models.aggregates import Aggregate
from django.db.models.fields import DateField
@@ -387,11 +389,13 @@ def delete(self):
# becoming too long.
seen_objs = None
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.
+ # 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)
- for object in del_query[:CHUNK_SIZE]:
- object._collect_sub_objects(seen_objs)
+ for i, obj in izip(xrange(CHUNK_SIZE), del_query):
+ obj._collect_sub_objects(seen_objs)
if not seen_objs:
break
View
9 django/db/models/sql/query.py
@@ -456,13 +456,14 @@ def as_nested_sql(self):
"""
Perform the same functionality as the as_sql() method, returning an
SQL string and parameters. However, the alias prefixes are bumped
- beforehand (in a copy -- the current query isn't changed) and any
- ordering is removed.
-
+ beforehand (in a copy -- the current query isn't changed), and any
+ ordering is removed if the query is unsliced.
Used when nesting this query inside another.
"""
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()
return obj.as_sql()
View
50 tests/regressiontests/queries/tests.py
@@ -1,27 +1,65 @@
import unittest
-from models import Tag, Annotation
+
+from django.db import DatabaseError, connection
from django.db.models import Count
+from django.test import TestCase
+
+from models import Tag, Annotation, DumbCategory
class QuerysetOrderedTests(unittest.TestCase):
"""
Tests for the Queryset.ordered attribute.
"""
-
+
def test_no_default_or_explicit_ordering(self):
self.assertEqual(Annotation.objects.all().ordered, False)
def test_cleared_default_ordering(self):
self.assertEqual(Tag.objects.all().ordered, True)
self.assertEqual(Tag.objects.all().order_by().ordered, False)
-
+
def test_explicit_ordering(self):
self.assertEqual(Annotation.objects.all().order_by('id').ordered, True)
-
+
def test_order_by_extra(self):
self.assertEqual(Annotation.objects.all().extra(order_by=['id']).ordered, True)
-
+
def test_annotated_ordering(self):
qs = Annotation.objects.annotate(num_notes=Count('notes'))
self.assertEqual(qs.ordered, False)
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)
Please sign in to comment.
Something went wrong with that request. Please try again.