Loading django/db/models/sql/query.py +30 −4 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ themselves do not have to (and could be backed by things other than SQL databases). The abstraction barrier only works one way: this module has to know all about the internals of models in order to get the information it needs. """ from string import ascii_uppercase from itertools import count, product from collections import OrderedDict import copy Loading Loading @@ -837,13 +839,37 @@ class Query(object): conflict. Even tables that previously had no alias will get an alias after this call. """ def prefix_gen(): """ Generates a sequence of characters in alphabetical order: -> 'A', 'B', 'C', ... When the alphabet is finished, the sequence will continue with the Cartesian product: -> 'AA', 'AB', 'AC', ... """ alphabet = ascii_uppercase prefix = chr(ord(self.alias_prefix) + 1) yield prefix for n in count(1): seq = alphabet[alphabet.index(prefix):] if prefix else alphabet for s in product(seq, repeat=n): yield ''.join(s) prefix = None if self.alias_prefix != outer_query.alias_prefix: # No clashes between self and outer query should be possible. return self.alias_prefix = chr(ord(self.alias_prefix) + 1) while self.alias_prefix in self.subq_aliases: self.alias_prefix = chr(ord(self.alias_prefix) + 1) assert self.alias_prefix < 'Z' local_recursion_limit = 127 # explicitly avoid infinite loop for pos, prefix in enumerate(prefix_gen()): if prefix not in self.subq_aliases: self.alias_prefix = prefix break if pos > local_recursion_limit: raise RuntimeError( 'Maximum recursion depth exceeded: too many subqueries.' ) self.subq_aliases = self.subq_aliases.union([self.alias_prefix]) outer_query.subq_aliases = outer_query.subq_aliases.union(self.subq_aliases) change_map = OrderedDict() Loading docs/releases/1.7.2.txt +3 −0 Original line number Diff line number Diff line Loading @@ -183,3 +183,6 @@ Bugfixes convention in the template engine (:ticket:`23831`). * Prevented extraneous ``DROP DEFAULT`` SQL in migrations (:ticket:`23581`). * Restored the ability to use more than five levels of subqueries (:ticket:`23758`). tests/queries/tests.py +20 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ from django.db.models.sql.datastructures import EmptyResultSet from django.test import TestCase, skipUnlessDBFeature from django.test.utils import str_prefix, CaptureQueriesContext from django.utils import six from django.utils.six.moves import range from .models import ( Annotation, Article, Author, Celebrity, Child, Cover, Detail, DumbCategory, Loading Loading @@ -380,6 +381,25 @@ class Queries1Tests(BaseQuerysetTest): ['<Item: four>'] ) def test_avoid_infinite_loop_on_too_many_subqueries(self): x = Tag.objects.filter(pk=1) local_recursion_limit = 127 msg = 'Maximum recursion depth exceeded: too many subqueries.' with self.assertRaisesMessage(RuntimeError, msg): for i in six.moves.range(local_recursion_limit * 2): x = Tag.objects.filter(pk__in=x) def test_reasonable_number_of_subq_aliases(self): x = Tag.objects.filter(pk=1) for _ in range(20): x = Tag.objects.filter(pk__in=x) self.assertEqual( x.query.subq_aliases, { 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', } ) def test_heterogeneous_qs_combination(self): # Combining querysets built on different models should behave in a well-defined # fashion. We raise an error. Loading Loading
django/db/models/sql/query.py +30 −4 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ themselves do not have to (and could be backed by things other than SQL databases). The abstraction barrier only works one way: this module has to know all about the internals of models in order to get the information it needs. """ from string import ascii_uppercase from itertools import count, product from collections import OrderedDict import copy Loading Loading @@ -837,13 +839,37 @@ class Query(object): conflict. Even tables that previously had no alias will get an alias after this call. """ def prefix_gen(): """ Generates a sequence of characters in alphabetical order: -> 'A', 'B', 'C', ... When the alphabet is finished, the sequence will continue with the Cartesian product: -> 'AA', 'AB', 'AC', ... """ alphabet = ascii_uppercase prefix = chr(ord(self.alias_prefix) + 1) yield prefix for n in count(1): seq = alphabet[alphabet.index(prefix):] if prefix else alphabet for s in product(seq, repeat=n): yield ''.join(s) prefix = None if self.alias_prefix != outer_query.alias_prefix: # No clashes between self and outer query should be possible. return self.alias_prefix = chr(ord(self.alias_prefix) + 1) while self.alias_prefix in self.subq_aliases: self.alias_prefix = chr(ord(self.alias_prefix) + 1) assert self.alias_prefix < 'Z' local_recursion_limit = 127 # explicitly avoid infinite loop for pos, prefix in enumerate(prefix_gen()): if prefix not in self.subq_aliases: self.alias_prefix = prefix break if pos > local_recursion_limit: raise RuntimeError( 'Maximum recursion depth exceeded: too many subqueries.' ) self.subq_aliases = self.subq_aliases.union([self.alias_prefix]) outer_query.subq_aliases = outer_query.subq_aliases.union(self.subq_aliases) change_map = OrderedDict() Loading
docs/releases/1.7.2.txt +3 −0 Original line number Diff line number Diff line Loading @@ -183,3 +183,6 @@ Bugfixes convention in the template engine (:ticket:`23831`). * Prevented extraneous ``DROP DEFAULT`` SQL in migrations (:ticket:`23581`). * Restored the ability to use more than five levels of subqueries (:ticket:`23758`).
tests/queries/tests.py +20 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ from django.db.models.sql.datastructures import EmptyResultSet from django.test import TestCase, skipUnlessDBFeature from django.test.utils import str_prefix, CaptureQueriesContext from django.utils import six from django.utils.six.moves import range from .models import ( Annotation, Article, Author, Celebrity, Child, Cover, Detail, DumbCategory, Loading Loading @@ -380,6 +381,25 @@ class Queries1Tests(BaseQuerysetTest): ['<Item: four>'] ) def test_avoid_infinite_loop_on_too_many_subqueries(self): x = Tag.objects.filter(pk=1) local_recursion_limit = 127 msg = 'Maximum recursion depth exceeded: too many subqueries.' with self.assertRaisesMessage(RuntimeError, msg): for i in six.moves.range(local_recursion_limit * 2): x = Tag.objects.filter(pk__in=x) def test_reasonable_number_of_subq_aliases(self): x = Tag.objects.filter(pk=1) for _ in range(20): x = Tag.objects.filter(pk__in=x) self.assertEqual( x.query.subq_aliases, { 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG', 'AH', 'AI', 'AJ', 'AK', 'AL', 'AM', 'AN', } ) def test_heterogeneous_qs_combination(self): # Combining querysets built on different models should behave in a well-defined # fashion. We raise an error. Loading